@ankhorage/studio 2.0.1 → 2.0.3
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 +1 -1
- package/dist/host/layout/auth/resolveAuthLayoutPlan.d.ts +1 -1
- package/dist/host/layout/auth/resolveAuthLayoutPlan.d.ts.map +1 -1
- package/dist/host/layout/auth/resolveAuthLayoutPlan.js +24 -1
- package/dist/host/layout/auth/resolveAuthLayoutPlan.js.map +1 -1
- package/dist/host/layout/layoutGenerator.d.ts.map +1 -1
- package/dist/host/layout/layoutGenerator.js +45 -20
- package/dist/host/layout/layoutGenerator.js.map +1 -1
- package/dist/host/layout/templates/auth/adapter.js +1 -1
- package/dist/host/layout/templates/auth/adapter.js.map +1 -1
- package/dist/host/layout/templates/auth/callback.d.ts.map +1 -1
- package/dist/host/layout/templates/auth/callback.js +80 -42
- package/dist/host/layout/templates/auth/callback.js.map +1 -1
- package/dist/host/layout/templates/auth/form.d.ts +2 -0
- package/dist/host/layout/templates/auth/form.d.ts.map +1 -0
- package/dist/host/layout/templates/auth/form.js +238 -0
- package/dist/host/layout/templates/auth/form.js.map +1 -0
- package/dist/host/layout/templates/auth/navigation.d.ts +10 -0
- package/dist/host/layout/templates/auth/navigation.d.ts.map +1 -0
- package/dist/host/layout/templates/auth/navigation.js +183 -0
- package/dist/host/layout/templates/auth/navigation.js.map +1 -0
- package/dist/host/layout/templates/auth/oauth.d.ts.map +1 -1
- package/dist/host/layout/templates/auth/oauth.js +146 -217
- package/dist/host/layout/templates/auth/oauth.js.map +1 -1
- package/dist/host/layout/templates/auth/oauthCompletion.d.ts +6 -0
- package/dist/host/layout/templates/auth/oauthCompletion.d.ts.map +1 -0
- package/dist/host/layout/templates/auth/oauthCompletion.js +147 -0
- package/dist/host/layout/templates/auth/oauthCompletion.js.map +1 -0
- package/dist/host/layout/templates/auth/oauthState.d.ts +2 -0
- package/dist/host/layout/templates/auth/oauthState.d.ts.map +1 -0
- package/dist/host/layout/templates/auth/oauthState.js +45 -0
- package/dist/host/layout/templates/auth/oauthState.js.map +1 -0
- package/dist/host/layout/templates/auth/screen.d.ts +5 -2
- package/dist/host/layout/templates/auth/screen.d.ts.map +1 -1
- package/dist/host/layout/templates/auth/screen.js +296 -531
- package/dist/host/layout/templates/auth/screen.js.map +1 -1
- package/dist/host/layout/templates/auth/session.js +6 -6
- package/dist/host/layout/templates/auth/signOut.d.ts.map +1 -1
- package/dist/host/layout/templates/auth/signOut.js +10 -9
- package/dist/host/layout/templates/auth/signOut.js.map +1 -1
- package/dist/host/layout/templates/index.d.ts +5 -1
- package/dist/host/layout/templates/index.d.ts.map +1 -1
- package/dist/host/layout/templates/index.js +5 -1
- package/dist/host/layout/templates/index.js.map +1 -1
- package/dist/host/layout/templates/rootLayout.d.ts.map +1 -1
- package/dist/host/layout/templates/rootLayout.js +9 -149
- package/dist/host/layout/templates/rootLayout.js.map +1 -1
- package/dist/host/orchestrator/generatedAppFiles.d.ts.map +1 -1
- package/dist/host/orchestrator/generatedAppFiles.js +6 -4
- package/dist/host/orchestrator/generatedAppFiles.js.map +1 -1
- package/dist/host/orchestrator/generatedRouteCleanup.js +1 -1
- package/dist/host/orchestrator/generatedRouteCleanup.js.map +1 -1
- package/dist/host/orchestrator/scaffolder.js +3 -3
- package/dist/host/orchestrator/scaffolder.js.map +1 -1
- package/dist/host/orchestrator/templates.d.ts.map +1 -1
- package/dist/host/orchestrator/templates.js +54 -52
- package/dist/host/orchestrator/templates.js.map +1 -1
- package/dist/projectDeployAssetPicker.d.ts +2 -1
- package/dist/projectDeployAssetPicker.d.ts.map +1 -1
- package/dist/projectDeployAssetPicker.js +27 -21
- package/dist/projectDeployAssetPicker.js.map +1 -1
- package/package.json +5 -4
|
@@ -1,350 +1,324 @@
|
|
|
1
1
|
import { escapeStringLiteral } from '../../utils/escapeStringLiteral';
|
|
2
2
|
import { routeNameToGroupedHref } from '../utils/routes';
|
|
3
3
|
import { toSafeComponentName } from '../utils/strings';
|
|
4
|
-
const AUTH_SCREEN_CONTAINER_PADDING = 24;
|
|
5
|
-
const AUTH_SCREEN_CARD_MAX_WIDTH = 560;
|
|
6
4
|
function serializeStringArrayLiteral(values) {
|
|
7
5
|
return `[${values.map((value) => `'${escapeStringLiteral(value)}'`).join(', ')}]`;
|
|
8
6
|
}
|
|
9
7
|
export function getAuthScreenTsx(args) {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
startOAuthAuthorization,
|
|
23
|
-
} from '@/auth/oauth';\n`
|
|
24
|
-
: '';
|
|
25
|
-
const oauthState = oauthEnabled
|
|
26
|
-
? ` const [oauthLoadingProvider, setOAuthLoadingProvider] = useState<string | null>(null);\n`
|
|
27
|
-
: '';
|
|
28
|
-
const oauthHandler = oauthEnabled
|
|
29
|
-
? `
|
|
30
|
-
async function handleOAuthProviderPress(providerId: string) {
|
|
31
|
-
if (loading || oauthLoadingProvider !== null) return;
|
|
32
|
-
setError(null);
|
|
33
|
-
setInfo(null);
|
|
34
|
-
setOAuthLoadingProvider(providerId);
|
|
35
|
-
try {
|
|
36
|
-
const outcome = await startOAuthAuthorization(providerId);
|
|
37
|
-
if (outcome.status === 'authenticated') {
|
|
38
|
-
router.replace(POST_SIGN_IN_ROUTE);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (outcome.status === 'cancelled') {
|
|
42
|
-
setInfo(outcome.message);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
setError(outcome.message);
|
|
46
|
-
} catch (caught) {
|
|
47
|
-
setError(getErrorMessage(caught));
|
|
48
|
-
} finally {
|
|
49
|
-
setOAuthLoadingProvider(null);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
`
|
|
53
|
-
: '';
|
|
54
|
-
const oauthJsx = oauthEnabled
|
|
55
|
-
? `
|
|
56
|
-
<OAuthProviderList
|
|
57
|
-
disabled={loading}
|
|
58
|
-
onProviderPress={handleOAuthProviderPress}
|
|
59
|
-
providers={generatedOAuthProviderItems.map((provider) => ({
|
|
60
|
-
...provider,
|
|
61
|
-
disabled:
|
|
62
|
-
oauthLoadingProvider !== null && oauthLoadingProvider !== provider.id,
|
|
63
|
-
loading: oauthLoadingProvider === provider.id,
|
|
64
|
-
}))}
|
|
65
|
-
/>
|
|
66
|
-
<View style={styles.separatorRow}>
|
|
67
|
-
<View style={[styles.separatorLine, { backgroundColor: theme.colors.border }]} />
|
|
68
|
-
<Text emphasis="muted" variant="bodySmall">
|
|
69
|
-
or continue with password
|
|
70
|
-
</Text>
|
|
71
|
-
<View style={[styles.separatorLine, { backgroundColor: theme.colors.border }]} />
|
|
72
|
-
</View>
|
|
73
|
-
`
|
|
8
|
+
const safeName = toSafeComponentName(args.screenName);
|
|
9
|
+
return `import { GeneratedAuthScreen } from '@/screens/auth-screen';
|
|
10
|
+
|
|
11
|
+
export default function ${safeName}Screen() {
|
|
12
|
+
return <GeneratedAuthScreen initialMode="${args.initialMode}" title="${escapeStringLiteral(args.title ?? args.screenName)}" />;
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
}
|
|
16
|
+
export function getAuthScreenRuntimeTsx(args) {
|
|
17
|
+
const oauthEnabled = (args.oauthProviders?.length ?? 0) > 0;
|
|
18
|
+
const oauthImports = oauthEnabled
|
|
19
|
+
? `import { generatedOAuthProviderItems, startOAuthAuthorization } from '@/auth/oauth';\n`
|
|
74
20
|
: '';
|
|
75
|
-
const
|
|
76
|
-
const
|
|
21
|
+
const oauthZoraImport = oauthEnabled ? ' OAuthProviderList,\n' : '';
|
|
22
|
+
const oauthController = oauthEnabled
|
|
23
|
+
? getOAuthControllerSource()
|
|
24
|
+
: `
|
|
25
|
+
function handleOAuthProviderPress(): Promise<void> {
|
|
26
|
+
return Promise.resolve();
|
|
27
|
+
}`;
|
|
28
|
+
const oauthView = oauthEnabled ? getOAuthViewSource() : '';
|
|
29
|
+
const formLoading = oauthEnabled
|
|
30
|
+
? 'controller.loading || controller.oauthLoadingProvider !== null'
|
|
31
|
+
: 'controller.loading';
|
|
32
|
+
const postSignInTarget = routeNameToGroupedHref(args.postSignInRoute, 'app');
|
|
77
33
|
return `import type { AppManifest } from '@ankhorage/contracts';
|
|
78
|
-
import
|
|
34
|
+
import { ManifestProvider } from '@ankhorage/runtime';
|
|
79
35
|
import {
|
|
80
|
-
|
|
81
|
-
${zoraOAuthImport} SignInForm,
|
|
36
|
+
${oauthZoraImport} SignInForm,
|
|
82
37
|
type SignInFormValues,
|
|
83
38
|
SignUpForm,
|
|
84
|
-
type SignUpFormField,
|
|
85
39
|
type SignUpFormValues,
|
|
86
40
|
Text,
|
|
87
41
|
useZoraTheme,
|
|
88
42
|
} from '@ankhorage/zora';
|
|
89
|
-
import { ManifestProvider } from '@ankhorage/runtime';
|
|
90
|
-
import { isEmail, isPhone, isUsername } from '@ankhorage/utility/regex';
|
|
91
43
|
import ankhConfig from '@root/ankh.config.json';
|
|
92
44
|
import { Stack, useRouter } from 'expo-router';
|
|
93
|
-
import
|
|
45
|
+
import { useMemo, useState } from 'react';
|
|
94
46
|
import { StyleSheet, View } from 'react-native';
|
|
95
47
|
|
|
96
48
|
import { authAdapter } from '@/auth/adapter';
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
49
|
+
import {
|
|
50
|
+
type AuthSubmitValues,
|
|
51
|
+
buildAuthIdentifierInput,
|
|
52
|
+
buildSignUpFields,
|
|
53
|
+
buildSignUpProfile,
|
|
54
|
+
getFormValue,
|
|
55
|
+
isAuthSession,
|
|
56
|
+
resolveAuthIdentifiers,
|
|
57
|
+
resolveIdentifierFieldDefinition,
|
|
58
|
+
validateAuthSubmitValues,
|
|
59
|
+
} from '@/auth/form';
|
|
60
|
+
${oauthImports}import { clearStoredAuthSession, setStoredAuthSession } from '@/auth/session';
|
|
61
|
+
|
|
62
|
+
const SIGN_IN_IDENTIFIERS: string[] = ${serializeStringArrayLiteral(args.signInIdentifiers)};
|
|
63
|
+
const SIGN_UP_REQUIRED_FIELDS: string[] = ${serializeStringArrayLiteral(args.signUpRequiredFields)};
|
|
64
|
+
const SIGN_UP_OPTIONAL_FIELDS: string[] = ${serializeStringArrayLiteral(args.signUpOptionalFields)};
|
|
65
|
+
const SIGN_IN_ROUTE = '${escapeStringLiteral(routeNameToGroupedHref(args.signInRoute, 'auth'))}';
|
|
66
|
+
const SIGN_UP_ROUTE = '${escapeStringLiteral(routeNameToGroupedHref(args.signUpRoute, 'auth'))}';
|
|
104
67
|
const POST_SIGN_IN_ROUTE = '${escapeStringLiteral(postSignInTarget)}';
|
|
68
|
+
const AUTO_SIGN_IN_AFTER_SIGN_UP = ${String(args.signUpPolicy === 'autoSignIn')};
|
|
105
69
|
const fallbackManifest = ankhConfig as unknown as AppManifest;
|
|
106
|
-
const authScreenOptions = {
|
|
107
|
-
title: '${escapeStringLiteral(title ?? screenName)}',
|
|
108
|
-
};
|
|
109
70
|
|
|
110
71
|
type AuthMode = 'signIn' | 'signUp';
|
|
111
72
|
|
|
112
|
-
interface
|
|
73
|
+
interface AuthScreenController {
|
|
74
|
+
authIdentifiers: ReturnType<typeof resolveAuthIdentifiers>;
|
|
75
|
+
error: string | null;
|
|
76
|
+
handleOAuthProviderPress: (providerId: string) => Promise<void>;
|
|
77
|
+
handleSignInSubmit: (values: SignInFormValues) => Promise<void>;
|
|
78
|
+
handleSignUpSubmit: (values: SignUpFormValues) => Promise<void>;
|
|
79
|
+
identifierField: ReturnType<typeof resolveIdentifierFieldDefinition>;
|
|
80
|
+
info: string | null;
|
|
81
|
+
loading: boolean;
|
|
113
82
|
mode: AuthMode;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
displayName: string;
|
|
83
|
+
oauthLoadingProvider: string | null;
|
|
84
|
+
showSignIn: () => void;
|
|
85
|
+
showSignUp: () => void;
|
|
86
|
+
signUpFields: ReturnType<typeof buildSignUpFields>;
|
|
119
87
|
}
|
|
120
88
|
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
89
|
+
export function GeneratedAuthScreen({
|
|
90
|
+
initialMode,
|
|
91
|
+
title,
|
|
92
|
+
}: {
|
|
93
|
+
initialMode: AuthMode;
|
|
94
|
+
title: string;
|
|
95
|
+
}) {
|
|
96
|
+
const controller = useAuthScreenController(initialMode);
|
|
97
|
+
const { theme } = useZoraTheme();
|
|
98
|
+
return (
|
|
99
|
+
<ManifestProvider manifest={fallbackManifest}>
|
|
100
|
+
<View style={[styles.container, { backgroundColor: theme.colors.background }]}>
|
|
101
|
+
<Stack.Screen options={{ title }} />
|
|
102
|
+
<View
|
|
103
|
+
style={[
|
|
104
|
+
styles.card,
|
|
105
|
+
{ backgroundColor: theme.colors.surface, borderColor: theme.colors.border },
|
|
106
|
+
]}
|
|
107
|
+
>
|
|
108
|
+
<AuthScreenContent borderColor={theme.colors.border} controller={controller} />
|
|
109
|
+
</View>
|
|
110
|
+
</View>
|
|
111
|
+
</ManifestProvider>
|
|
112
|
+
);
|
|
125
113
|
}
|
|
126
114
|
|
|
127
|
-
|
|
115
|
+
function useAuthScreenController(initialMode: AuthMode): AuthScreenController {
|
|
128
116
|
const router = useRouter();
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
const [mode, setMode] = useState<AuthMode>('${initialMode}');
|
|
117
|
+
const [mode, setMode] = useState<AuthMode>(initialMode);
|
|
132
118
|
const [loading, setLoading] = useState(false);
|
|
133
|
-
|
|
119
|
+
const [oauthLoadingProvider, setOAuthLoadingProvider] = useState<string | null>(null);
|
|
120
|
+
const [error, setError] = useState<string | null>(null);
|
|
134
121
|
const [info, setInfo] = useState<string | null>(null);
|
|
135
|
-
|
|
136
122
|
const identifierField = useMemo(() => resolveIdentifierFieldDefinition(SIGN_IN_IDENTIFIERS), []);
|
|
137
123
|
const authIdentifiers = useMemo(() => resolveAuthIdentifiers(SIGN_IN_IDENTIFIERS), []);
|
|
138
124
|
const signUpFields = useMemo(
|
|
139
125
|
() =>
|
|
140
126
|
buildSignUpFields({
|
|
141
127
|
identifierField,
|
|
142
|
-
configuredFields:
|
|
128
|
+
configuredFields: [...new Set([...SIGN_UP_REQUIRED_FIELDS, ...SIGN_UP_OPTIONAL_FIELDS])],
|
|
143
129
|
requiredFields: SIGN_UP_REQUIRED_FIELDS,
|
|
144
130
|
}),
|
|
145
131
|
[identifierField],
|
|
146
132
|
);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
setMode('signIn');
|
|
133
|
+
const showMode = (nextMode: AuthMode) => {
|
|
134
|
+
setMode(nextMode);
|
|
150
135
|
setError(null);
|
|
151
136
|
setInfo(null);
|
|
152
|
-
router.replace(SIGN_IN_ROUTE);
|
|
153
|
-
}
|
|
137
|
+
router.replace(nextMode === 'signIn' ? SIGN_IN_ROUTE : SIGN_UP_ROUTE);
|
|
138
|
+
};
|
|
139
|
+
const submit = (values: AuthSubmitValues) =>
|
|
140
|
+
submitAuthFormAsync({ router, setError, setInfo, setLoading, values });
|
|
141
|
+
const handleSignInSubmit = (values: SignInFormValues) => submit(createSignInSubmitValues(values));
|
|
142
|
+
const handleSignUpSubmit = (values: SignUpFormValues) => submit(createSignUpSubmitValues(values));${oauthController}
|
|
143
|
+
return {
|
|
144
|
+
authIdentifiers,
|
|
145
|
+
error,
|
|
146
|
+
handleOAuthProviderPress,
|
|
147
|
+
handleSignInSubmit,
|
|
148
|
+
handleSignUpSubmit,
|
|
149
|
+
identifierField,
|
|
150
|
+
info,
|
|
151
|
+
loading,
|
|
152
|
+
mode,
|
|
153
|
+
oauthLoadingProvider,
|
|
154
|
+
showSignIn: () => showMode('signIn'),
|
|
155
|
+
showSignUp: () => showMode('signUp'),
|
|
156
|
+
signUpFields,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
154
159
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
function AuthScreenContent({
|
|
161
|
+
borderColor,
|
|
162
|
+
controller,
|
|
163
|
+
}: {
|
|
164
|
+
borderColor: string;
|
|
165
|
+
controller: AuthScreenController;
|
|
166
|
+
}) {
|
|
167
|
+
return (
|
|
168
|
+
<>
|
|
169
|
+
<Text variant="lead" weight="semiBold">
|
|
170
|
+
{controller.mode === 'signIn' ? 'Sign in' : 'Create account'}
|
|
171
|
+
</Text>
|
|
172
|
+
<Text emphasis="muted" variant="bodySmall">
|
|
173
|
+
{controller.identifierField.helper}
|
|
174
|
+
</Text>${oauthView}
|
|
175
|
+
<AuthForm controller={controller} />
|
|
176
|
+
{controller.info ? (
|
|
177
|
+
<Text color="success" variant="bodySmall">
|
|
178
|
+
{controller.info}
|
|
179
|
+
</Text>
|
|
180
|
+
) : null}
|
|
181
|
+
</>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
161
184
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
185
|
+
function AuthForm({ controller }: { controller: AuthScreenController }) {
|
|
186
|
+
if (controller.mode === 'signIn') {
|
|
187
|
+
return (
|
|
188
|
+
<SignInForm
|
|
189
|
+
error={controller.error}
|
|
190
|
+
identifierLabel={controller.identifierField.label}
|
|
191
|
+
identifiers={controller.authIdentifiers}
|
|
192
|
+
loading={${formLoading}}
|
|
193
|
+
onSignUp={controller.showSignUp}
|
|
194
|
+
onSubmit={controller.handleSignInSubmit}
|
|
195
|
+
signUpLabel="Need an account? Sign up"
|
|
196
|
+
submitLabel="Sign in"
|
|
197
|
+
/>
|
|
198
|
+
);
|
|
171
199
|
}
|
|
200
|
+
return (
|
|
201
|
+
<SignUpForm
|
|
202
|
+
error={controller.error}
|
|
203
|
+
fields={controller.signUpFields}
|
|
204
|
+
loading={${formLoading}}
|
|
205
|
+
onSignIn={controller.showSignIn}
|
|
206
|
+
onSubmit={controller.handleSignUpSubmit}
|
|
207
|
+
signInLabel="Already have an account? Sign in"
|
|
208
|
+
submitLabel="Create account"
|
|
209
|
+
/>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
172
212
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
213
|
+
async function submitAuthFormAsync(args: {
|
|
214
|
+
router: ReturnType<typeof useRouter>;
|
|
215
|
+
setError: (message: string | null) => void;
|
|
216
|
+
setInfo: (message: string | null) => void;
|
|
217
|
+
setLoading: (loading: boolean) => void;
|
|
218
|
+
values: AuthSubmitValues;
|
|
219
|
+
}): Promise<void> {
|
|
220
|
+
const { router, setError, setInfo, setLoading, values } = args;
|
|
221
|
+
setError(null);
|
|
222
|
+
setInfo(null);
|
|
223
|
+
const validationError = validateAuthSubmitValues(
|
|
224
|
+
values,
|
|
225
|
+
SIGN_IN_IDENTIFIERS,
|
|
226
|
+
SIGN_UP_REQUIRED_FIELDS,
|
|
227
|
+
);
|
|
228
|
+
if (validationError) {
|
|
229
|
+
setError(validationError);
|
|
230
|
+
return;
|
|
182
231
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (identifierValidationError) {
|
|
199
|
-
setError(identifierValidationError);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (normalizedPassword.length < 6) {
|
|
204
|
-
setError('Password must be at least 6 characters.');
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (mode === 'signUp') {
|
|
209
|
-
const signUpValidationError = validateSignUpInput({
|
|
210
|
-
identifier: trimmedIdentifier,
|
|
211
|
-
password: normalizedPassword,
|
|
212
|
-
requiredFields: SIGN_UP_REQUIRED_FIELDS,
|
|
213
|
-
firstName,
|
|
214
|
-
lastName,
|
|
215
|
-
displayName,
|
|
216
|
-
});
|
|
217
|
-
if (signUpValidationError) {
|
|
218
|
-
setError(signUpValidationError);
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const authIdentifier = buildAuthIdentifierInput(trimmedIdentifier, SIGN_IN_IDENTIFIERS);
|
|
224
|
-
if (!authIdentifier) {
|
|
225
|
-
setError('Unable to resolve the configured sign-in identifier.');
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
setLoading(true);
|
|
230
|
-
try {
|
|
231
|
-
if (mode === 'signIn') {
|
|
232
|
-
const result = await authAdapter.signIn({
|
|
233
|
-
identifier: authIdentifier,
|
|
234
|
-
password: normalizedPassword,
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
if (!result.ok) {
|
|
238
|
-
setError(result.error.message);
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (!result.data) {
|
|
243
|
-
setError('Sign in succeeded without a session.');
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
await setStoredAuthSession(result.data);
|
|
248
|
-
router.replace(POST_SIGN_IN_ROUTE);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const result = await authAdapter.signUp({
|
|
253
|
-
identifier: authIdentifier,
|
|
254
|
-
password: normalizedPassword,
|
|
255
|
-
profile: buildSignUpProfile({
|
|
256
|
-
firstName,
|
|
257
|
-
lastName,
|
|
258
|
-
displayName,
|
|
259
|
-
}),
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
if (!result.ok) {
|
|
263
|
-
setError(result.error.message);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
232
|
+
const identifier = buildAuthIdentifierInput(values.identifier, SIGN_IN_IDENTIFIERS);
|
|
233
|
+
if (!identifier) {
|
|
234
|
+
setError('Unable to resolve the configured sign-in identifier.');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
setLoading(true);
|
|
238
|
+
try {
|
|
239
|
+
if (values.mode === 'signIn') await signInAsync(identifier, values.password, router, setError);
|
|
240
|
+
else await signUpAsync(identifier, values, router, setError);
|
|
241
|
+
} catch (caught) {
|
|
242
|
+
setError(getErrorMessage(caught));
|
|
243
|
+
} finally {
|
|
244
|
+
setLoading(false);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
266
247
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
248
|
+
async function signInAsync(
|
|
249
|
+
identifier: NonNullable<ReturnType<typeof buildAuthIdentifierInput>>,
|
|
250
|
+
password: string,
|
|
251
|
+
router: ReturnType<typeof useRouter>,
|
|
252
|
+
setError: (message: string) => void,
|
|
253
|
+
): Promise<void> {
|
|
254
|
+
const result = await authAdapter.signIn({ identifier, password });
|
|
255
|
+
if (!result.ok) return setError(result.error.message);
|
|
256
|
+
if (!result.data) return setError('Sign in succeeded without a session.');
|
|
257
|
+
await setStoredAuthSession(result.data);
|
|
258
|
+
router.replace(POST_SIGN_IN_ROUTE);
|
|
259
|
+
}
|
|
271
260
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
261
|
+
async function signUpAsync(
|
|
262
|
+
identifier: NonNullable<ReturnType<typeof buildAuthIdentifierInput>>,
|
|
263
|
+
values: AuthSubmitValues,
|
|
264
|
+
router: ReturnType<typeof useRouter>,
|
|
265
|
+
setError: (message: string) => void,
|
|
266
|
+
): Promise<void> {
|
|
267
|
+
const result = await authAdapter.signUp({
|
|
268
|
+
identifier,
|
|
269
|
+
password: values.password,
|
|
270
|
+
profile: buildSignUpProfile(values),
|
|
271
|
+
});
|
|
272
|
+
if (!result.ok) return setError(result.error.message);
|
|
273
|
+
if (isAuthSession(result.data) && AUTO_SIGN_IN_AFTER_SIGN_UP) {
|
|
274
|
+
await setStoredAuthSession(result.data);
|
|
275
|
+
router.replace(POST_SIGN_IN_ROUTE);
|
|
276
|
+
return;
|
|
278
277
|
}
|
|
278
|
+
await clearStoredAuthSession();
|
|
279
|
+
router.replace(SIGN_IN_ROUTE);
|
|
280
|
+
}
|
|
279
281
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
>
|
|
291
|
-
<Stack.Screen options={authScreenOptions} />
|
|
292
|
-
<View
|
|
293
|
-
style={[
|
|
294
|
-
styles.card,
|
|
295
|
-
{
|
|
296
|
-
backgroundColor: theme.colors.surface,
|
|
297
|
-
borderColor: theme.colors.border,
|
|
298
|
-
},
|
|
299
|
-
]}
|
|
300
|
-
>
|
|
301
|
-
<Text variant="lead" weight="semiBold">
|
|
302
|
-
{mode === 'signIn' ? 'Sign in' : 'Create account'}
|
|
303
|
-
</Text>
|
|
304
|
-
<Text emphasis="muted" variant="bodySmall">
|
|
305
|
-
{identifierField.helper}
|
|
306
|
-
</Text>
|
|
307
|
-
${oauthJsx}
|
|
308
|
-
{mode === 'signIn' ? (
|
|
309
|
-
<SignInForm
|
|
310
|
-
error={error}
|
|
311
|
-
identifierLabel={identifierField.label}
|
|
312
|
-
identifiers={authIdentifiers}
|
|
313
|
-
loading={${formLoading}}
|
|
314
|
-
onSignUp={showSignUp}
|
|
315
|
-
onSubmit={handleSignInSubmit}
|
|
316
|
-
signUpLabel="Need an account? Sign up"
|
|
317
|
-
submitLabel="Sign in"
|
|
318
|
-
/>
|
|
319
|
-
) : (
|
|
320
|
-
<SignUpForm
|
|
321
|
-
error={error}
|
|
322
|
-
fields={signUpFields}
|
|
323
|
-
loading={${formLoading}}
|
|
324
|
-
onSignIn={showSignIn}
|
|
325
|
-
onSubmit={handleSignUpSubmit}
|
|
326
|
-
signInLabel="Already have an account? Sign in"
|
|
327
|
-
submitLabel="Create account"
|
|
328
|
-
/>
|
|
329
|
-
)}
|
|
282
|
+
function createSignInSubmitValues(values: SignInFormValues): AuthSubmitValues {
|
|
283
|
+
return {
|
|
284
|
+
mode: 'signIn',
|
|
285
|
+
identifier: values.identifier,
|
|
286
|
+
password: values.secret,
|
|
287
|
+
firstName: '',
|
|
288
|
+
lastName: '',
|
|
289
|
+
displayName: '',
|
|
290
|
+
};
|
|
291
|
+
}
|
|
330
292
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
293
|
+
function createSignUpSubmitValues(values: SignUpFormValues): AuthSubmitValues {
|
|
294
|
+
return {
|
|
295
|
+
mode: 'signUp',
|
|
296
|
+
identifier: getFormValue(values, 'identifier'),
|
|
297
|
+
password: getFormValue(values, 'password'),
|
|
298
|
+
firstName: getFormValue(values, 'firstName'),
|
|
299
|
+
lastName: getFormValue(values, 'lastName'),
|
|
300
|
+
displayName: getFormValue(values, 'displayName'),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function getErrorMessage(caught: unknown): string {
|
|
305
|
+
if (caught instanceof Error) return caught.message;
|
|
306
|
+
if (typeof caught === 'string') return caught;
|
|
307
|
+
return 'Unknown auth error';
|
|
340
308
|
}
|
|
341
309
|
|
|
342
310
|
const styles = StyleSheet.create({
|
|
311
|
+
container: {
|
|
312
|
+
alignItems: 'center',
|
|
313
|
+
flex: 1,
|
|
314
|
+
justifyContent: 'center',
|
|
315
|
+
padding: 24,
|
|
316
|
+
},
|
|
343
317
|
card: {
|
|
344
318
|
borderRadius: 24,
|
|
345
319
|
borderWidth: 1,
|
|
346
320
|
gap: 16,
|
|
347
|
-
maxWidth:
|
|
321
|
+
maxWidth: 560,
|
|
348
322
|
padding: 24,
|
|
349
323
|
width: '100%',
|
|
350
324
|
},
|
|
@@ -358,255 +332,46 @@ const styles = StyleSheet.create({
|
|
|
358
332
|
gap: 12,
|
|
359
333
|
},
|
|
360
334
|
});
|
|
361
|
-
|
|
362
|
-
function buildAuthIdentifierInput(
|
|
363
|
-
identifier: string,
|
|
364
|
-
identifiers: string[],
|
|
365
|
-
): AuthIdentifier | null {
|
|
366
|
-
const normalizedIdentifier = identifier.trim();
|
|
367
|
-
const resolvedIdentifiers = resolveAuthIdentifiers(identifiers);
|
|
368
|
-
|
|
369
|
-
if (resolvedIdentifiers.includes('email') && isEmail(normalizedIdentifier)) {
|
|
370
|
-
return { kind: 'email' as const, value: normalizedIdentifier };
|
|
371
|
-
}
|
|
372
|
-
if (resolvedIdentifiers.includes('phone') && isPhone(normalizedIdentifier)) {
|
|
373
|
-
return { kind: 'phone' as const, value: normalizedIdentifier };
|
|
374
|
-
}
|
|
375
|
-
if (resolvedIdentifiers.includes('username') && isUsername(normalizedIdentifier)) {
|
|
376
|
-
return { kind: 'username' as const, value: normalizedIdentifier };
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const [fallbackKind] = resolvedIdentifiers;
|
|
380
|
-
return fallbackKind ? { kind: fallbackKind, value: normalizedIdentifier } : null;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function buildSignUpProfile(args: { firstName: string; lastName: string; displayName: string }) {
|
|
384
|
-
const { firstName, lastName, displayName } = args;
|
|
385
|
-
const profile: Record<string, string> = {};
|
|
386
|
-
if (firstName.trim()) profile.firstName = firstName.trim();
|
|
387
|
-
if (lastName.trim()) profile.lastName = lastName.trim();
|
|
388
|
-
if (displayName.trim()) profile.displayName = displayName.trim();
|
|
389
|
-
return Object.keys(profile).length > 0 ? profile : undefined;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function isAuthSession(value: unknown): value is AuthSession {
|
|
393
|
-
if (!isRecord(value)) return false;
|
|
394
|
-
const { accessToken, user } = value;
|
|
395
|
-
if (typeof accessToken !== 'string' || accessToken.length === 0) return false;
|
|
396
|
-
if (!isRecord(user)) return false;
|
|
397
|
-
const { id: userId } = user;
|
|
398
|
-
return typeof userId === 'string' && userId.length > 0;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
402
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function getFormValue(values: SignUpFormValues, name: string): string {
|
|
406
|
-
return values[name] ?? '';
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function buildSignUpFields(args: {
|
|
410
|
-
identifierField: ReturnType<typeof resolveIdentifierFieldDefinition>;
|
|
411
|
-
configuredFields: string[];
|
|
412
|
-
requiredFields: string[];
|
|
413
|
-
}): SignUpFormField[] {
|
|
414
|
-
const { identifierField, configuredFields, requiredFields } = args;
|
|
415
|
-
const fields: SignUpFormField[] = [
|
|
416
|
-
{
|
|
417
|
-
name: 'identifier',
|
|
418
|
-
label: identifierField.label,
|
|
419
|
-
helperText: identifierField.helper,
|
|
420
|
-
placeholder: identifierField.placeholder,
|
|
421
|
-
type: identifierField.type,
|
|
422
|
-
autoCapitalize: 'none',
|
|
423
|
-
keyboardType: identifierField.keyboardType,
|
|
424
|
-
required:
|
|
425
|
-
hasConfiguredSignUpField(requiredFields, 'email') ||
|
|
426
|
-
hasConfiguredSignUpField(requiredFields, 'phone') ||
|
|
427
|
-
hasConfiguredSignUpField(requiredFields, 'username'),
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
name: 'password',
|
|
431
|
-
label: 'Password',
|
|
432
|
-
type: 'password',
|
|
433
|
-
required: hasConfiguredSignUpField(requiredFields, 'password'),
|
|
434
|
-
},
|
|
435
|
-
];
|
|
436
|
-
|
|
437
|
-
if (hasConfiguredSignUpField(configuredFields, 'firstname')) {
|
|
438
|
-
fields.push({
|
|
439
|
-
name: 'firstName',
|
|
440
|
-
label: 'First name',
|
|
441
|
-
type: 'text',
|
|
442
|
-
required: hasConfiguredSignUpField(requiredFields, 'firstname'),
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
if (hasConfiguredSignUpField(configuredFields, 'lastname')) {
|
|
446
|
-
fields.push({
|
|
447
|
-
name: 'lastName',
|
|
448
|
-
label: 'Last name',
|
|
449
|
-
type: 'text',
|
|
450
|
-
required: hasConfiguredSignUpField(requiredFields, 'lastname'),
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
if (hasConfiguredSignUpField(configuredFields, 'displayname')) {
|
|
454
|
-
fields.push({
|
|
455
|
-
name: 'displayName',
|
|
456
|
-
label: 'Display name',
|
|
457
|
-
type: 'text',
|
|
458
|
-
required: hasConfiguredSignUpField(requiredFields, 'displayname'),
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
return fields;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function hasConfiguredSignUpField(fields: string[], field: string): boolean {
|
|
465
|
-
return fields.some((value) => value.trim().toLowerCase() === field);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function resolveAuthIdentifiers(identifiers: string[]): AuthIdentifierKind[] {
|
|
469
|
-
const resolved: AuthIdentifierKind[] = [];
|
|
470
|
-
for (const identifier of identifiers) {
|
|
471
|
-
const normalized = identifier.trim().toLowerCase();
|
|
472
|
-
if (isAuthIdentifierKind(normalized) && !resolved.includes(normalized)) {
|
|
473
|
-
resolved.push(normalized);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
return resolved.length > 0 ? resolved : ['email'];
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function isAuthIdentifierKind(value: string): value is AuthIdentifierKind {
|
|
480
|
-
return value === 'email' || value === 'phone' || value === 'username';
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function resolveIdentifierFieldDefinition(identifiers: string[]) {
|
|
484
|
-
const set = new Set(identifiers.map((identifier) => identifier.trim().toLowerCase()));
|
|
485
|
-
const supportsEmail = set.has('email');
|
|
486
|
-
const supportsPhone = set.has('phone');
|
|
487
|
-
const supportsUsername = set.has('username');
|
|
488
|
-
|
|
489
|
-
if (supportsEmail && !supportsPhone && !supportsUsername) {
|
|
490
|
-
return {
|
|
491
|
-
label: 'Email',
|
|
492
|
-
placeholder: 'hello@example.com',
|
|
493
|
-
helper: 'Use your email to continue.',
|
|
494
|
-
type: 'email' as const,
|
|
495
|
-
keyboardType: 'email-address' as const,
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
if (supportsPhone && !supportsEmail && !supportsUsername) {
|
|
499
|
-
return {
|
|
500
|
-
label: 'Phone',
|
|
501
|
-
placeholder: '+1 555 123 4567',
|
|
502
|
-
helper: 'Use your phone to continue.',
|
|
503
|
-
type: 'tel' as const,
|
|
504
|
-
keyboardType: 'phone-pad' as const,
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
if (supportsUsername && !supportsEmail && !supportsPhone) {
|
|
508
|
-
return {
|
|
509
|
-
label: 'Username',
|
|
510
|
-
placeholder: 'your-username',
|
|
511
|
-
helper: 'Use your username to continue.',
|
|
512
|
-
type: 'text' as const,
|
|
513
|
-
keyboardType: 'default' as const,
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
return {
|
|
517
|
-
label: supportsUsername ? 'Identifier' : 'Email or phone',
|
|
518
|
-
placeholder: 'Email or phone',
|
|
519
|
-
helper: 'Use your configured identifier to continue.',
|
|
520
|
-
type: 'text' as const,
|
|
521
|
-
keyboardType: 'default' as const,
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function validateIdentifier(identifier: string, identifiers: string[]): string | null {
|
|
526
|
-
const normalizedIdentifier = identifier.trim();
|
|
527
|
-
const set = new Set(identifiers.map((entry) => entry.trim().toLowerCase()));
|
|
528
|
-
const allowed: { label: string; matches: boolean }[] = [];
|
|
529
|
-
if (set.has('email')) allowed.push({ label: 'email address', matches: isEmail(normalizedIdentifier) });
|
|
530
|
-
if (set.has('phone')) allowed.push({ label: 'phone number', matches: isPhone(normalizedIdentifier) });
|
|
531
|
-
if (set.has('username')) allowed.push({ label: 'username', matches: isUsername(normalizedIdentifier) });
|
|
532
|
-
if (allowed.length === 0 || allowed.some((entry) => entry.matches)) return null;
|
|
533
|
-
if (allowed.length === 1 && allowed[0]?.label === 'username') {
|
|
534
|
-
return 'Username must be at least 3 characters and use letters, numbers, dot, underscore, or dash.';
|
|
535
|
-
}
|
|
536
|
-
if (allowed.length === 1) return 'Use a valid ' + (allowed[0]?.label ?? 'identifier') + '.';
|
|
537
|
-
if (allowed.length === 2) {
|
|
538
|
-
return (
|
|
539
|
-
'Use a valid ' +
|
|
540
|
-
(allowed[0]?.label ?? 'identifier') +
|
|
541
|
-
' or ' +
|
|
542
|
-
(allowed[1]?.label ?? 'identifier') +
|
|
543
|
-
'.'
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
return (
|
|
547
|
-
'Use a valid ' +
|
|
548
|
-
(allowed[0]?.label ?? 'identifier') +
|
|
549
|
-
', ' +
|
|
550
|
-
(allowed[1]?.label ?? 'identifier') +
|
|
551
|
-
', or ' +
|
|
552
|
-
(allowed[2]?.label ?? 'identifier') +
|
|
553
|
-
'.'
|
|
554
|
-
);
|
|
335
|
+
`;
|
|
555
336
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
case 'phone':
|
|
573
|
-
if (!identifier.trim()) missing.push(fieldLabel(normalized));
|
|
574
|
-
break;
|
|
575
|
-
case 'password':
|
|
576
|
-
if (password.length === 0) missing.push('password');
|
|
577
|
-
break;
|
|
578
|
-
case 'firstname':
|
|
579
|
-
if (!firstName.trim()) missing.push('first name');
|
|
580
|
-
break;
|
|
581
|
-
case 'lastname':
|
|
582
|
-
if (!lastName.trim()) missing.push('last name');
|
|
583
|
-
break;
|
|
584
|
-
case 'displayname':
|
|
585
|
-
if (!displayName.trim()) missing.push('display name');
|
|
586
|
-
break;
|
|
587
|
-
default:
|
|
588
|
-
break;
|
|
337
|
+
function getOAuthControllerSource() {
|
|
338
|
+
return `
|
|
339
|
+
async function handleOAuthProviderPress(providerId: string) {
|
|
340
|
+
if (loading || oauthLoadingProvider !== null) return;
|
|
341
|
+
setError(null);
|
|
342
|
+
setInfo(null);
|
|
343
|
+
setOAuthLoadingProvider(providerId);
|
|
344
|
+
try {
|
|
345
|
+
const outcome = await startOAuthAuthorization(providerId);
|
|
346
|
+
if (outcome.status === 'authenticated') router.replace(POST_SIGN_IN_ROUTE);
|
|
347
|
+
else if (outcome.status === 'cancelled') setInfo(outcome.message);
|
|
348
|
+
else setError(outcome.message);
|
|
349
|
+
} catch (caught) {
|
|
350
|
+
setError(getErrorMessage(caught));
|
|
351
|
+
} finally {
|
|
352
|
+
setOAuthLoadingProvider(null);
|
|
589
353
|
}
|
|
590
|
-
}
|
|
591
|
-
return missing.length > 0 ? 'Complete required fields: ' + missing.join(', ') + '.' : null;
|
|
354
|
+
}`;
|
|
592
355
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
356
|
+
function getOAuthViewSource() {
|
|
357
|
+
return `
|
|
358
|
+
<OAuthProviderList
|
|
359
|
+
disabled={controller.loading}
|
|
360
|
+
onProviderPress={controller.handleOAuthProviderPress}
|
|
361
|
+
providers={generatedOAuthProviderItems.map((provider) => ({
|
|
362
|
+
...provider,
|
|
363
|
+
disabled:
|
|
364
|
+
controller.oauthLoadingProvider !== null &&
|
|
365
|
+
controller.oauthLoadingProvider !== provider.id,
|
|
366
|
+
loading: controller.oauthLoadingProvider === provider.id,
|
|
367
|
+
}))}
|
|
368
|
+
/>
|
|
369
|
+
<View style={styles.separatorRow}>
|
|
370
|
+
<View style={[styles.separatorLine, { backgroundColor: borderColor }]} />
|
|
371
|
+
<Text emphasis="muted" variant="bodySmall">
|
|
372
|
+
or continue with password
|
|
373
|
+
</Text>
|
|
374
|
+
<View style={[styles.separatorLine, { backgroundColor: borderColor }]} />
|
|
375
|
+
</View>`;
|
|
611
376
|
}
|
|
612
377
|
//# sourceMappingURL=screen.js.map
|