@chem-po/firebase-native 0.0.16 → 0.0.17

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 (204) hide show
  1. package/lib/commonjs/adapter/auth.js +221 -0
  2. package/lib/commonjs/adapter/auth.js.map +1 -0
  3. package/lib/commonjs/adapter/db.js +103 -0
  4. package/lib/commonjs/adapter/db.js.map +1 -0
  5. package/lib/commonjs/adapter/index.js +16 -0
  6. package/lib/commonjs/adapter/index.js.map +1 -0
  7. package/lib/commonjs/adapter/storage.js +52 -0
  8. package/lib/commonjs/adapter/storage.js.map +1 -0
  9. package/lib/commonjs/auth/functions.js +11 -0
  10. package/lib/commonjs/auth/functions.js.map +1 -0
  11. package/lib/commonjs/auth/index.js +17 -0
  12. package/lib/commonjs/auth/index.js.map +1 -0
  13. package/lib/commonjs/components/AuthenticatorVerify.js +90 -0
  14. package/lib/commonjs/components/AuthenticatorVerify.js.map +1 -0
  15. package/lib/commonjs/components/FirebaseSignIn.js +196 -0
  16. package/lib/commonjs/components/FirebaseSignIn.js.map +1 -0
  17. package/lib/commonjs/components/PhoneVerify.js +123 -0
  18. package/lib/commonjs/components/PhoneVerify.js.map +1 -0
  19. package/lib/commonjs/components/TwoFactorAuthModal.js +118 -0
  20. package/lib/commonjs/components/TwoFactorAuthModal.js.map +1 -0
  21. package/lib/commonjs/components/index.js +28 -0
  22. package/lib/commonjs/components/index.js.map +1 -0
  23. package/lib/commonjs/contexts/FirebaseContext.js +48 -0
  24. package/lib/commonjs/contexts/FirebaseContext.js.map +1 -0
  25. package/lib/commonjs/contexts/index.js +17 -0
  26. package/lib/commonjs/contexts/index.js.map +1 -0
  27. package/lib/commonjs/db/index.js +17 -0
  28. package/lib/commonjs/db/index.js.map +1 -0
  29. package/lib/commonjs/db/utils.js +120 -0
  30. package/lib/commonjs/db/utils.js.map +1 -0
  31. package/lib/commonjs/hooks/backend.js +12 -0
  32. package/lib/commonjs/hooks/backend.js.map +1 -0
  33. package/lib/commonjs/hooks/index.js +17 -0
  34. package/lib/commonjs/hooks/index.js.map +1 -0
  35. package/lib/commonjs/hooks/useAuthenticatorVerify.js +52 -0
  36. package/lib/commonjs/hooks/useAuthenticatorVerify.js.map +1 -0
  37. package/lib/commonjs/hooks/usePhoneVerify.js +83 -0
  38. package/lib/commonjs/hooks/usePhoneVerify.js.map +1 -0
  39. package/lib/commonjs/icons/Google.js +29 -0
  40. package/lib/commonjs/icons/Google.js.map +1 -0
  41. package/lib/commonjs/index.js +94 -0
  42. package/lib/commonjs/index.js.map +1 -0
  43. package/lib/commonjs/storage/index.js +17 -0
  44. package/lib/commonjs/storage/index.js.map +1 -0
  45. package/lib/commonjs/storage/utils.js +37 -0
  46. package/lib/commonjs/storage/utils.js.map +1 -0
  47. package/lib/commonjs/types/adapter.js +6 -0
  48. package/lib/commonjs/types/adapter.js.map +1 -0
  49. package/lib/commonjs/types/auth.js +6 -0
  50. package/lib/commonjs/types/auth.js.map +1 -0
  51. package/lib/commonjs/types/db.js +6 -0
  52. package/lib/commonjs/types/db.js.map +1 -0
  53. package/lib/commonjs/types/functions.js +6 -0
  54. package/lib/commonjs/types/functions.js.map +1 -0
  55. package/lib/commonjs/types/index.js +6 -0
  56. package/lib/commonjs/types/index.js.map +1 -0
  57. package/lib/commonjs/types/storage.js +6 -0
  58. package/lib/commonjs/types/storage.js.map +1 -0
  59. package/lib/module/adapter/auth.js +214 -0
  60. package/lib/module/adapter/auth.js.map +1 -0
  61. package/lib/module/adapter/db.js +96 -0
  62. package/lib/module/adapter/db.js.map +1 -0
  63. package/lib/module/adapter/index.js +9 -0
  64. package/lib/module/adapter/index.js.map +1 -0
  65. package/lib/module/adapter/storage.js +45 -0
  66. package/lib/module/adapter/storage.js.map +1 -0
  67. package/lib/module/auth/functions.js +3 -0
  68. package/lib/module/auth/functions.js.map +1 -0
  69. package/lib/module/auth/index.js +2 -0
  70. package/lib/module/auth/index.js.map +1 -0
  71. package/lib/module/components/AuthenticatorVerify.js +82 -0
  72. package/lib/module/components/AuthenticatorVerify.js.map +1 -0
  73. package/lib/module/components/FirebaseSignIn.js +187 -0
  74. package/lib/module/components/FirebaseSignIn.js.map +1 -0
  75. package/lib/module/components/PhoneVerify.js +116 -0
  76. package/lib/module/components/PhoneVerify.js.map +1 -0
  77. package/lib/module/components/TwoFactorAuthModal.js +110 -0
  78. package/lib/module/components/TwoFactorAuthModal.js.map +1 -0
  79. package/lib/module/components/index.js +3 -0
  80. package/lib/module/components/index.js.map +1 -0
  81. package/lib/module/contexts/FirebaseContext.js +39 -0
  82. package/lib/module/contexts/FirebaseContext.js.map +1 -0
  83. package/lib/module/contexts/index.js +2 -0
  84. package/lib/module/contexts/index.js.map +1 -0
  85. package/lib/module/db/index.js +2 -0
  86. package/lib/module/db/index.js.map +1 -0
  87. package/lib/module/db/utils.js +111 -0
  88. package/lib/module/db/utils.js.map +1 -0
  89. package/lib/module/hooks/backend.js +5 -0
  90. package/lib/module/hooks/backend.js.map +1 -0
  91. package/lib/module/hooks/index.js +2 -0
  92. package/lib/module/hooks/index.js.map +1 -0
  93. package/lib/module/hooks/useAuthenticatorVerify.js +45 -0
  94. package/lib/module/hooks/useAuthenticatorVerify.js.map +1 -0
  95. package/lib/module/hooks/usePhoneVerify.js +76 -0
  96. package/lib/module/hooks/usePhoneVerify.js.map +1 -0
  97. package/lib/module/icons/Google.js +22 -0
  98. package/lib/module/icons/Google.js.map +1 -0
  99. package/lib/module/index.js +9 -0
  100. package/lib/module/index.js.map +1 -0
  101. package/lib/module/storage/index.js +2 -0
  102. package/lib/module/storage/index.js.map +1 -0
  103. package/lib/module/storage/utils.js +30 -0
  104. package/lib/module/storage/utils.js.map +1 -0
  105. package/lib/module/types/adapter.js +2 -0
  106. package/lib/module/types/adapter.js.map +1 -0
  107. package/lib/module/types/auth.js +2 -0
  108. package/lib/module/types/auth.js.map +1 -0
  109. package/lib/module/types/db.js +2 -0
  110. package/lib/module/types/db.js.map +1 -0
  111. package/lib/module/types/functions.js +2 -0
  112. package/lib/module/types/functions.js.map +1 -0
  113. package/lib/module/types/index.js +2 -0
  114. package/lib/module/types/index.js.map +1 -0
  115. package/lib/module/types/storage.js +2 -0
  116. package/lib/module/types/storage.js.map +1 -0
  117. package/lib/typescript/adapter/auth.d.ts +7 -0
  118. package/lib/typescript/adapter/auth.d.ts.map +1 -0
  119. package/lib/typescript/adapter/db.d.ts +5 -0
  120. package/lib/typescript/adapter/db.d.ts.map +1 -0
  121. package/lib/typescript/adapter/index.d.ts +9 -0
  122. package/lib/typescript/adapter/index.d.ts.map +1 -0
  123. package/lib/typescript/adapter/storage.d.ts +4 -0
  124. package/lib/typescript/adapter/storage.d.ts.map +1 -0
  125. package/lib/typescript/auth/functions.d.ts +4 -0
  126. package/lib/typescript/auth/functions.d.ts.map +1 -0
  127. package/lib/typescript/auth/index.d.ts +2 -0
  128. package/lib/typescript/auth/index.d.ts.map +1 -0
  129. package/lib/typescript/components/AuthenticatorVerify.d.ts +3 -0
  130. package/lib/typescript/components/AuthenticatorVerify.d.ts.map +1 -0
  131. package/lib/typescript/components/FirebaseSignIn.d.ts +6 -0
  132. package/lib/typescript/components/FirebaseSignIn.d.ts.map +1 -0
  133. package/lib/typescript/components/PhoneVerify.d.ts +6 -0
  134. package/lib/typescript/components/PhoneVerify.d.ts.map +1 -0
  135. package/lib/typescript/components/TwoFactorAuthModal.d.ts +3 -0
  136. package/lib/typescript/components/TwoFactorAuthModal.d.ts.map +1 -0
  137. package/lib/typescript/components/index.d.ts +3 -0
  138. package/lib/typescript/components/index.d.ts.map +1 -0
  139. package/lib/typescript/contexts/FirebaseContext.d.ts +9 -0
  140. package/lib/typescript/contexts/FirebaseContext.d.ts.map +1 -0
  141. package/lib/typescript/contexts/index.d.ts +2 -0
  142. package/lib/typescript/contexts/index.d.ts.map +1 -0
  143. package/lib/typescript/db/index.d.ts +2 -0
  144. package/lib/typescript/db/index.d.ts.map +1 -0
  145. package/lib/typescript/db/utils.d.ts +6 -0
  146. package/lib/typescript/db/utils.d.ts.map +1 -0
  147. package/lib/typescript/hooks/backend.d.ts +2 -0
  148. package/lib/typescript/hooks/backend.d.ts.map +1 -0
  149. package/lib/typescript/hooks/index.d.ts +2 -0
  150. package/lib/typescript/hooks/index.d.ts.map +1 -0
  151. package/lib/typescript/hooks/useAuthenticatorVerify.d.ts +8 -0
  152. package/lib/typescript/hooks/useAuthenticatorVerify.d.ts.map +1 -0
  153. package/lib/typescript/hooks/usePhoneVerify.d.ts +9 -0
  154. package/lib/typescript/hooks/usePhoneVerify.d.ts.map +1 -0
  155. package/lib/typescript/icons/Google.d.ts +5 -0
  156. package/lib/typescript/icons/Google.d.ts.map +1 -0
  157. package/lib/typescript/index.d.ts +9 -0
  158. package/lib/typescript/index.d.ts.map +1 -0
  159. package/lib/typescript/storage/index.d.ts +2 -0
  160. package/lib/typescript/storage/index.d.ts.map +1 -0
  161. package/lib/typescript/storage/utils.d.ts +4 -0
  162. package/lib/typescript/storage/utils.d.ts.map +1 -0
  163. package/lib/typescript/types/adapter.d.ts +6 -0
  164. package/lib/typescript/types/adapter.d.ts.map +1 -0
  165. package/lib/typescript/types/auth.d.ts +12 -0
  166. package/lib/typescript/types/auth.d.ts.map +1 -0
  167. package/lib/typescript/types/db.d.ts +8 -0
  168. package/lib/typescript/types/db.d.ts.map +1 -0
  169. package/lib/typescript/types/functions.d.ts +3 -0
  170. package/lib/typescript/types/functions.d.ts.map +1 -0
  171. package/lib/typescript/types/index.d.ts +24 -0
  172. package/lib/typescript/types/index.d.ts.map +1 -0
  173. package/lib/typescript/types/storage.d.ts +3 -0
  174. package/lib/typescript/types/storage.d.ts.map +1 -0
  175. package/package.json +29 -12
  176. package/src/adapter/auth.ts +281 -0
  177. package/src/adapter/db.ts +146 -0
  178. package/src/adapter/index.ts +30 -0
  179. package/src/adapter/storage.ts +58 -0
  180. package/src/auth/functions.ts +7 -0
  181. package/src/auth/index.ts +1 -0
  182. package/src/components/AuthenticatorVerify.tsx +75 -0
  183. package/src/components/FirebaseSignIn.tsx +187 -0
  184. package/src/components/PhoneVerify.tsx +102 -0
  185. package/src/components/TwoFactorAuthModal.tsx +133 -0
  186. package/src/components/index.ts +2 -0
  187. package/src/contexts/FirebaseContext.tsx +54 -0
  188. package/src/contexts/index.ts +1 -0
  189. package/src/db/index.ts +1 -0
  190. package/src/db/utils.ts +142 -0
  191. package/src/hooks/backend.ts +4 -0
  192. package/src/hooks/index.ts +1 -0
  193. package/src/hooks/useAuthenticatorVerify.ts +45 -0
  194. package/src/hooks/usePhoneVerify.ts +76 -0
  195. package/src/icons/Google.tsx +24 -0
  196. package/src/index.ts +8 -0
  197. package/src/storage/index.ts +1 -0
  198. package/src/storage/utils.ts +29 -0
  199. package/src/types/adapter.ts +13 -0
  200. package/src/types/auth.ts +13 -0
  201. package/src/types/db.ts +10 -0
  202. package/src/types/functions.ts +3 -0
  203. package/src/types/index.ts +26 -0
  204. package/src/types/storage.ts +3 -0
