@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.
Files changed (39) hide show
  1. package/lib/commonjs/adapter/auth.js +10 -3
  2. package/lib/commonjs/adapter/auth.js.map +1 -1
  3. package/lib/commonjs/components/FirebaseSignIn.js +20 -14
  4. package/lib/commonjs/components/FirebaseSignIn.js.map +1 -1
  5. package/lib/commonjs/components/PhoneVerify.js +45 -33
  6. package/lib/commonjs/components/PhoneVerify.js.map +1 -1
  7. package/lib/commonjs/components/TwoFactorAuthModal.js +72 -15
  8. package/lib/commonjs/components/TwoFactorAuthModal.js.map +1 -1
  9. package/lib/commonjs/hooks/usePhoneVerify.js +13 -5
  10. package/lib/commonjs/hooks/usePhoneVerify.js.map +1 -1
  11. package/lib/commonjs/utils/validation.js +4 -0
  12. package/lib/commonjs/utils/validation.js.map +1 -1
  13. package/lib/module/adapter/auth.js +10 -3
  14. package/lib/module/adapter/auth.js.map +1 -1
  15. package/lib/module/components/FirebaseSignIn.js +21 -15
  16. package/lib/module/components/FirebaseSignIn.js.map +1 -1
  17. package/lib/module/components/PhoneVerify.js +47 -35
  18. package/lib/module/components/PhoneVerify.js.map +1 -1
  19. package/lib/module/components/TwoFactorAuthModal.js +71 -15
  20. package/lib/module/components/TwoFactorAuthModal.js.map +1 -1
  21. package/lib/module/hooks/usePhoneVerify.js +13 -5
  22. package/lib/module/hooks/usePhoneVerify.js.map +1 -1
  23. package/lib/module/utils/validation.js +4 -0
  24. package/lib/module/utils/validation.js.map +1 -1
  25. package/lib/typescript/adapter/auth.d.ts.map +1 -1
  26. package/lib/typescript/components/FirebaseSignIn.d.ts.map +1 -1
  27. package/lib/typescript/components/PhoneVerify.d.ts.map +1 -1
  28. package/lib/typescript/components/TwoFactorAuthModal.d.ts +1 -0
  29. package/lib/typescript/components/TwoFactorAuthModal.d.ts.map +1 -1
  30. package/lib/typescript/hooks/usePhoneVerify.d.ts +3 -1
  31. package/lib/typescript/hooks/usePhoneVerify.d.ts.map +1 -1
  32. package/lib/typescript/utils/validation.d.ts.map +1 -1
  33. package/package.json +4 -4
  34. package/src/adapter/auth.ts +98 -83
  35. package/src/components/FirebaseSignIn.tsx +21 -14
  36. package/src/components/PhoneVerify.tsx +46 -33
  37. package/src/components/TwoFactorAuthModal.tsx +87 -23
  38. package/src/hooks/usePhoneVerify.ts +15 -4
  39. package/src/utils/validation.ts +7 -0
@@ -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', { verificationId: verificationId.substring(0, 10) + '...' })
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('No multi-factor verification methods found, please enroll one on the website')
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', { factors: enrollmentFactors.map(f => ({ type: f.type, uid: f.uid })) })
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', { name: provider.name, alreadyInitialized: providerInitialized[provider.name] })
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
- debugLog('Initializing Google provider', { webClientId: !!(provider as GoogleAuthProvider).webClientId })
236
-
237
- if (!(provider as GoogleAuthProvider).webClientId) {
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
- 'Get your webClientId from Firebase Console > Authentication > Sign-in method > Google > Web SDK configuration. ' +
242
- 'Then provide it in your GoogleAuthProvider: { name: "google", webClientId: "YOUR_CLIENT_ID" }'
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: (provider as any).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
- 'Please ensure Google Play Services is installed and up to date on this device.'
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).then((result) => {
532
- debugLog('User subscription callback completed', { uid: result.uid, role: result.role })
533
- callback(result)
534
- }).catch((error) => {
535
- debugLog('Error in user subscription callback', error)
536
- callback(null)
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 borderColor = useBorderColor()
44
-
44
+ const textColor = useTextColor(400)
45
45
  return (
46
46
  <View style={styles.row}>
47
- <LoadingButton style={[styles.googleLoginButton, { borderColor }]} onPress={handleSignIn}>
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
- <Txt>Log In with {thirdPartyInfo[provider.name].name}</Txt>
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
- contentStyle={styles.forgotPasswordButton}
90
- onPress={() => {
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()} disabled={isLoading}>
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: 20,
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 { ActivityIndicator, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native'
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
- usePhoneVerify(factor, true)
12
-
13
- const {multiFactorVerification: twoFactorVerification, multiFactorLoading: loading} = useAuth()
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 (loading) {
28
+ if (sendingCode) {
20
29
  body = (
21
30
  <View style={styles.container}>
22
- <ActivityIndicator size="large" color="#0000ff" />
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="Verification Code"
35
- keyboardType="number-pad"
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
- <TouchableOpacity style={styles.button} onPress={handleVerify} disabled={verifying}>
40
- <Txt style={styles.buttonText}>{verifying ? 'Verifying...' : 'Verify'}</Txt>
41
- </TouchableOpacity>
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
- <Txt style={styles.text}>We'll send a verification code to {factor.phoneNumber}</Txt>
48
- <TouchableOpacity style={styles.button} onPress={() => {}} disabled={loading}>
49
- <Txt style={styles.buttonText}>Send Verification Code</Txt>
50
- </TouchableOpacity>
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: 16,
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
  },