@chem-po/firebase-native 0.0.38 → 0.0.40
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/adapter/auth.js +10 -3
- package/lib/commonjs/adapter/auth.js.map +1 -1
- package/lib/commonjs/components/FirebaseSignIn.js +20 -14
- package/lib/commonjs/components/FirebaseSignIn.js.map +1 -1
- package/lib/commonjs/components/PhoneVerify.js +45 -33
- package/lib/commonjs/components/PhoneVerify.js.map +1 -1
- package/lib/commonjs/components/TwoFactorAuthModal.js +72 -15
- package/lib/commonjs/components/TwoFactorAuthModal.js.map +1 -1
- package/lib/commonjs/hooks/usePhoneVerify.js +13 -5
- package/lib/commonjs/hooks/usePhoneVerify.js.map +1 -1
- package/lib/commonjs/utils/validation.js +4 -0
- package/lib/commonjs/utils/validation.js.map +1 -1
- package/lib/module/adapter/auth.js +10 -3
- package/lib/module/adapter/auth.js.map +1 -1
- package/lib/module/components/FirebaseSignIn.js +21 -15
- package/lib/module/components/FirebaseSignIn.js.map +1 -1
- package/lib/module/components/PhoneVerify.js +47 -35
- package/lib/module/components/PhoneVerify.js.map +1 -1
- package/lib/module/components/TwoFactorAuthModal.js +71 -15
- package/lib/module/components/TwoFactorAuthModal.js.map +1 -1
- package/lib/module/hooks/usePhoneVerify.js +13 -5
- package/lib/module/hooks/usePhoneVerify.js.map +1 -1
- package/lib/module/utils/validation.js +4 -0
- package/lib/module/utils/validation.js.map +1 -1
- package/lib/typescript/adapter/auth.d.ts.map +1 -1
- package/lib/typescript/components/FirebaseSignIn.d.ts.map +1 -1
- package/lib/typescript/components/PhoneVerify.d.ts.map +1 -1
- package/lib/typescript/components/TwoFactorAuthModal.d.ts +1 -0
- package/lib/typescript/components/TwoFactorAuthModal.d.ts.map +1 -1
- package/lib/typescript/hooks/usePhoneVerify.d.ts +3 -1
- package/lib/typescript/hooks/usePhoneVerify.d.ts.map +1 -1
- package/lib/typescript/utils/validation.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/adapter/auth.ts +98 -83
- package/src/components/FirebaseSignIn.tsx +21 -14
- package/src/components/PhoneVerify.tsx +46 -33
- package/src/components/TwoFactorAuthModal.tsx +87 -23
- package/src/hooks/usePhoneVerify.ts +15 -4
- package/src/utils/validation.ts +7 -0
package/src/adapter/auth.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
EnrollmentFactorsResult,
|
|
8
8
|
LoginResult,
|
|
9
9
|
MultiFactorVerification,
|
|
10
|
-
WithMultiFactorVerified
|
|
10
|
+
WithMultiFactorVerified,
|
|
11
11
|
} from '@chem-po/core'
|
|
12
12
|
import {
|
|
13
13
|
AppleAuthProvider,
|
|
@@ -27,12 +27,11 @@ import {
|
|
|
27
27
|
|
|
28
28
|
import { appleAuth } from '@invertase/react-native-apple-authentication'
|
|
29
29
|
import { GoogleSignin } from '@react-native-google-signin/google-signin'
|
|
30
|
+
import { Platform } from 'react-native'
|
|
30
31
|
import { Auth, User } from '../types/auth'
|
|
31
32
|
|
|
32
|
-
const isDebug =
|
|
33
|
-
typeof process !== 'undefined' &&
|
|
34
|
-
process.env?.EXPO_PUBLIC_DEBUG === 'true'
|
|
35
|
-
) || false
|
|
33
|
+
const isDebug =
|
|
34
|
+
(typeof process !== 'undefined' && process.env?.EXPO_PUBLIC_DEBUG === 'true') || false
|
|
36
35
|
|
|
37
36
|
const debugLog = (message: string, data?: any) => {
|
|
38
37
|
if (isDebug) {
|
|
@@ -46,24 +45,24 @@ const getUserWithRole = async <UserData extends BaseUserData>(
|
|
|
46
45
|
user: FirebaseAuthTypes.User
|
|
47
46
|
): Promise<WithMultiFactorVerified<UserData>> => {
|
|
48
47
|
debugLog('Getting user with role', { uid: user.uid, email: user.email })
|
|
49
|
-
|
|
48
|
+
|
|
50
49
|
try {
|
|
51
50
|
const { claims } = await getIdTokenResult(user)
|
|
52
51
|
debugLog('Retrieved ID token claims', { role: claims.role, customClaims: Object.keys(claims) })
|
|
53
|
-
|
|
52
|
+
|
|
54
53
|
const userWithRole = {
|
|
55
54
|
...(user as any),
|
|
56
55
|
role: claims.role ?? ('user' as BaseUserRole),
|
|
57
|
-
multiFactorVerified: !!user.multiFactor?.enrolledFactors.length
|
|
56
|
+
multiFactorVerified: !!user.multiFactor?.enrolledFactors.length,
|
|
58
57
|
} as WithMultiFactorVerified<UserData>
|
|
59
|
-
|
|
60
|
-
debugLog('User with role created', {
|
|
61
|
-
uid: userWithRole.uid,
|
|
62
|
-
role: userWithRole.role,
|
|
58
|
+
|
|
59
|
+
debugLog('User with role created', {
|
|
60
|
+
uid: userWithRole.uid,
|
|
61
|
+
role: userWithRole.role,
|
|
63
62
|
multiFactorVerified: userWithRole.multiFactorVerified,
|
|
64
|
-
enrolledFactorsCount: user.multiFactor?.enrolledFactors.length || 0
|
|
63
|
+
enrolledFactorsCount: user.multiFactor?.enrolledFactors.length || 0,
|
|
65
64
|
})
|
|
66
|
-
|
|
65
|
+
|
|
67
66
|
return userWithRole
|
|
68
67
|
} catch (error) {
|
|
69
68
|
debugLog('Error getting user with role', error)
|
|
@@ -121,7 +120,7 @@ const sendMultiFactorCode = async (
|
|
|
121
120
|
resolver: FirebaseAuthTypes.MultiFactorResolver
|
|
122
121
|
): Promise<MultiFactorVerification> => {
|
|
123
122
|
debugLog('Sending multi-factor code', { factorType: factor.type, factorUid: factor.uid })
|
|
124
|
-
|
|
123
|
+
|
|
125
124
|
const sessionId = resolver.session
|
|
126
125
|
// const verificationId = await new PhoneAuthProvider(auth).verifyPhoneNumber(phoneSignInFactor, sessionId)
|
|
127
126
|
if (factor.type === 'phone') {
|
|
@@ -130,7 +129,9 @@ const sendMultiFactorCode = async (
|
|
|
130
129
|
toFirebaseFactor(factor),
|
|
131
130
|
sessionId
|
|
132
131
|
)
|
|
133
|
-
debugLog('Multi-factor code sent successfully', {
|
|
132
|
+
debugLog('Multi-factor code sent successfully', {
|
|
133
|
+
verificationId: verificationId.substring(0, 10) + '...',
|
|
134
|
+
})
|
|
134
135
|
return {
|
|
135
136
|
verificationId,
|
|
136
137
|
factor,
|
|
@@ -146,19 +147,23 @@ const sendMultiFactorCode = async (
|
|
|
146
147
|
|
|
147
148
|
const getEnrolledFactors = async (auth: Auth, error: any): Promise<EnrollmentFactorsResult> => {
|
|
148
149
|
debugLog('Getting enrolled factors from error', { errorCode: error?.code })
|
|
149
|
-
|
|
150
|
+
|
|
150
151
|
try {
|
|
151
152
|
const resolver = getMultiFactorResolver(auth, error)
|
|
152
153
|
debugLog('Multi-factor resolver created', { hintsCount: resolver.hints.length })
|
|
153
|
-
|
|
154
|
+
|
|
154
155
|
if (resolver.hints.length === 0) {
|
|
155
156
|
debugLog('No enrolled factors found')
|
|
156
|
-
throw new Error(
|
|
157
|
+
throw new Error(
|
|
158
|
+
'No multi-factor verification methods found, please enroll one on the website'
|
|
159
|
+
)
|
|
157
160
|
}
|
|
158
|
-
|
|
161
|
+
|
|
159
162
|
const enrollmentFactors = resolver.hints.map(toEnrollmentFactor)
|
|
160
|
-
debugLog('Enrollment factors mapped', {
|
|
161
|
-
|
|
163
|
+
debugLog('Enrollment factors mapped', {
|
|
164
|
+
factors: enrollmentFactors.map((f) => ({ type: f.type, uid: f.uid })),
|
|
165
|
+
})
|
|
166
|
+
|
|
162
167
|
return {
|
|
163
168
|
enrollmentFactors,
|
|
164
169
|
multiFactorResolver: resolver,
|
|
@@ -173,36 +178,36 @@ const verifyMultiFactor = async (
|
|
|
173
178
|
verification: MultiFactorVerification,
|
|
174
179
|
code: string
|
|
175
180
|
): Promise<LoginResult<User>> => {
|
|
176
|
-
debugLog('Verifying multi-factor code', {
|
|
177
|
-
factorType: verification.factor.type,
|
|
178
|
-
codeLength: code.length
|
|
181
|
+
debugLog('Verifying multi-factor code', {
|
|
182
|
+
factorType: verification.factor.type,
|
|
183
|
+
codeLength: code.length,
|
|
179
184
|
})
|
|
180
|
-
|
|
185
|
+
|
|
181
186
|
try {
|
|
182
187
|
const credential = await PhoneAuthProvider.credential(verification.verificationId, code)
|
|
183
188
|
debugLog('Phone credential created')
|
|
184
|
-
|
|
189
|
+
|
|
185
190
|
const assertion = PhoneMultiFactorGenerator.assertion(credential)
|
|
186
191
|
debugLog('Multi-factor assertion created')
|
|
187
|
-
|
|
192
|
+
|
|
188
193
|
const resolver = (verification as any).resolver
|
|
189
194
|
if (!resolver) {
|
|
190
195
|
debugLog('Multi-factor resolver not found')
|
|
191
196
|
throw new Error('Internal error signing in with two factor: resolver not found')
|
|
192
197
|
}
|
|
193
|
-
|
|
198
|
+
|
|
194
199
|
const userCredential = await resolver.resolveSignIn(assertion)
|
|
195
200
|
debugLog('Multi-factor sign-in resolved', { uid: userCredential.user?.uid })
|
|
196
|
-
|
|
201
|
+
|
|
197
202
|
const user = userCredential.user
|
|
198
203
|
if (!user) {
|
|
199
204
|
debugLog('No user found after multi-factor resolution')
|
|
200
205
|
throw new Error('No user found')
|
|
201
206
|
}
|
|
202
|
-
|
|
207
|
+
|
|
203
208
|
const userWithRole = await getUserWithRole(user)
|
|
204
209
|
debugLog('Multi-factor verification completed successfully')
|
|
205
|
-
|
|
210
|
+
|
|
206
211
|
return { user: userWithRole }
|
|
207
212
|
} catch (error) {
|
|
208
213
|
debugLog('Error verifying multi-factor', error)
|
|
@@ -212,43 +217,51 @@ const verifyMultiFactor = async (
|
|
|
212
217
|
|
|
213
218
|
const handleSignInError = async (error: any): Promise<LoginResult<User>> => {
|
|
214
219
|
debugLog('Handling sign-in error', { code: error?.code, message: error?.message })
|
|
215
|
-
|
|
220
|
+
|
|
216
221
|
if (error.code === 'auth/multi-factor-auth-required') {
|
|
217
222
|
debugLog('Multi-factor authentication required')
|
|
218
223
|
return { requestArgs: error }
|
|
219
224
|
}
|
|
220
|
-
|
|
225
|
+
|
|
221
226
|
debugLog('Re-throwing sign-in error')
|
|
222
227
|
throw error
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
const initializeProvider = async (provider: BaseAuthProvider) => {
|
|
226
|
-
debugLog('Initializing provider', {
|
|
227
|
-
|
|
231
|
+
debugLog('Initializing provider', {
|
|
232
|
+
name: provider.name,
|
|
233
|
+
alreadyInitialized: providerInitialized[provider.name],
|
|
234
|
+
})
|
|
235
|
+
|
|
228
236
|
if (providerInitialized[provider.name]) {
|
|
229
237
|
debugLog('Provider already initialized, skipping')
|
|
230
238
|
return
|
|
231
239
|
}
|
|
232
|
-
|
|
240
|
+
|
|
233
241
|
switch (provider.name) {
|
|
234
242
|
case 'google':
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
243
|
+
const { webClientId, iosClientId } = provider as GoogleAuthProvider
|
|
244
|
+
debugLog('Initializing Google provider', {
|
|
245
|
+
webClientId,
|
|
246
|
+
iosClientId,
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
if (!webClientId) {
|
|
238
250
|
debugLog('Google web client ID missing')
|
|
239
251
|
throw new Error(
|
|
240
252
|
'Google web client ID is required when using Google Auth. ' +
|
|
241
|
-
|
|
242
|
-
|
|
253
|
+
'Get your webClientId from Firebase Console > Authentication > Sign-in method > Google > Web SDK configuration. ' +
|
|
254
|
+
'Then provide it in your GoogleAuthProvider: { name: "google", webClientId: "YOUR_CLIENT_ID" }'
|
|
243
255
|
)
|
|
244
256
|
}
|
|
245
|
-
|
|
257
|
+
|
|
246
258
|
try {
|
|
247
259
|
await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true })
|
|
248
260
|
debugLog('Google Play Services available')
|
|
249
|
-
|
|
261
|
+
|
|
250
262
|
GoogleSignin.configure({
|
|
251
|
-
webClientId:
|
|
263
|
+
webClientId: Platform.OS === 'ios' ? iosClientId : webClientId,
|
|
264
|
+
iosClientId,
|
|
252
265
|
})
|
|
253
266
|
debugLog('Google Sign-In configured successfully')
|
|
254
267
|
} catch (error) {
|
|
@@ -256,7 +269,7 @@ const initializeProvider = async (provider: BaseAuthProvider) => {
|
|
|
256
269
|
if (error instanceof Error && error.message.includes('UNAVAILABLE')) {
|
|
257
270
|
throw new Error(
|
|
258
271
|
'Google Play Services is not available or needs to be updated. ' +
|
|
259
|
-
|
|
272
|
+
'Please ensure Google Play Services is installed and up to date on this device.'
|
|
260
273
|
)
|
|
261
274
|
}
|
|
262
275
|
throw error
|
|
@@ -294,28 +307,28 @@ const handleInitialLogin = async (
|
|
|
294
307
|
userCredential: FirebaseAuthTypes.UserCredential,
|
|
295
308
|
twoFactorRequired: boolean
|
|
296
309
|
): Promise<LoginResult<User>> => {
|
|
297
|
-
debugLog('Handling initial login', {
|
|
298
|
-
uid: userCredential.user?.uid,
|
|
310
|
+
debugLog('Handling initial login', {
|
|
311
|
+
uid: userCredential.user?.uid,
|
|
299
312
|
twoFactorRequired,
|
|
300
|
-
enrolledFactorsCount: userCredential.user?.multiFactor?.enrolledFactors.length || 0
|
|
313
|
+
enrolledFactorsCount: userCredential.user?.multiFactor?.enrolledFactors.length || 0,
|
|
301
314
|
})
|
|
302
|
-
|
|
315
|
+
|
|
303
316
|
const user = userCredential.user
|
|
304
317
|
if (!user) {
|
|
305
318
|
debugLog('No user found in credential')
|
|
306
319
|
throw new Error('No user found')
|
|
307
320
|
}
|
|
308
|
-
|
|
321
|
+
|
|
309
322
|
if (twoFactorRequired) {
|
|
310
323
|
debugLog('Two-factor authentication is required but user has no enrolled factors')
|
|
311
324
|
throw new Error(
|
|
312
325
|
'This app requires two factor authentication, please enroll a factor on the website and try again'
|
|
313
326
|
)
|
|
314
327
|
}
|
|
315
|
-
|
|
328
|
+
|
|
316
329
|
const userWithRole = await getUserWithRole(user)
|
|
317
330
|
debugLog('Initial login completed successfully')
|
|
318
|
-
|
|
331
|
+
|
|
319
332
|
return { user: userWithRole }
|
|
320
333
|
}
|
|
321
334
|
|
|
@@ -324,7 +337,7 @@ const loginWithApple = async (
|
|
|
324
337
|
twoFactorRequired: boolean
|
|
325
338
|
): Promise<LoginResult<User>> => {
|
|
326
339
|
debugLog('Starting Apple sign-in', { twoFactorRequired })
|
|
327
|
-
|
|
340
|
+
|
|
328
341
|
try {
|
|
329
342
|
// Start the sign-in request
|
|
330
343
|
const appleAuthRequestResponse = await appleAuth.performRequest({
|
|
@@ -338,9 +351,9 @@ const loginWithApple = async (
|
|
|
338
351
|
throw new Error('Apple Sign-In failed - no identity token returned')
|
|
339
352
|
}
|
|
340
353
|
|
|
341
|
-
debugLog('Apple sign-in response received', {
|
|
354
|
+
debugLog('Apple sign-in response received', {
|
|
342
355
|
hasIdentityToken: appleAuthRequestResponse.identityToken,
|
|
343
|
-
hasNonce: appleAuthRequestResponse.nonce
|
|
356
|
+
hasNonce: appleAuthRequestResponse.nonce,
|
|
344
357
|
})
|
|
345
358
|
|
|
346
359
|
// Create a Firebase credential from the response
|
|
@@ -362,24 +375,24 @@ const loginWithGoogle = async (
|
|
|
362
375
|
twoFactorRequired: boolean
|
|
363
376
|
): Promise<LoginResult<User>> => {
|
|
364
377
|
debugLog('Starting Google sign-in', { twoFactorRequired })
|
|
365
|
-
|
|
378
|
+
|
|
366
379
|
try {
|
|
367
380
|
debugLog('Calling GoogleSignin.signIn()')
|
|
368
381
|
const signInResult = await GoogleSignin.signIn()
|
|
369
382
|
debugLog('Google sign-in result received', { hasIdToken: !!signInResult.data?.idToken })
|
|
370
|
-
|
|
383
|
+
|
|
371
384
|
const idToken = signInResult.data?.idToken
|
|
372
385
|
if (!idToken) {
|
|
373
386
|
debugLog('No ID token found in Google sign-in result')
|
|
374
387
|
throw new Error('No ID token found')
|
|
375
388
|
}
|
|
376
|
-
|
|
389
|
+
|
|
377
390
|
debugLog('Creating Google credential')
|
|
378
391
|
const googleCredential = GoogleAuthProvider.credential(idToken)
|
|
379
|
-
|
|
392
|
+
|
|
380
393
|
debugLog('Signing in with Google credential')
|
|
381
394
|
const userCredential = await signInWithCredential(auth, googleCredential)
|
|
382
|
-
|
|
395
|
+
|
|
383
396
|
return await handleInitialLogin(userCredential, twoFactorRequired)
|
|
384
397
|
} catch (error) {
|
|
385
398
|
debugLog('Error in Google sign-in', error)
|
|
@@ -393,18 +406,18 @@ const getLoginWithPassword =
|
|
|
393
406
|
provider: BaseAuthProvider,
|
|
394
407
|
{ email, password }: { email: string; password: string }
|
|
395
408
|
): Promise<LoginResult<User>> => {
|
|
396
|
-
debugLog('Starting email/password sign-in', {
|
|
397
|
-
provider: provider.name,
|
|
398
|
-
email,
|
|
399
|
-
twoFactorRequired
|
|
409
|
+
debugLog('Starting email/password sign-in', {
|
|
410
|
+
provider: provider.name,
|
|
411
|
+
email,
|
|
412
|
+
twoFactorRequired,
|
|
400
413
|
})
|
|
401
|
-
|
|
414
|
+
|
|
402
415
|
await initializeProvider(provider)
|
|
403
416
|
try {
|
|
404
417
|
debugLog('Calling signInWithEmailAndPassword')
|
|
405
418
|
const userCredential = await signInWithEmailAndPassword(auth, email, password)
|
|
406
419
|
debugLog('Email/password sign-in successful', { uid: userCredential.user?.uid })
|
|
407
|
-
|
|
420
|
+
|
|
408
421
|
return await handleInitialLogin(userCredential, twoFactorRequired)
|
|
409
422
|
} catch (error) {
|
|
410
423
|
debugLog('Error in email/password sign-in', error)
|
|
@@ -416,7 +429,7 @@ const getLoginWithPopup =
|
|
|
416
429
|
(auth: Auth, twoFactorRequired: boolean) =>
|
|
417
430
|
async (provider: BaseAuthProvider): Promise<LoginResult<User>> => {
|
|
418
431
|
debugLog('Starting popup sign-in', { provider: provider.name, twoFactorRequired })
|
|
419
|
-
|
|
432
|
+
|
|
420
433
|
await initializeProvider(provider)
|
|
421
434
|
switch (provider.name) {
|
|
422
435
|
case 'google':
|
|
@@ -433,7 +446,7 @@ const getLoginWithRedirect =
|
|
|
433
446
|
(auth: Auth, twoFactorRequired: boolean) =>
|
|
434
447
|
async (provider: BaseAuthProvider): Promise<LoginResult<User>> => {
|
|
435
448
|
debugLog('Starting redirect sign-in', { provider: provider.name, twoFactorRequired })
|
|
436
|
-
|
|
449
|
+
|
|
437
450
|
// In React Native, redirect is the same as popup since we're using native SDKs
|
|
438
451
|
return getLoginWithPopup(auth, twoFactorRequired)(provider)
|
|
439
452
|
}
|
|
@@ -441,18 +454,18 @@ const getLoginWithRedirect =
|
|
|
441
454
|
const getLoginWithToken =
|
|
442
455
|
(auth: Auth, twoFactorRequired: boolean) =>
|
|
443
456
|
async (provider: BaseAuthProvider, token: string): Promise<LoginResult<User>> => {
|
|
444
|
-
debugLog('Starting token sign-in', {
|
|
445
|
-
provider: provider.name,
|
|
457
|
+
debugLog('Starting token sign-in', {
|
|
458
|
+
provider: provider.name,
|
|
446
459
|
tokenLength: token.length,
|
|
447
|
-
twoFactorRequired
|
|
460
|
+
twoFactorRequired,
|
|
448
461
|
})
|
|
449
|
-
|
|
462
|
+
|
|
450
463
|
await initializeProvider(provider)
|
|
451
464
|
try {
|
|
452
465
|
debugLog('Calling signInWithCustomToken')
|
|
453
466
|
const userCredential = await signInWithCustomToken(auth, token)
|
|
454
467
|
debugLog('Token sign-in successful', { uid: userCredential.user?.uid })
|
|
455
|
-
|
|
468
|
+
|
|
456
469
|
return await handleInitialLogin(userCredential, twoFactorRequired)
|
|
457
470
|
} catch (error) {
|
|
458
471
|
debugLog('Error in token sign-in', error)
|
|
@@ -466,7 +479,7 @@ const resetPassword = async (
|
|
|
466
479
|
usernameOrEmail: string
|
|
467
480
|
): Promise<void> => {
|
|
468
481
|
debugLog('Starting password reset', { provider: provider.name, email: usernameOrEmail })
|
|
469
|
-
|
|
482
|
+
|
|
470
483
|
await initializeProvider(provider)
|
|
471
484
|
try {
|
|
472
485
|
await sendPasswordResetEmail(auth, usernameOrEmail)
|
|
@@ -482,7 +495,7 @@ export const getFirebaseAuthAdapter = (
|
|
|
482
495
|
twoFactorRequired: boolean
|
|
483
496
|
): AuthAdapter<BaseAuthProvider, User, { email: string; password: string }> => {
|
|
484
497
|
debugLog('Creating Firebase Auth Adapter', { twoFactorRequired })
|
|
485
|
-
|
|
498
|
+
|
|
486
499
|
return {
|
|
487
500
|
twoFactorRequired,
|
|
488
501
|
getCurrentUser: async () => {
|
|
@@ -528,13 +541,15 @@ export const getFirebaseAuthAdapter = (
|
|
|
528
541
|
return
|
|
529
542
|
}
|
|
530
543
|
debugLog('User state changed: user signed in', { uid: user.uid, email: user.email })
|
|
531
|
-
getUserWithRole(user)
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
544
|
+
getUserWithRole(user)
|
|
545
|
+
.then((result) => {
|
|
546
|
+
debugLog('User subscription callback completed', { uid: result.uid, role: result.role })
|
|
547
|
+
callback(result)
|
|
548
|
+
})
|
|
549
|
+
.catch((error) => {
|
|
550
|
+
debugLog('Error in user subscription callback', error)
|
|
551
|
+
callback(null)
|
|
552
|
+
})
|
|
538
553
|
})
|
|
539
554
|
},
|
|
540
555
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useBackend } from '../hooks'
|
|
2
2
|
|
|
3
3
|
import { AppleAuthProvider, GoogleAuthProvider, ThirdPartyAuthProvider } from '@chem-po/core'
|
|
4
|
-
import { TextField, useBorderColor, useToast } from '@chem-po/react'
|
|
4
|
+
import { TextField, useBorderColor, useTextColor, useToast } from '@chem-po/react'
|
|
5
5
|
import { LoadingButton, StandaloneInput, Txt } from '@chem-po/react-native'
|
|
6
|
+
import { ButtonText } from '@chem-po/react-native/src/components/button/ButtonText'
|
|
6
7
|
import appleAuth from '@invertase/react-native-apple-authentication'
|
|
7
8
|
import React, { useCallback, useState } from 'react'
|
|
8
9
|
import { StyleSheet, View } from 'react-native'
|
|
@@ -40,14 +41,19 @@ const ThirdPartyLogin = ({ provider }: { provider: GoogleAuthProvider | AppleAut
|
|
|
40
41
|
})
|
|
41
42
|
}, [auth, provider, showError])
|
|
42
43
|
|
|
43
|
-
const
|
|
44
|
-
|
|
44
|
+
const textColor = useTextColor(400)
|
|
45
45
|
return (
|
|
46
46
|
<View style={styles.row}>
|
|
47
|
-
<LoadingButton
|
|
47
|
+
<LoadingButton
|
|
48
|
+
color={textColor}
|
|
49
|
+
variant="outline"
|
|
50
|
+
style={[styles.googleLoginButton]}
|
|
51
|
+
onPress={handleSignIn}>
|
|
48
52
|
<View style={styles.googleLogin}>
|
|
49
53
|
{thirdPartyInfo[provider.name].icon}
|
|
50
|
-
<
|
|
54
|
+
<ButtonText
|
|
55
|
+
variant="outline"
|
|
56
|
+
color={textColor}>{`Log In with ${thirdPartyInfo[provider.name].name}`}</ButtonText>
|
|
51
57
|
</View>
|
|
52
58
|
</LoadingButton>
|
|
53
59
|
</View>
|
|
@@ -57,20 +63,18 @@ const ThirdPartyLogin = ({ provider }: { provider: GoogleAuthProvider | AppleAut
|
|
|
57
63
|
const EmailPasswordLogin = () => {
|
|
58
64
|
const [email, setEmail] = useState('')
|
|
59
65
|
const [password, setPassword] = useState('')
|
|
60
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
61
66
|
const [error, setError] = useState<string | null>(null)
|
|
67
|
+
const forgotPasswordTextColor = useTextColor(400)
|
|
62
68
|
|
|
63
69
|
const { auth } = useBackend()
|
|
64
70
|
const { showInfo } = useToast()
|
|
65
71
|
|
|
66
72
|
const handleLogin = async () => {
|
|
67
|
-
setIsLoading(true)
|
|
68
73
|
try {
|
|
69
74
|
await auth.loginWithPassword({ name: 'email' }, { email, password })
|
|
70
75
|
} catch (error) {
|
|
71
76
|
setError(error instanceof Error ? error.message : 'An unknown error occurred')
|
|
72
77
|
}
|
|
73
|
-
setIsLoading(false)
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
return (
|
|
@@ -85,18 +89,19 @@ const EmailPasswordLogin = () => {
|
|
|
85
89
|
<View style={styles.row}>
|
|
86
90
|
<View style={styles.forgotPassword}>
|
|
87
91
|
<LoadingButton
|
|
92
|
+
color={forgotPasswordTextColor}
|
|
88
93
|
textStyle={styles.forgotPasswordButtonText}
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
style={styles.forgotPasswordButton}
|
|
95
|
+
size="sm"
|
|
96
|
+
variant="outline"
|
|
97
|
+
onPress={async () => {
|
|
91
98
|
// console.log('press')
|
|
92
99
|
showInfo('Visit our website to reset your password')
|
|
93
100
|
}}>
|
|
94
101
|
Forgot Password?
|
|
95
102
|
</LoadingButton>
|
|
96
103
|
</View>
|
|
97
|
-
<LoadingButton onPress={() => handleLogin()}
|
|
98
|
-
Sign In
|
|
99
|
-
</LoadingButton>
|
|
104
|
+
<LoadingButton onPress={() => handleLogin()}>Sign In</LoadingButton>
|
|
100
105
|
</View>
|
|
101
106
|
</View>
|
|
102
107
|
)
|
|
@@ -149,7 +154,7 @@ const styles = StyleSheet.create({
|
|
|
149
154
|
},
|
|
150
155
|
column: {
|
|
151
156
|
flexDirection: 'column',
|
|
152
|
-
gap:
|
|
157
|
+
gap: 10,
|
|
153
158
|
flex: 1,
|
|
154
159
|
width: '100%',
|
|
155
160
|
alignItems: 'center',
|
|
@@ -176,6 +181,7 @@ const styles = StyleSheet.create({
|
|
|
176
181
|
},
|
|
177
182
|
forgotPasswordButton: {
|
|
178
183
|
borderWidth: 0,
|
|
184
|
+
backgroundColor: 'transparent',
|
|
179
185
|
},
|
|
180
186
|
forgotPasswordButtonText: {
|
|
181
187
|
fontSize: 14,
|
|
@@ -183,6 +189,7 @@ const styles = StyleSheet.create({
|
|
|
183
189
|
row: {
|
|
184
190
|
flexDirection: 'row',
|
|
185
191
|
justifyContent: 'space-between',
|
|
192
|
+
alignItems: 'center',
|
|
186
193
|
width: '100%',
|
|
187
194
|
},
|
|
188
195
|
forgotPassword: {
|
|
@@ -1,53 +1,66 @@
|
|
|
1
|
-
import { PhoneEnrollmentFactor } from '@chem-po/core'
|
|
2
|
-
import { useAuth } from '@chem-po/react'
|
|
3
|
-
import { Txt } from '@chem-po/react-native'
|
|
4
|
-
import React from 'react'
|
|
5
|
-
import {
|
|
1
|
+
import { formatPhoneNumber, PhoneEnrollmentFactor } from '@chem-po/core'
|
|
2
|
+
import { useAuth, useBorderColor, useButtonColor, usePlaceholderColor, useTextColor } from '@chem-po/react'
|
|
3
|
+
import { CircularProgress, LoadingButton, Txt } from '@chem-po/react-native'
|
|
4
|
+
import React, { useMemo } from 'react'
|
|
5
|
+
import { StyleSheet, TextInput, View } from 'react-native'
|
|
6
6
|
import { usePhoneVerify } from '../hooks/usePhoneVerify'
|
|
7
7
|
|
|
8
8
|
// UI to send code and verify code
|
|
9
9
|
export const PhoneVerify = ({ factor }: { factor: PhoneEnrollmentFactor }) => {
|
|
10
|
-
const { handleVerify, verifying, error, code, setCode } =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
10
|
+
const { handleVerify, verifying, error, code, setCode, sendCode, sendingCode } = usePhoneVerify(factor, true)
|
|
11
|
+
const { multiFactorVerification: twoFactorVerification, multiFactorLoading: loading } = useAuth()
|
|
12
|
+
const buttonBackgroundColor = useButtonColor()
|
|
13
|
+
const borderColor = useBorderColor()
|
|
14
|
+
const textColor = useTextColor()
|
|
15
|
+
const placeholderColor = usePlaceholderColor()
|
|
14
16
|
|
|
15
17
|
const verificationId = twoFactorVerification?.verificationId
|
|
18
|
+
const formattedPhoneNumber = useMemo(
|
|
19
|
+
() =>
|
|
20
|
+
Number.isNaN(Number(factor.phoneNumber))
|
|
21
|
+
? factor.phoneNumber
|
|
22
|
+
: formatPhoneNumber(factor.phoneNumber),
|
|
23
|
+
[factor.phoneNumber]
|
|
24
|
+
)
|
|
16
25
|
|
|
17
26
|
let body: React.ReactNode = null
|
|
18
27
|
|
|
19
|
-
if (
|
|
28
|
+
if (sendingCode) {
|
|
20
29
|
body = (
|
|
21
30
|
<View style={styles.container}>
|
|
22
|
-
<
|
|
31
|
+
<CircularProgress size='large' />
|
|
23
32
|
<Txt style={styles.text}>Sending verification code...</Txt>
|
|
24
33
|
</View>
|
|
25
34
|
)
|
|
26
|
-
} else if (verificationId) {
|
|
35
|
+
} else if (verificationId || verifying) {
|
|
27
36
|
body = (
|
|
28
37
|
<View style={styles.container}>
|
|
29
38
|
<Txt style={styles.text}>Enter the code sent to your phone:</Txt>
|
|
30
39
|
<TextInput
|
|
31
|
-
style={styles.input}
|
|
40
|
+
style={[styles.input, { borderColor, color: textColor }]}
|
|
32
41
|
value={code}
|
|
42
|
+
placeholderTextColor={placeholderColor}
|
|
33
43
|
onChangeText={setCode}
|
|
34
|
-
placeholder=
|
|
35
|
-
keyboardType=
|
|
44
|
+
placeholder='Verification Code'
|
|
45
|
+
keyboardType='number-pad'
|
|
36
46
|
maxLength={6}
|
|
37
47
|
/>
|
|
38
48
|
{error ? <Txt style={styles.errorText}>{error}</Txt> : null}
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
</
|
|
49
|
+
<LoadingButton variant='solid' onPress={handleVerify} color={buttonBackgroundColor}>
|
|
50
|
+
{verifying ? 'Verifying...' : 'Verify'}
|
|
51
|
+
</LoadingButton>
|
|
42
52
|
</View>
|
|
43
53
|
)
|
|
44
54
|
} else {
|
|
45
55
|
body = (
|
|
46
56
|
<View style={styles.container}>
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
<Txt style={styles.
|
|
50
|
-
</
|
|
57
|
+
<View style={styles.textContainer}>
|
|
58
|
+
<Txt style={styles.infoText}>We'll send a verification code to:</Txt>
|
|
59
|
+
<Txt style={styles.text}>{formattedPhoneNumber}</Txt>
|
|
60
|
+
</View>
|
|
61
|
+
<LoadingButton variant='solid' onPress={sendCode} disabled={loading} color={buttonBackgroundColor}>
|
|
62
|
+
Send Verification Code
|
|
63
|
+
</LoadingButton>
|
|
51
64
|
</View>
|
|
52
65
|
)
|
|
53
66
|
}
|
|
@@ -64,7 +77,17 @@ const styles = StyleSheet.create({
|
|
|
64
77
|
flex: 1,
|
|
65
78
|
alignItems: 'center',
|
|
66
79
|
justifyContent: 'center',
|
|
67
|
-
gap:
|
|
80
|
+
gap: 8,
|
|
81
|
+
},
|
|
82
|
+
textContainer: {
|
|
83
|
+
alignItems: 'center',
|
|
84
|
+
justifyContent: 'center',
|
|
85
|
+
gap: 4,
|
|
86
|
+
},
|
|
87
|
+
infoText: {
|
|
88
|
+
fontSize: 15,
|
|
89
|
+
opacity: 0.8,
|
|
90
|
+
textAlign: 'center',
|
|
68
91
|
},
|
|
69
92
|
text: {
|
|
70
93
|
fontSize: 16,
|
|
@@ -75,22 +98,12 @@ const styles = StyleSheet.create({
|
|
|
75
98
|
width: '100%',
|
|
76
99
|
height: 48,
|
|
77
100
|
borderWidth: 1,
|
|
78
|
-
borderColor: '#ccc',
|
|
79
101
|
borderRadius: 8,
|
|
80
102
|
paddingHorizontal: 16,
|
|
81
103
|
fontSize: 16,
|
|
82
104
|
marginBottom: 8,
|
|
83
105
|
},
|
|
84
|
-
button: {
|
|
85
|
-
backgroundColor: '#007AFF',
|
|
86
|
-
paddingHorizontal: 24,
|
|
87
|
-
paddingVertical: 12,
|
|
88
|
-
borderRadius: 8,
|
|
89
|
-
minWidth: 200,
|
|
90
|
-
alignItems: 'center',
|
|
91
|
-
},
|
|
92
106
|
buttonText: {
|
|
93
|
-
color: 'white',
|
|
94
107
|
fontSize: 16,
|
|
95
108
|
fontWeight: '600',
|
|
96
109
|
},
|