@oxy.so/services 8.1.1 → 8.3.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/index.js +2 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +5 -1
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/RequireOxyAuth.js +9 -1
- package/lib/commonjs/ui/components/RequireOxyAuth.js.map +1 -1
- 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/commonjs/ui/context/OxyContext.js +25 -4
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/accountQueryPersistence.js +157 -0
- package/lib/commonjs/ui/hooks/accountQueryPersistence.js.map +1 -0
- package/lib/commonjs/ui/hooks/queryClient.js +17 -9
- package/lib/commonjs/ui/hooks/queryClient.js.map +1 -1
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/utils/buildId.js +25 -0
- package/lib/commonjs/ui/utils/buildId.js.map +1 -0
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +5 -1
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/RequireOxyAuth.js +9 -1
- package/lib/module/ui/components/RequireOxyAuth.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/module/ui/context/OxyContext.js +27 -6
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/accountQueryPersistence.js +153 -0
- package/lib/module/ui/hooks/accountQueryPersistence.js.map +1 -0
- package/lib/module/ui/hooks/queryClient.js +18 -10
- package/lib/module/ui/hooks/queryClient.js.map +1 -1
- package/lib/module/ui/index.js +3 -0
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/utils/buildId.js +21 -0
- package/lib/module/ui/utils/buildId.js.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +1 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts.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/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +9 -0
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/accountQueryPersistence.d.ts +54 -0
- package/lib/typescript/commonjs/ui/hooks/accountQueryPersistence.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/queryClient.d.ts +8 -2
- package/lib/typescript/commonjs/ui/hooks/queryClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/icons/Ionicons.d.ts +1 -1
- package/lib/typescript/commonjs/ui/icons/MaterialCommunityIcons.d.ts +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts +1 -0
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/navigation.d.ts +9 -0
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/buildId.d.ts +16 -0
- package/lib/typescript/commonjs/ui/utils/buildId.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +1 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/RequireOxyAuth.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/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +9 -0
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/accountQueryPersistence.d.ts +54 -0
- package/lib/typescript/module/ui/hooks/accountQueryPersistence.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/queryClient.d.ts +8 -2
- package/lib/typescript/module/ui/hooks/queryClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/icons/Ionicons.d.ts +1 -1
- package/lib/typescript/module/ui/icons/MaterialCommunityIcons.d.ts +1 -1
- package/lib/typescript/module/ui/index.d.ts +1 -0
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/navigation.d.ts +9 -0
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/buildId.d.ts +16 -0
- package/lib/typescript/module/ui/utils/buildId.d.ts.map +1 -0
- package/package.json +7 -7
- package/src/index.ts +2 -0
- package/src/ui/components/OxyProvider.tsx +5 -1
- package/src/ui/components/RequireOxyAuth.tsx +7 -2
- 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
- package/src/ui/context/OxyContext.tsx +35 -5
- package/src/ui/context/oxyContextTypes.ts +9 -0
- package/src/ui/hooks/accountQueryPersistence.ts +201 -0
- package/src/ui/hooks/queryClient.ts +17 -8
- package/src/ui/index.ts +2 -0
- package/src/ui/types/navigation.ts +9 -0
- package/src/ui/utils/buildId.ts +18 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../../../src/ui/types/navigation.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../../../src/ui/types/navigation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identity of the JS bundle that is running, used as the buster for every
|
|
3
|
+
* persisted query cache the SDK owns.
|
|
4
|
+
*
|
|
5
|
+
* A persisted cache is restored and served before any refetch (offline-first),
|
|
6
|
+
* so data written by an older build reaches components in whatever shape that
|
|
7
|
+
* build used. Busting on the build means no app ever has to remember to bump a
|
|
8
|
+
* version when a query's shape changes: a new build simply starts from the
|
|
9
|
+
* network. Inbox shipped exactly that crash when a hand-kept buster was missed.
|
|
10
|
+
*
|
|
11
|
+
* `@oxy.so/app-preset`'s Babel config replaces `process.env.OXY_BUILD_ID` with
|
|
12
|
+
* the commit at bundle time, node_modules included. An app that does not use
|
|
13
|
+
* the preset gets a constant, which behaves like a buster that is never bumped.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getOxyBuildId(): string;
|
|
16
|
+
//# sourceMappingURL=buildId.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildId.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/buildId.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAGtC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxy.so/services",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.3.0",
|
|
4
4
|
"description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
},
|
|
156
156
|
"dependencies": {
|
|
157
157
|
"@expo/config-plugins": "57.0.9",
|
|
158
|
-
"@oxy.so/contracts": "^4.
|
|
158
|
+
"@oxy.so/contracts": "^4.1.0",
|
|
159
159
|
"color": "^4.2.3"
|
|
160
160
|
},
|
|
161
161
|
"devDependencies": {
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
"@commitlint/cli": "^21.2.1",
|
|
164
164
|
"@commitlint/config-conventional": "^21.2.0",
|
|
165
165
|
"@expo/vector-icons": "^15.0.3",
|
|
166
|
-
"@oxy.so/core": "3.0
|
|
166
|
+
"@oxy.so/core": "3.1.0",
|
|
167
167
|
"@react-native-async-storage/async-storage": "^2.0.0",
|
|
168
168
|
"@react-native-community/netinfo": "^12.0.1",
|
|
169
169
|
"@react-native/babel-preset": "^0.86.2",
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
"@types/jest": "^30.0.0",
|
|
179
179
|
"@types/node": "^20.19.43",
|
|
180
180
|
"@types/react": "*",
|
|
181
|
-
"@oxy.so/bloom": "^4.
|
|
181
|
+
"@oxy.so/bloom": "^4.31.0",
|
|
182
182
|
"copyfiles": "^2.4.1",
|
|
183
183
|
"expo-constants": "~57.0.5",
|
|
184
184
|
"expo-file-system": "~57.0.1",
|
|
@@ -211,11 +211,11 @@
|
|
|
211
211
|
"typescript": "^5.9.2",
|
|
212
212
|
"zustand": "^5.0.14"
|
|
213
213
|
},
|
|
214
|
-
"//@oxy.so/bloom": "RE-RAISE THIS FLOOR ON EVERY BREAKING BLOOM RELEASE, in the same commit as the root package.json catalog entry and the code that requires it. The breaking position is the MAJOR, so `^3.0.1` means `>=3.0.1 <4.0.0`: 3.x minors are admitted on semver's additive promise, and Bloom 4.0.0 is refused until someone raises this deliberately. The CAP is the part that is load-bearing. An earlier revision of this key recorded the opposite trade -- an uncapped `>=` floor, accepted so that intentionally-breaking Bloom 0.x minors would not be blocked -- and that trade is closed by evidence rather than by preference: an unbounded range is exactly what let `bun install` pair this package with Bloom 1.0.0 and emit no warning at all, and 1.0.0 broke it four ways (a deleted provider, a renamed confirm option, a renamed Chip fill, and two REMOVED SUBPATHS that fail at bundle time, before any typecheck runs). A floor is a claim about what was measured; a cap is the claim that nothing beyond it was. RAISED TO 3.0.1 from `^2.1.0`. Bloom 3.0.0 breaks exactly two things -- `PageHeader` defaults to `presentation=\"floating\"` and stops hiding the title under `transparent`, and `Field disabled` now reaches the control that reads the field context -- and THIS PACKAGE IMPORTS NEITHER: it renders no `PageHeader` and no `Field`, and 3.0 removes no subpath (unlike 2.0.0, which is why that floor cost code changes and this one does not). Its six `transparent` occurrences are CSS colour values, not the PageHeader prop. The cap had to move rather than widen to a union, because leaving `^2.1.0` in place is what made `bun install` resolve a SECOND, nested Bloom 2.12.4 under this package for any app on Bloom 3 -- two copies of a library whose 3.0 contracts (`control-surface`, `TopEdgeProvider`, `ScrollOffsetProvider`) are React contexts, so a provider from one copy cannot be read by a consumer from the other. Mention's `oxyRuntimeCompatibility` test is what caught it; nothing in a typecheck can. RAISED AGAIN TO 3.2.0 from `^3.0.1`, for the same class of reason the 2.x note records, only in the other direction: 3.2.0 ADDS the per-glyph icon subpaths (`@oxy.so/bloom/icons/RiAtLine`) that this package's screens now import instead of the `@oxy.so/bloom/icons` barrel, and a missing subpath fails at bundle time, in the consumer's Metro, long after this package has typechecked and shipped. `^3.0.1` would have admitted Bloom 3.0.1 and 3.1.0, neither of which has those subpaths, so the floor is the only thing standing between a consumer and a red build. Nothing else about 3.1.0 or 3.2.0 is required here; the cap stays at the major. RAISED TO ^4.2.0: FollowButton now delegates its visuals to Bloom media-header FollowButton with loading, textStyle and large size; the new shared UI contracts require one Bloom4 context across Services and its consumer.",
|
|
214
|
+
"//@oxy.so/bloom": "RE-RAISE THIS FLOOR ON EVERY BREAKING BLOOM RELEASE, in the same commit as the root package.json catalog entry and the code that requires it. The breaking position is the MAJOR, so `^3.0.1` means `>=3.0.1 <4.0.0`: 3.x minors are admitted on semver's additive promise, and Bloom 4.0.0 is refused until someone raises this deliberately. The CAP is the part that is load-bearing. An earlier revision of this key recorded the opposite trade -- an uncapped `>=` floor, accepted so that intentionally-breaking Bloom 0.x minors would not be blocked -- and that trade is closed by evidence rather than by preference: an unbounded range is exactly what let `bun install` pair this package with Bloom 1.0.0 and emit no warning at all, and 1.0.0 broke it four ways (a deleted provider, a renamed confirm option, a renamed Chip fill, and two REMOVED SUBPATHS that fail at bundle time, before any typecheck runs). A floor is a claim about what was measured; a cap is the claim that nothing beyond it was. RAISED TO 3.0.1 from `^2.1.0`. Bloom 3.0.0 breaks exactly two things -- `PageHeader` defaults to `presentation=\"floating\"` and stops hiding the title under `transparent`, and `Field disabled` now reaches the control that reads the field context -- and THIS PACKAGE IMPORTS NEITHER: it renders no `PageHeader` and no `Field`, and 3.0 removes no subpath (unlike 2.0.0, which is why that floor cost code changes and this one does not). Its six `transparent` occurrences are CSS colour values, not the PageHeader prop. The cap had to move rather than widen to a union, because leaving `^2.1.0` in place is what made `bun install` resolve a SECOND, nested Bloom 2.12.4 under this package for any app on Bloom 3 -- two copies of a library whose 3.0 contracts (`control-surface`, `TopEdgeProvider`, `ScrollOffsetProvider`) are React contexts, so a provider from one copy cannot be read by a consumer from the other. Mention's `oxyRuntimeCompatibility` test is what caught it; nothing in a typecheck can. RAISED AGAIN TO 3.2.0 from `^3.0.1`, for the same class of reason the 2.x note records, only in the other direction: 3.2.0 ADDS the per-glyph icon subpaths (`@oxy.so/bloom/icons/RiAtLine`) that this package's screens now import instead of the `@oxy.so/bloom/icons` barrel, and a missing subpath fails at bundle time, in the consumer's Metro, long after this package has typechecked and shipped. `^3.0.1` would have admitted Bloom 3.0.1 and 3.1.0, neither of which has those subpaths, so the floor is the only thing standing between a consumer and a red build. Nothing else about 3.1.0 or 3.2.0 is required here; the cap stays at the major. RAISED TO ^4.2.0: FollowButton now delegates its visuals to Bloom media-header FollowButton with loading, textStyle and large size; the new shared UI contracts require one Bloom4 context across Services and its consumer. RAISED TO ^4.31.0: the sign-in and account screens type one-time codes into `InputOtp` with `type=\"alphanumeric\"` (the long sign-in code and backup codes), which 4.31.0 adds; older 4.x drops the letters.",
|
|
215
215
|
"peerDependencies": {
|
|
216
216
|
"@expo/vector-icons": "^15.0.3",
|
|
217
|
-
"@oxy.so/bloom": "^4.
|
|
218
|
-
"@oxy.so/core": "^3.
|
|
217
|
+
"@oxy.so/bloom": "^4.31.0",
|
|
218
|
+
"@oxy.so/core": "^3.1.0",
|
|
219
219
|
"@react-native-async-storage/async-storage": "^2.0.0",
|
|
220
220
|
"@react-native-community/netinfo": ">=11.4.1",
|
|
221
221
|
"@tanstack/query-async-storage-persister": "^5.101",
|
package/src/index.ts
CHANGED
|
@@ -306,6 +306,8 @@ export type {
|
|
|
306
306
|
// wall before the device-first cold boot resolves. Opens the ONE account dialog.
|
|
307
307
|
export { RequireOxyAuth } from './ui/components/RequireOxyAuth';
|
|
308
308
|
export type { RequireOxyAuthProps, RequireOxyAuthPrompt } from './ui/components/RequireOxyAuth';
|
|
309
|
+
// `OxyProvider`'s `accountQueries`: the app's per-account offline cache.
|
|
310
|
+
export type { AccountQueriesConfig } from './ui/hooks/accountQueryPersistence';
|
|
309
311
|
|
|
310
312
|
export { default as FollowButton } from './ui/components/FollowButton';
|
|
311
313
|
export type { FollowButtonProps, SingleFollowButtonProps, MultiFollowButtonProps } from './ui/components/FollowButton';
|
|
@@ -88,6 +88,7 @@ const OxyProvider: FC<OxyProviderProps> = ({
|
|
|
88
88
|
sessionMode = 'account',
|
|
89
89
|
webAuthMode = 'popup',
|
|
90
90
|
queryClient: providedQueryClient,
|
|
91
|
+
accountQueries,
|
|
91
92
|
requireAuth = 'off',
|
|
92
93
|
backgroundSession = false,
|
|
93
94
|
language,
|
|
@@ -102,6 +103,8 @@ const OxyProvider: FC<OxyProviderProps> = ({
|
|
|
102
103
|
const queryClientRef = useRef<ReturnType<typeof createQueryClient> | null>(null);
|
|
103
104
|
const persistenceUnsubRef = useRef<(() => void) | null>(null);
|
|
104
105
|
const ownsQueryClientRef = useRef(providedQueryClient === undefined);
|
|
106
|
+
// `accountQueries` is read once, at mount, like the client itself.
|
|
107
|
+
const accountMutationKeysRef = useRef(accountQueries?.mutationKeys);
|
|
105
108
|
const [platformStorage, setPlatformStorage] = useState<StorageInterface | null>(null);
|
|
106
109
|
|
|
107
110
|
// If the consumer supplied their own QueryClient we use it as-is and skip
|
|
@@ -135,7 +138,7 @@ const OxyProvider: FC<OxyProviderProps> = ({
|
|
|
135
138
|
setPlatformStorage(storage);
|
|
136
139
|
const client = queryClientRef.current;
|
|
137
140
|
if (!client || !ownsQueryClientRef.current) return;
|
|
138
|
-
const persistence = attachQueryPersistence(client, storage);
|
|
141
|
+
const persistence = attachQueryPersistence(client, storage, accountMutationKeysRef.current);
|
|
139
142
|
persistenceUnsubRef.current = persistence.unsubscribe;
|
|
140
143
|
await persistence.restored;
|
|
141
144
|
};
|
|
@@ -240,6 +243,7 @@ const OxyProvider: FC<OxyProviderProps> = ({
|
|
|
240
243
|
webAuthMode={webAuthMode}
|
|
241
244
|
backgroundSession={backgroundSession}
|
|
242
245
|
platformStorage={platformStorage}
|
|
246
|
+
accountQueries={accountQueries}
|
|
243
247
|
onAuthStateChange={onAuthStateChange as OxyRuntimeProviderProps['onAuthStateChange']}
|
|
244
248
|
>
|
|
245
249
|
{productAnalytics ? <ProductAnalyticsObserver analytics={productAnalytics} /> : null}
|
|
@@ -75,7 +75,7 @@ export const RequireOxyAuth: React.FC<RequireOxyAuthProps> = ({
|
|
|
75
75
|
bannerMessage,
|
|
76
76
|
bannerActionLabel,
|
|
77
77
|
}) => {
|
|
78
|
-
const { canUsePrivateApi, isPrivateApiPending, openAccountDialog } = useOxy();
|
|
78
|
+
const { canUsePrivateApi, isPrivateApiPending, isAccountCacheReady, openAccountDialog } = useOxy();
|
|
79
79
|
|
|
80
80
|
// Public app: render straight through. Cheap enough to short-circuit before
|
|
81
81
|
// touching any gate UI.
|
|
@@ -83,8 +83,13 @@ export const RequireOxyAuth: React.FC<RequireOxyAuthProps> = ({
|
|
|
83
83
|
return <>{children}</>;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
// Signed in (and token ready): render the protected subtree in every mode
|
|
86
|
+
// Signed in (and token ready): render the protected subtree in every mode,
|
|
87
|
+
// once the account's offline cache (`accountQueries`) has restored. Before
|
|
88
|
+
// that it would render with no rows, or with the previous account's.
|
|
87
89
|
if (canUsePrivateApi) {
|
|
90
|
+
if (!isAccountCacheReady) {
|
|
91
|
+
return loadingFallback ? <>{loadingFallback}</> : <NeutralLoading />;
|
|
92
|
+
}
|
|
88
93
|
return <>{children}</>;
|
|
89
94
|
}
|
|
90
95
|
|
|
@@ -30,8 +30,8 @@ import { OxyAuthLoading, OxyAuthScreen, OxyAuthScreenHeader } from './OxyAuthScr
|
|
|
30
30
|
import { ReauthStep } from './ReauthStep';
|
|
31
31
|
import {
|
|
32
32
|
AccountFlowAction,
|
|
33
|
+
AccountFlowCodeField,
|
|
33
34
|
AccountFlowErrorLine,
|
|
34
|
-
AccountFlowField,
|
|
35
35
|
AccountFlowNote,
|
|
36
36
|
describeSignInError,
|
|
37
37
|
} from './accountFlowParts';
|
|
@@ -161,16 +161,12 @@ export const OxyAuthenticatorPanel: React.FC<OxyAuthenticatorPanelProps> = ({ on
|
|
|
161
161
|
{enrollment.secret.replace(/(.{4})/g, '$1 ').trim()}
|
|
162
162
|
</Text>
|
|
163
163
|
</View>
|
|
164
|
-
<
|
|
164
|
+
<AccountFlowCodeField
|
|
165
165
|
label={t('signInSecurity.totp.codeLabel')}
|
|
166
166
|
value={code}
|
|
167
|
-
onChange={
|
|
168
|
-
onSubmit={() => undefined}
|
|
167
|
+
onChange={setCode}
|
|
169
168
|
error={null}
|
|
170
|
-
|
|
171
|
-
autoComplete="one-time-code"
|
|
172
|
-
keyboardType="number-pad"
|
|
173
|
-
maxLength={TOTP_DIGITS}
|
|
169
|
+
length={TOTP_DIGITS}
|
|
174
170
|
testID="totp-enroll-code"
|
|
175
171
|
/>
|
|
176
172
|
</ReauthStep>
|
|
@@ -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}
|