@@ -0,0 +1,187 @@
1
+ import { useBackend } from '../hooks'
2
+
3
+ import { GoogleAuthProvider } from '@chem-po/core'
4
+ import { TextField, useBorderColor, useToast } from '@chem-po/react'
5
+ import { LoadingButton, StandaloneInput, Txt } from '@chem-po/react-native'
6
+ import React, { useCallback, useState } from 'react'
7
+ import { StyleSheet, View } from 'react-native'
8
+ import { Divider } from 'react-native-paper'
9
+ import SvgGoogle from '../icons/Google'
10
+
11
+ const emailField: TextField = {
12
+ _type: 'text',
13
+ type: 'email',
14
+ placeholder: 'Email',
15
+ }
16
+
17
+ const passwordField: TextField = {
18
+ _type: 'text',
19
+ type: 'password',
20
+ placeholder: 'Password',
21
+ }
22
+
23
+ const GoogleLogin = ({ provider }: { provider: GoogleAuthProvider }) => {
24
+ const { auth } = useBackend()
25
+ const { showError } = useToast()
26
+
27
+ const handleSignIn = useCallback(() => {
28
+ auth.loginWithPopup(provider).catch(error => {
29
+ showError(error instanceof Error ? error.message : 'An unknown error occurred')
30
+ })
31
+ }, [auth, provider, showError])
32
+
33
+ return (
34
+ <View style={styles.row}>
35
+ <LoadingButton style={styles.googleLoginButton} onPress={handleSignIn}>
36
+ <View style={styles.googleLogin}>
37
+ <SvgGoogle width={24} />
38
+ <Txt>Log In with Google</Txt>
39
+ </View>
40
+ </LoadingButton>
41
+ </View>
42
+ )
43
+ }
44
+
45
+ const EmailPasswordLogin = () => {
46
+ const [email, setEmail] = useState('')
47
+ const [password, setPassword] = useState('')
48
+ const [isLoading, setIsLoading] = useState(false)
49
+ const [error, setError] = useState<string | null>(null)
50
+
51
+ const { auth } = useBackend()
52
+
53
+ const handleLogin = async () => {
54
+ setIsLoading(true)
55
+ try {
56
+ await auth.loginWithPassword({ name: 'email' }, { email, password })
57
+ } catch (error) {
58
+ setError(error instanceof Error ? error.message : 'An unknown error occurred')
59
+ }
60
+ setIsLoading(false)
61
+ }
62
+
63
+ return (
64
+ <View style={styles.column}>
65
+ <StandaloneInput field={emailField} onChange={setEmail} value={email} />
66
+ <StandaloneInput field={passwordField} onChange={setPassword} value={password} />
67
+ {error && (
68
+ <View style={styles.error}>
69
+ <Txt style={styles.errorText}>{error}</Txt>
70
+ </View>
71
+ )}
72
+ <View style={styles.row}>
73
+ <View style={styles.forgotPassword}>
74
+ <LoadingButton
75
+ textStyle={styles.forgotPasswordButtonText}
76
+ contentStyle={styles.forgotPasswordButton}
77
+ onPress={() => {
78
+ console.log('press')
79
+ }}>
80
+ Forgot Password?
81
+ </LoadingButton>
82
+ </View>
83
+ <LoadingButton onPress={() => handleLogin()} disabled={isLoading}>
84
+ Sign In
85
+ </LoadingButton>
86
+ </View>
87
+ </View>
88
+ )
89
+ }
90
+
91
+ export const FirebaseSignIn = ({
92
+ googleProvider,
93
+ }: {
94
+ googleProvider: GoogleAuthProvider | null
95
+ }) => {
96
+ const borderColor = useBorderColor()
97
+ return (
98
+ <View style={styles.signInContent}>
99
+ <EmailPasswordLogin />
100
+ {googleProvider ? (
101
+ <>
102
+ <Divider style={{ backgroundColor: borderColor, width: '100%' }} />
103
+ <GoogleLogin provider={googleProvider} />
104
+ </>
105
+ ) : null}
106
+ </View>
107
+ )
108
+ }
109
+
110
+ const styles = StyleSheet.create({
111
+ container: {
112
+ flex: 1,
113
+ width: '100%',
114
+ justifyContent: 'center',
115
+ alignItems: 'center',
116
+ },
117
+ column: {
118
+ flexDirection: 'column',
119
+ gap: 20,
120
+ flex: 1,
121
+ width: '100%',
122
+ alignItems: 'center',
123
+ justifyContent: 'center',
124
+ },
125
+ colorModeToggle: {
126
+ paddingVertical: 20,
127
+ },
128
+ signInContent: {
129
+ paddingVertical: 20,
130
+ paddingHorizontal: 20,
131
+ height: 300,
132
+ gap: 12,
133
+ justifyContent: 'center',
134
+ alignItems: 'center',
135
+ width: '100%',
136
+ },
137
+ forgotPasswordButton: {
138
+ borderWidth: 0,
139
+ },
140
+ forgotPasswordButtonText: {
141
+ fontSize: 14,
142
+ },
143
+ row: {
144
+ flexDirection: 'row',
145
+ justifyContent: 'space-between',
146
+ width: '100%',
147
+ },
148
+ forgotPassword: {
149
+ // flex: 1,
150
+ },
151
+ header: {
152
+ fontSize: 16,
153
+ opacity: 0.7,
154
+ fontWeight: 'bold',
155
+ },
156
+ infoText: {
157
+ fontSize: 16,
158
+ textAlign: 'center',
159
+ // fontFamily: fontFamilies.body.regular,
160
+ paddingBottom: 20,
161
+ },
162
+ logo: {
163
+ height: 50,
164
+ resizeMode: 'contain',
165
+ },
166
+ link: {
167
+ fontSize: 14,
168
+ // fontFamily: fontFamilies.body.regular,
169
+ textDecorationLine: 'underline',
170
+ fontWeight: 'semibold',
171
+ },
172
+ googleLogin: {
173
+ flexDirection: 'row',
174
+ alignItems: 'center',
175
+ justifyContent: 'center',
176
+ gap: 10,
177
+ width: '100%',
178
+ },
179
+ googleLoginButton: {
180
+ width: '100%',
181
+ },
182
+ error: {},
183
+ errorText: {
184
+ fontSize: 12,
185
+ color: 'red',
186
+ },
187
+ })
@@ -0,0 +1,102 @@
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'
6
+ import { usePhoneVerify } from '../hooks/usePhoneVerify'
7
+
8
+ // UI to send code and verify code
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()
14
+
15
+ const verificationId = twoFactorVerification?.verificationId
16
+
17
+ let body: React.ReactNode = null
18
+
19
+ if (loading) {
20
+ body = (
21
+ <View style={styles.container}>
22
+ <ActivityIndicator size="large" color="#0000ff" />
23
+ <Txt style={styles.text}>Sending verification code...</Txt>
24
+ </View>
25
+ )
26
+ } else if (verificationId) {
27
+ body = (
28
+ <View style={styles.container}>
29
+ <Txt style={styles.text}>Enter the code sent to your phone:</Txt>
30
+ <TextInput
31
+ style={styles.input}
32
+ value={code}
33
+ onChangeText={setCode}
34
+ placeholder="Verification Code"
35
+ keyboardType="number-pad"
36
+ maxLength={6}
37
+ />
38
+ {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>
42
+ </View>
43
+ )
44
+ } else {
45
+ body = (
46
+ <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>
51
+ </View>
52
+ )
53
+ }
54
+
55
+ return <View style={styles.wrapper}>{body}</View>
56
+ }
57
+
58
+ const styles = StyleSheet.create({
59
+ wrapper: {
60
+ flex: 1,
61
+ padding: 16,
62
+ },
63
+ container: {
64
+ flex: 1,
65
+ alignItems: 'center',
66
+ justifyContent: 'center',
67
+ gap: 16,
68
+ },
69
+ text: {
70
+ fontSize: 16,
71
+ textAlign: 'center',
72
+ marginBottom: 8,
73
+ },
74
+ input: {
75
+ width: '100%',
76
+ height: 48,
77
+ borderWidth: 1,
78
+ borderColor: '#ccc',
79
+ borderRadius: 8,
80
+ paddingHorizontal: 16,
81
+ fontSize: 16,
82
+ marginBottom: 8,
83
+ },
84
+ button: {
85
+ backgroundColor: '#007AFF',
86
+ paddingHorizontal: 24,
87
+ paddingVertical: 12,
88
+ borderRadius: 8,
89
+ minWidth: 200,
90
+ alignItems: 'center',
91
+ },
92
+ buttonText: {
93
+ color: 'white',
94
+ fontSize: 16,
95
+ fontWeight: '600',
96
+ },
97
+ errorText: {
98
+ color: 'red',
99
+ fontSize: 14,
100
+ marginBottom: 8,
101
+ },
102
+ })
@@ -0,0 +1,133 @@
1
+ import { EnrollmentFactor, PhoneEnrollmentFactor } from '@chem-po/core'
2
+ import { useAuth, useBackgroundColor } from '@chem-po/react'
3
+ import { Txt } from '@chem-po/react-native'
4
+ import React, { useState } from 'react'
5
+ import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native'
6
+ import { Modal, Portal } from 'react-native-paper'
7
+ import { useBackend } from '../hooks/backend'
8
+ import { AuthenticatorVerify } from './AuthenticatorVerify'
9
+ import { PhoneVerify } from './PhoneVerify'
10
+
11
+
12
+ const TwoFactorSelect = ({
13
+ factors,
14
+ onSelect,
15
+ }: {
16
+ factors: EnrollmentFactor[]
17
+ onSelect: (factor: EnrollmentFactor) => void
18
+ }) => {
19
+ const buttonBackgroundColor = useBackgroundColor(100)
20
+ return (
21
+ <View style={styles.selectContainer}>
22
+ <Txt style={styles.selectTitle}>Select an option to verify with:</Txt>
23
+ <View style={styles.factorsList}>
24
+ {factors.map((factor) => (
25
+ <TouchableOpacity
26
+ key={factor.uid}
27
+ style={[
28
+ styles.factorButton,
29
+ {
30
+ backgroundColor: buttonBackgroundColor,
31
+ },
32
+ ]}
33
+ onPress={() => onSelect(factor)}
34
+ >
35
+ <Txt style={styles.factorHeaderText}>
36
+ {factor.type === 'phone' ? `Phone: ${factor.phoneNumber}` : 'Authenticator App'}
37
+ </Txt>
38
+ {
39
+ factor.displayName ? (
40
+ <Txt style={styles.factorDetailsText}>{factor.displayName}</Txt>
41
+ ) : null
42
+ }
43
+ </TouchableOpacity>
44
+ ))}
45
+ </View>
46
+ </View>
47
+ )
48
+ }
49
+
50
+ const TwoFactorVerify = ({ factorInfo }: { factorInfo: EnrollmentFactor }) =>
51
+ factorInfo.type === 'phone' ? (
52
+ <PhoneVerify factor={factorInfo as PhoneEnrollmentFactor} />
53
+ ) : (
54
+ <AuthenticatorVerify />
55
+ )
56
+
57
+ const TwoFactorContent = () => {
58
+ const [selectedEnrollmentFactor, setSelectedEnrollmentFactor] = useState<EnrollmentFactor | null>(
59
+ null
60
+ )
61
+ const enrolledFactors = useAuth((s) => s.enrollmentFactors)
62
+
63
+ if (selectedEnrollmentFactor) return <TwoFactorVerify factorInfo={selectedEnrollmentFactor} />
64
+ if (!enrolledFactors) return null
65
+ if (enrolledFactors.enrollmentFactors.length === 0) return null
66
+ // if (enrolledFactors.enrollmentFactors.length === 1) return <TwoFactorVerify factorInfo={enrolledFactors.enrollmentFactors[0]} />
67
+ return (
68
+ <TwoFactorSelect
69
+ factors={enrolledFactors.enrollmentFactors}
70
+ onSelect={setSelectedEnrollmentFactor}
71
+ />
72
+ )
73
+ }
74
+
75
+ export const TwoFactorAuthModal = () => {
76
+ const { multiFactorVerification: twoFactorVerification, enrollmentFactors } = useAuth()
77
+
78
+ const {auth} = useBackend()
79
+ const backgroundColor = useBackgroundColor(150)
80
+ if (!twoFactorVerification && !enrollmentFactors) return null
81
+
82
+ return (
83
+ <Portal>
84
+ <Modal
85
+ visible={true}
86
+ onDismiss={() => {auth.logout()}}
87
+ contentContainerStyle={[styles.modalContent, { backgroundColor }]}
88
+ style={styles.modal}
89
+ >
90
+ <ScrollView contentContainerStyle={styles.scrollContent}>
91
+ <TwoFactorContent />
92
+ </ScrollView>
93
+ </Modal>
94
+ </Portal>
95
+ )
96
+ }
97
+
98
+ const styles = StyleSheet.create({
99
+ modal: {
100
+ margin: 0,
101
+ },
102
+ modalContent: {
103
+ margin: 20,
104
+ borderRadius: 12,
105
+ maxHeight: '80%',
106
+ },
107
+ scrollContent: {
108
+ padding: 16,
109
+ },
110
+ selectContainer: {
111
+ width: '100%',
112
+ },
113
+ selectTitle: {
114
+ fontSize: 16,
115
+ marginBottom: 16,
116
+ },
117
+ factorsList: {
118
+ gap: 8,
119
+ },
120
+ factorButton: {
121
+ padding: 16,
122
+ borderRadius: 8,
123
+ borderWidth: 1,
124
+ },
125
+ factorHeaderText: {
126
+ fontSize: 14,
127
+ fontWeight: 'bold',
128
+ opacity: 0.8,
129
+ },
130
+ factorDetailsText: {
131
+ fontSize: 16,
132
+ },
133
+ })
@@ -0,0 +1,2 @@
1
+ export * from './FirebaseSignIn'
2
+ export * from './TwoFactorAuthModal'
@@ -0,0 +1,54 @@
1
+ import firebase from '@react-native-firebase/app'
2
+ import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'
3
+ import type {
4
+ FirebaseAuth,
5
+ FirebaseConfig,
6
+ FirebaseContextValue,
7
+ FirebaseFirestore,
8
+ FirebaseStorage,
9
+ } from '../types'
10
+
11
+ const FirebaseContext = createContext<FirebaseContextValue | null>(null)
12
+
13
+ export interface FirebaseProviderProps {
14
+ config: FirebaseConfig
15
+ children: React.ReactNode
16
+ }
17
+
18
+ export function FirebaseProvider({ config, children }: FirebaseProviderProps) {
19
+ const [loading, setLoading] = useState(true)
20
+ const [app, setApp] = useState<FirebaseContextValue['app'] | null>(null)
21
+ const [auth] = useState<FirebaseAuth>(firebase.auth())
22
+ const [firestore] = useState<FirebaseFirestore>(firebase.firestore())
23
+ const [storage] = useState<FirebaseStorage>(firebase.storage())
24
+
25
+ useEffect(() => {
26
+ const initializeApp = async () => {
27
+ const firebaseApp = await firebase.initializeApp(config)
28
+ setApp(firebaseApp)
29
+ setLoading(false)
30
+ }
31
+ initializeApp()
32
+ }, [config])
33
+
34
+ const value: FirebaseContextValue = useMemo(
35
+ () => ({
36
+ loading,
37
+ app,
38
+ auth,
39
+ firestore,
40
+ storage,
41
+ }),
42
+ [app, auth, firestore, storage, loading],
43
+ )
44
+
45
+ return <FirebaseContext.Provider value={value}>{children}</FirebaseContext.Provider>
46
+ }
47
+
48
+ export function useFirebase() {
49
+ const context = useContext(FirebaseContext)
50
+ if (!context) {
51
+ throw new Error('useFirebase must be used within a FirebaseProvider')
52
+ }
53
+ return context
54
+ }
@@ -0,0 +1 @@
1
+ export * from './FirebaseContext'
@@ -0,0 +1 @@
1
+ export * from './utils'
@@ -0,0 +1,142 @@
1
+ import { AnyObject, BaseQuery, isInequalityOperator, stringTransforms } from '@chem-po/core'
2
+ import {
3
+ collection,
4
+ collectionGroup,
5
+ limit as limitTo,
6
+ orderBy,
7
+ query,
8
+ startAfter,
9
+ where,
10
+ } from '@react-native-firebase/firestore'
11
+ import { Firestore, FirestoreBaseQuery, FirestoreCursor, FirestoreQuery } from '../types/db'
12
+
13
+ export const toFirestoreQuery = <T extends AnyObject>(
14
+ db: Firestore,
15
+ baseQuery: FirestoreBaseQuery<T>,
16
+ ): FirestoreQuery<T> => {
17
+ const {
18
+ collection: collectionPath,
19
+ filters,
20
+ limit,
21
+ search,
22
+ sort,
23
+ isCollectionGroup,
24
+ cursor: startAfterDoc,
25
+ } = baseQuery
26
+
27
+ let q: FirestoreQuery<T> = (
28
+ isCollectionGroup ? collectionGroup(db, collectionPath) : collection(db, collectionPath)
29
+ ) as FirestoreQuery<T>
30
+
31
+ if (filters) {
32
+ filters.forEach(({ key, operator, value }) => {
33
+ q = query(q, where(key, operator, value))
34
+ })
35
+ }
36
+
37
+ if (sort) {
38
+ q = query(q, orderBy(sort.key, sort.direction))
39
+ }
40
+
41
+ if (startAfterDoc) {
42
+ q = query(q, startAfter(startAfterDoc))
43
+ }
44
+
45
+ if (search?.paths?.length && search.query) {
46
+ const trimmed = (search.query || '').trim()
47
+ if (trimmed) {
48
+ q = query(
49
+ q,
50
+ where(search.paths[0].prop, '>=', trimmed),
51
+ where(search.paths[0].prop, '<=', `${trimmed}\uf8ff`),
52
+ )
53
+ }
54
+ }
55
+
56
+ return limit ? query(q, limitTo(limit)) : q
57
+ }
58
+
59
+ export const toCursorQuery = <T extends AnyObject>(
60
+ db: Firestore,
61
+ baseQuery: FirestoreBaseQuery<T>,
62
+ cursor: FirestoreCursor | null,
63
+ ): FirestoreQuery<T> => {
64
+ const { collection: collectionPath, filters, sort, isCollectionGroup, search, limit } = baseQuery
65
+
66
+ let q: FirestoreQuery<T> = (
67
+ isCollectionGroup ? collectionGroup(db, collectionPath) : collection(db, collectionPath)
68
+ ) as FirestoreQuery<T>
69
+
70
+ if (filters) {
71
+ filters.forEach(({ key, operator, value }) => {
72
+ q = query(q, where(key, operator, value))
73
+ })
74
+ }
75
+
76
+ if (sort) {
77
+ q = query(q, orderBy(sort.key, sort.direction))
78
+ }
79
+
80
+ if (search?.paths?.length && search.query) {
81
+ const trimmed = (search.query || '').trim()
82
+ if (trimmed) {
83
+ q = query(
84
+ q,
85
+ where(search.paths[0].prop, '>=', trimmed),
86
+ where(search.paths[0].prop, '<=', `${trimmed}\uf8ff`),
87
+ )
88
+ }
89
+ }
90
+
91
+ if (cursor) {
92
+ q = query(q, startAfter(cursor))
93
+ }
94
+
95
+ return limit ? query(q, limitTo(limit), limitTo(1)) : q
96
+ }
97
+
98
+ export const toFirestoreQueries = <T extends AnyObject>(
99
+ db: Firestore,
100
+ baseQuery: BaseQuery<T>,
101
+ cursors: Array<FirestoreCursor | null> | null,
102
+ ): Array<FirestoreQuery<T>> => {
103
+ const { collection: collectionPath, filters, limit, search, sort, isCollectionGroup } = baseQuery
104
+
105
+ let q: FirestoreQuery<T> = (
106
+ isCollectionGroup ? collectionGroup(db, collectionPath) : collection(db, collectionPath)
107
+ ) as FirestoreQuery<T>
108
+ if (filters) {
109
+ let filterSortKey: string | null = null
110
+ filters.forEach(({ key, operator, value }) => {
111
+ q = query(q, where(key, operator, value))
112
+ if (!filterSortKey && isInequalityOperator(operator) && (!sort || sort.key !== key)) {
113
+ filterSortKey = key
114
+ q = query(q, orderBy(key, 'asc'))
115
+ }
116
+ })
117
+ }
118
+
119
+ if (sort) {
120
+ q = query(q, orderBy(sort.key, sort.direction))
121
+ }
122
+
123
+ if (cursors?.length) {
124
+ q = query(q, startAfter(cursors[cursors.length - 1]))
125
+ }
126
+
127
+ const queries =
128
+ search?.paths?.length && search.query
129
+ ? search.paths.map(path => {
130
+ let trimmed = (search.query ?? '').trim()
131
+ if (!trimmed) return q
132
+ if (path.transform) trimmed = stringTransforms[path.transform](trimmed)
133
+ return query(
134
+ q,
135
+ where(path.prop, '>=', trimmed),
136
+ where(path.prop, '<=', `${trimmed}\uf8ff`),
137
+ )
138
+ })
139
+ : [q]
140
+
141
+ return queries.map(q0 => (limit ? query(q0, limitTo(limit)) : q0))
142
+ }
@@ -0,0 +1,4 @@
1
+ import { useBackendBase } from '@chem-po/react'
2
+ // import { FirebaseBackendAdapter } from '../types/adapter'
3
+
4
+ export const useBackend = () => useBackendBase()
@@ -0,0 +1 @@
1
+ export * from './backend'
@@ -0,0 +1,45 @@
1
+ import { useAuth } from '@chem-po/react'
2
+ import { FirebaseAuthTypes } from '@react-native-firebase/auth'
3
+ import { useCallback, useState } from 'react'
4
+ import { useBackend } from './backend'
5
+
6
+ export const useAuthenticatorVerify = () => {
7
+ const [code, setCode] = useState('')
8
+ const [verifying, setVerifying] = useState(false)
9
+ const [error, setError] = useState('')
10
+ const twoFactorVerification = useAuth(s => s.multiFactorVerification)
11
+ const { auth } = useBackend()
12
+ // const { showSuccess, showError, showInfo } = useToast()
13
+
14
+ const handleVerify = useCallback(async () => {
15
+ const verify = auth.verifyMultiFactor
16
+ if (!verify) {
17
+ setError('Error - Two factor verification is not supported')
18
+ return
19
+ }
20
+ if (!code) return
21
+ if (!twoFactorVerification) {
22
+ setError('Error - No session found')
23
+ return
24
+ }
25
+ const resolver = twoFactorVerification.resolver as FirebaseAuthTypes.MultiFactorResolver
26
+ if (!resolver) {
27
+ setError('Error - No session found')
28
+ return
29
+ }
30
+ setVerifying(true)
31
+ try {
32
+ await verify(twoFactorVerification, code)
33
+ } catch (error) {
34
+ setError(error instanceof Error ? error.message : 'An unknown error occurred')
35
+ }
36
+ }, [code, auth, twoFactorVerification])
37
+
38
+ return {
39
+ code,
40
+ setCode,
41
+ verifying,
42
+ error,
43
+ handleVerify,
44
+ }
45
+ }