yaver-feedback-react-native 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,26 +1,26 @@
1
- import React, { useEffect, useRef, useState } from 'react';
1
+ import React, { useState } from 'react';
2
2
  import {
3
- View,
4
- Text,
5
- TextInput,
6
- TouchableOpacity,
7
- StyleSheet,
8
3
  ActivityIndicator,
4
+ Alert,
5
+ KeyboardAvoidingView,
6
+ Platform,
7
+ Pressable,
9
8
  SafeAreaView,
10
9
  ScrollView,
11
- Linking,
12
- Platform,
10
+ StyleSheet,
11
+ Text,
12
+ TextInput,
13
+ View,
13
14
  } from 'react-native';
14
15
  import {
15
16
  loginWithEmail,
16
- signupWithEmail,
17
- pollDeviceCode,
18
- startDeviceCode,
19
- validateToken,
20
17
  saveToken,
21
18
  saveUser,
22
- OAuthProvider,
23
- DeviceCodeStart,
19
+ signInWithApple,
20
+ signInWithOAuth,
21
+ signupWithEmail,
22
+ validateToken,
23
+ type OAuthProvider,
24
24
  } from './auth';
25
25
 
26
26
  export interface YaverLoginScreenProps {
@@ -30,366 +30,303 @@ export interface YaverLoginScreenProps {
30
30
  onCancel?: () => void;
31
31
  }
32
32
 
33
- type Mode = 'device' | 'email';
34
-
35
- const PROVIDERS: { id: OAuthProvider; label: string; emoji: string }[] = [
36
- { id: 'apple', label: 'Apple', emoji: '' },
37
- { id: 'google', label: 'Google', emoji: 'G' },
38
- { id: 'github', label: 'GitHub', emoji: '' },
39
- { id: 'gitlab', label: 'GitLab', emoji: '' },
40
- { id: 'microsoft', label: 'Microsoft', emoji: 'M' },
41
- ];
42
-
43
33
  /**
44
- * Full-screen in-SDK login. Device-code is the default flow users sign in
45
- * with any OAuth provider (Apple/Google/GitHub/GitLab/Microsoft) or email on
46
- * yaver.io and the SDK polls for the issued session token. Email/password is
47
- * available as an inline fallback for headless environments.
34
+ * Full-screen in-SDK login. Mirrors the Yaver mobile app login UX: native
35
+ * Apple Sign-In on iOS, in-app browser OAuth for Google/GitHub/GitLab/
36
+ * Microsoft (no codes, no leaving the app), and inline email/password.
48
37
  */
49
38
  export const YaverLoginScreen: React.FC<YaverLoginScreenProps> = ({
50
39
  onLoggedIn,
51
40
  onCancel,
52
41
  }) => {
53
- const [mode, setMode] = useState<Mode>('device');
54
-
55
- const [code, setCode] = useState<DeviceCodeStart | null>(null);
56
- const [codeError, setCodeError] = useState<string | null>(null);
57
- const [starting, setStarting] = useState(false);
58
- const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
59
- const expiredTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
60
-
61
- const [emailMode, setEmailMode] = useState<'login' | 'signup'>('login');
42
+ const [busyProvider, setBusyProvider] = useState<OAuthProvider | 'apple' | null>(null);
43
+ const [showEmailForm, setShowEmailForm] = useState(false);
44
+ const [isSignUp, setIsSignUp] = useState(false);
62
45
  const [fullName, setFullName] = useState('');
63
46
  const [email, setEmail] = useState('');
64
47
  const [password, setPassword] = useState('');
48
+ const [confirmPassword, setConfirmPassword] = useState('');
65
49
  const [emailBusy, setEmailBusy] = useState(false);
66
- const [emailError, setEmailError] = useState<string | null>(null);
67
-
68
- useEffect(() => {
69
- if (mode === 'device' && !code && !starting) {
70
- void beginDeviceCode(undefined);
71
- }
72
- return () => {
73
- stopPolling();
74
- };
75
- // eslint-disable-next-line react-hooks/exhaustive-deps
76
- }, [mode]);
50
+ const [emailError, setEmailError] = useState('');
77
51
 
78
- const stopPolling = () => {
79
- if (pollRef.current) {
80
- clearInterval(pollRef.current);
81
- pollRef.current = null;
82
- }
83
- if (expiredTimerRef.current) {
84
- clearTimeout(expiredTimerRef.current);
85
- expiredTimerRef.current = null;
86
- }
52
+ const finish = async (token: string) => {
53
+ const user = await validateToken(token);
54
+ await saveToken(token);
55
+ if (user) await saveUser(user);
56
+ onLoggedIn(token);
87
57
  };
88
58
 
89
- const beginDeviceCode = async (preferredProvider?: OAuthProvider) => {
90
- setStarting(true);
91
- setCodeError(null);
92
- stopPolling();
59
+ const handleApple = async () => {
60
+ setBusyProvider('apple');
93
61
  try {
94
- const result = await startDeviceCode({
95
- platform: Platform.OS,
96
- machineName: `feedback-sdk-${Platform.OS}`,
97
- preferredProvider,
98
- });
99
- setCode(result);
100
-
101
- pollRef.current = setInterval(async () => {
102
- const poll = await pollDeviceCode(result.deviceCode);
103
- if (poll.status === 'authorized') {
104
- stopPolling();
105
- const user = await validateToken(poll.token);
106
- await saveToken(poll.token);
107
- if (user) await saveUser(user);
108
- onLoggedIn(poll.token);
109
- } else if (poll.status === 'expired') {
110
- stopPolling();
111
- setCodeError('Kod doldu — tekrar başlat.');
112
- setCode(null);
113
- }
114
- }, 3_000);
115
-
116
- expiredTimerRef.current = setTimeout(() => {
117
- stopPolling();
118
- setCode(null);
119
- setCodeError('Kod süresi doldu.');
120
- }, Math.max(0, result.expiresAt - Date.now()));
121
- } catch (err) {
122
- setCodeError(err instanceof Error ? err.message : String(err));
62
+ const { token } = await signInWithApple();
63
+ await finish(token);
64
+ } catch (e: unknown) {
65
+ const msg = e instanceof Error ? e.message : 'Apple Sign-In failed';
66
+ if (msg !== 'cancelled') Alert.alert('Sign In Failed', msg);
123
67
  } finally {
124
- setStarting(false);
68
+ setBusyProvider(null);
125
69
  }
126
70
  };
127
71
 
128
- const openVerification = () => {
129
- if (!code) return;
130
- Linking.openURL(code.verificationUrl).catch(() => {
131
- setCodeError('Tarayıcı açılamadı URL’yi elle aç.');
132
- });
72
+ const handleOAuth = async (provider: OAuthProvider) => {
73
+ setBusyProvider(provider);
74
+ try {
75
+ const { token } = await signInWithOAuth(provider);
76
+ await finish(token);
77
+ } catch (e: unknown) {
78
+ const msg = e instanceof Error ? e.message : 'Sign-in failed';
79
+ if (msg !== 'cancelled') Alert.alert('Sign In Failed', msg);
80
+ } finally {
81
+ setBusyProvider(null);
82
+ }
133
83
  };
134
84
 
135
85
  const handleEmailSubmit = async () => {
136
- setEmailError(null);
137
- if (!email.trim() || !password) {
138
- setEmailError('E-posta ve parola zorunlu.');
139
- return;
86
+ setEmailError('');
87
+ if (isSignUp) {
88
+ if (!fullName.trim()) return setEmailError('Full name is required');
89
+ if (password !== confirmPassword)
90
+ return setEmailError('Passwords do not match');
91
+ if (password.length < 8)
92
+ return setEmailError('Password must be at least 8 characters');
140
93
  }
94
+ if (!email.trim() || !password)
95
+ return setEmailError('Email and password are required');
96
+
141
97
  setEmailBusy(true);
142
98
  try {
143
- const result =
144
- emailMode === 'signup'
145
- ? await signupWithEmail(fullName.trim() || email.trim(), email.trim(), password)
146
- : await loginWithEmail(email.trim(), password);
147
- const user = await validateToken(result.token);
148
- await saveToken(result.token);
149
- if (user) await saveUser(user);
150
- onLoggedIn(result.token);
151
- } catch (err) {
152
- setEmailError(err instanceof Error ? err.message : String(err));
99
+ const result = isSignUp
100
+ ? await signupWithEmail(fullName.trim(), email.trim(), password)
101
+ : await loginWithEmail(email.trim(), password);
102
+ await finish(result.token);
103
+ } catch (e: unknown) {
104
+ setEmailError(e instanceof Error ? e.message : 'Something went wrong');
153
105
  } finally {
154
106
  setEmailBusy(false);
155
107
  }
156
108
  };
157
109
 
110
+ const renderProvider = (
111
+ id: OAuthProvider | 'apple',
112
+ label: string,
113
+ onPress: () => void,
114
+ ) => (
115
+ <Pressable
116
+ key={id}
117
+ style={({ pressed }) => [
118
+ styles.button,
119
+ pressed && styles.buttonPressed,
120
+ busyProvider === id && { opacity: 0.6 },
121
+ ]}
122
+ onPress={onPress}
123
+ disabled={busyProvider !== null}
124
+ >
125
+ {busyProvider === id ? (
126
+ <ActivityIndicator color="#e0e0e0" />
127
+ ) : (
128
+ <Text style={styles.buttonText}>{label}</Text>
129
+ )}
130
+ </Pressable>
131
+ );
132
+
158
133
  return (
159
- <SafeAreaView style={styles.container}>
160
- <ScrollView
161
- contentContainerStyle={styles.content}
162
- keyboardShouldPersistTaps="handled"
134
+ <SafeAreaView style={styles.safeArea}>
135
+ <KeyboardAvoidingView
136
+ style={{ flex: 1 }}
137
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
163
138
  >
164
- <View style={styles.header}>
165
- <Text style={styles.title}>Yaver Girişi</Text>
166
- {onCancel && (
167
- <TouchableOpacity onPress={onCancel} style={styles.cancel}>
168
- <Text style={styles.cancelText}>İptal</Text>
169
- </TouchableOpacity>
170
- )}
171
- </View>
172
-
173
- <View style={styles.tabRow}>
174
- <TouchableOpacity
175
- style={[styles.tab, mode === 'device' && styles.tabActive]}
176
- onPress={() => setMode('device')}
177
- >
178
- <Text
179
- style={[styles.tabText, mode === 'device' && styles.tabTextActive]}
180
- >
181
- Hızlı Giriş (OAuth)
182
- </Text>
183
- </TouchableOpacity>
184
- <TouchableOpacity
185
- style={[styles.tab, mode === 'email' && styles.tabActive]}
186
- onPress={() => setMode('email')}
187
- >
188
- <Text
189
- style={[styles.tabText, mode === 'email' && styles.tabTextActive]}
190
- >
191
- E-posta
192
- </Text>
193
- </TouchableOpacity>
194
- </View>
195
-
196
- {mode === 'device' && (
197
- <View style={styles.section}>
198
- {starting ? (
199
- <ActivityIndicator color="#6366f1" style={{ marginVertical: 40 }} />
200
- ) : code ? (
201
- <>
202
- <Text style={styles.hint}>
203
- Tarayıcıda yaver.io/auth/device aç ve aşağıdaki kodu gir.
204
- Sağlayıcıyla giriş yaptığında buraya otomatik dönecek.
205
- </Text>
206
- <View style={styles.codeBox}>
207
- <Text style={styles.codeText}>{code.userCode}</Text>
208
- </View>
209
- <TouchableOpacity style={styles.primaryButton} onPress={openVerification}>
210
- <Text style={styles.primaryButtonText}>Tarayıcıda Aç</Text>
211
- </TouchableOpacity>
212
-
213
- <Text style={[styles.hint, { marginTop: 20 }]}>
214
- Sağlayıcıyı önceden seçmek istersen:
215
- </Text>
216
- <View style={styles.providerRow}>
217
- {PROVIDERS.map((p) => (
218
- <TouchableOpacity
219
- key={p.id}
220
- style={styles.providerButton}
221
- onPress={() => beginDeviceCode(p.id)}
222
- >
223
- <Text style={styles.providerButtonText}>{p.label}</Text>
224
- </TouchableOpacity>
225
- ))}
226
- </View>
227
- </>
228
- ) : (
229
- <TouchableOpacity
230
- style={styles.primaryButton}
231
- onPress={() => beginDeviceCode(undefined)}
232
- >
233
- <Text style={styles.primaryButtonText}>Tekrar Başlat</Text>
234
- </TouchableOpacity>
139
+ <ScrollView
140
+ contentContainerStyle={styles.scrollContainer}
141
+ keyboardShouldPersistTaps="handled"
142
+ >
143
+ <View style={styles.header}>
144
+ <Text style={styles.logo}>Yaver</Text>
145
+ <Text style={styles.subtitle}>Sign in to send feedback</Text>
146
+ {onCancel && (
147
+ <Pressable onPress={onCancel} style={styles.cancel}>
148
+ <Text style={styles.cancelText}>Cancel</Text>
149
+ </Pressable>
235
150
  )}
236
- {codeError && <Text style={styles.error}>{codeError}</Text>}
237
151
  </View>
238
- )}
239
152
 
240
- {mode === 'email' && (
241
- <View style={styles.section}>
242
- <View style={styles.tabRow}>
243
- <TouchableOpacity
244
- style={[styles.subTab, emailMode === 'login' && styles.subTabActive]}
245
- onPress={() => setEmailMode('login')}
246
- >
247
- <Text style={styles.subTabText}>Giriş</Text>
248
- </TouchableOpacity>
249
- <TouchableOpacity
250
- style={[styles.subTab, emailMode === 'signup' && styles.subTabActive]}
251
- onPress={() => setEmailMode('signup')}
252
- >
253
- <Text style={styles.subTabText}>Kayıt</Text>
254
- </TouchableOpacity>
255
- </View>
153
+ <View style={styles.buttons}>
154
+ {Platform.OS === 'ios'
155
+ ? renderProvider('apple', 'Continue with Apple', handleApple)
156
+ : renderProvider('apple', 'Continue with Apple', () =>
157
+ handleOAuth('apple'),
158
+ )}
159
+ {renderProvider('google', 'Continue with Google', () =>
160
+ handleOAuth('google'),
161
+ )}
162
+ {renderProvider('github', 'Continue with GitHub', () =>
163
+ handleOAuth('github'),
164
+ )}
165
+ {renderProvider('gitlab', 'Continue with GitLab', () =>
166
+ handleOAuth('gitlab'),
167
+ )}
168
+ {renderProvider('microsoft', 'Continue with Microsoft', () =>
169
+ handleOAuth('microsoft'),
170
+ )}
256
171
 
257
- {emailMode === 'signup' && (
172
+ {!showEmailForm ? (
173
+ <Pressable
174
+ style={({ pressed }) => [
175
+ styles.button,
176
+ pressed && styles.buttonPressed,
177
+ ]}
178
+ onPress={() => setShowEmailForm(true)}
179
+ disabled={busyProvider !== null}
180
+ >
181
+ <Text style={styles.buttonText}>Continue with Email</Text>
182
+ </Pressable>
183
+ ) : (
258
184
  <>
259
- <Text style={styles.label}>Ad Soyad</Text>
185
+ <View style={styles.divider}>
186
+ <View style={styles.dividerLine} />
187
+ <Text style={styles.dividerText}>email</Text>
188
+ <View style={styles.dividerLine} />
189
+ </View>
190
+ {isSignUp && (
191
+ <TextInput
192
+ style={styles.input}
193
+ placeholder="Full Name"
194
+ placeholderTextColor="#666"
195
+ value={fullName}
196
+ onChangeText={setFullName}
197
+ autoCapitalize="words"
198
+ autoCorrect={false}
199
+ />
200
+ )}
260
201
  <TextInput
261
202
  style={styles.input}
262
- value={fullName}
263
- onChangeText={setFullName}
264
- placeholder="Adın Soyadın"
203
+ placeholder="Email"
265
204
  placeholderTextColor="#666"
266
- autoCapitalize="words"
205
+ value={email}
206
+ onChangeText={setEmail}
207
+ keyboardType="email-address"
208
+ autoCapitalize="none"
209
+ autoCorrect={false}
267
210
  />
268
- </>
269
- )}
270
-
271
- <Text style={styles.label}>E-posta</Text>
272
- <TextInput
273
- style={styles.input}
274
- value={email}
275
- onChangeText={setEmail}
276
- placeholder="you@example.com"
277
- placeholderTextColor="#666"
278
- keyboardType="email-address"
279
- autoCapitalize="none"
280
- autoCorrect={false}
281
- />
211
+ <TextInput
212
+ style={styles.input}
213
+ placeholder="Password"
214
+ placeholderTextColor="#666"
215
+ value={password}
216
+ onChangeText={setPassword}
217
+ secureTextEntry
218
+ autoCapitalize="none"
219
+ />
220
+ {isSignUp && (
221
+ <TextInput
222
+ style={styles.input}
223
+ placeholder="Confirm Password"
224
+ placeholderTextColor="#666"
225
+ value={confirmPassword}
226
+ onChangeText={setConfirmPassword}
227
+ secureTextEntry
228
+ />
229
+ )}
282
230
 
283
- <Text style={styles.label}>Parola</Text>
284
- <TextInput
285
- style={styles.input}
286
- value={password}
287
- onChangeText={setPassword}
288
- placeholder="••••••••"
289
- placeholderTextColor="#666"
290
- secureTextEntry
291
- autoCapitalize="none"
292
- />
231
+ {emailError ? (
232
+ <Text style={styles.errorText}>{emailError}</Text>
233
+ ) : null}
293
234
 
294
- <TouchableOpacity
295
- style={styles.primaryButton}
296
- onPress={handleEmailSubmit}
297
- disabled={emailBusy}
298
- >
299
- {emailBusy ? (
300
- <ActivityIndicator color="#fff" />
301
- ) : (
302
- <Text style={styles.primaryButtonText}>
303
- {emailMode === 'signup' ? 'Kayıt Ol' : 'Giriş Yap'}
304
- </Text>
305
- )}
306
- </TouchableOpacity>
235
+ <Pressable
236
+ style={({ pressed }) => [
237
+ styles.submitButton,
238
+ pressed && styles.buttonPressed,
239
+ emailBusy && { opacity: 0.6 },
240
+ ]}
241
+ onPress={handleEmailSubmit}
242
+ disabled={emailBusy}
243
+ >
244
+ {emailBusy ? (
245
+ <ActivityIndicator color="#fff" />
246
+ ) : (
247
+ <Text style={styles.submitButtonText}>
248
+ {isSignUp ? 'Create Account' : 'Sign In'}
249
+ </Text>
250
+ )}
251
+ </Pressable>
307
252
 
308
- {emailError && <Text style={styles.error}>{emailError}</Text>}
253
+ <Pressable
254
+ onPress={() => {
255
+ setIsSignUp(!isSignUp);
256
+ setEmailError('');
257
+ }}
258
+ >
259
+ <Text style={styles.toggleText}>
260
+ {isSignUp
261
+ ? 'Already have an account? Sign In'
262
+ : "Don't have an account? Sign Up"}
263
+ </Text>
264
+ </Pressable>
265
+ </>
266
+ )}
309
267
  </View>
310
- )}
311
- </ScrollView>
268
+ </ScrollView>
269
+ </KeyboardAvoidingView>
312
270
  </SafeAreaView>
313
271
  );
314
272
  };
315
273
 
316
274
  const styles = StyleSheet.create({
317
- container: { flex: 1, backgroundColor: '#1a1a2e' },
318
- content: { padding: 24, paddingTop: 16 },
319
- header: {
320
- flexDirection: 'row',
321
- alignItems: 'center',
322
- justifyContent: 'space-between',
323
- marginBottom: 20,
275
+ safeArea: { flex: 1, backgroundColor: '#1a1a2e' },
276
+ scrollContainer: {
277
+ flexGrow: 1,
278
+ paddingHorizontal: 24,
279
+ justifyContent: 'center',
324
280
  },
325
- title: { fontSize: 22, fontWeight: '700', color: '#e0e0e0' },
326
- cancel: { padding: 8 },
281
+ header: { alignItems: 'center', marginBottom: 40 },
282
+ logo: { fontSize: 44, fontWeight: '800', color: '#e0e0e0', letterSpacing: -1 },
283
+ subtitle: { fontSize: 15, color: '#9ca3af', marginTop: 6 },
284
+ cancel: { position: 'absolute', right: 0, top: 0, padding: 8 },
327
285
  cancelText: { color: '#9ca3af', fontSize: 14 },
328
- tabRow: { flexDirection: 'row', gap: 8, marginBottom: 16 },
329
- tab: {
330
- flex: 1,
331
- paddingVertical: 12,
332
- borderRadius: 10,
333
- backgroundColor: 'rgba(255,255,255,0.05)',
334
- alignItems: 'center',
335
- },
336
- tabActive: { backgroundColor: 'rgba(99,102,241,0.25)' },
337
- tabText: { color: '#9ca3af', fontSize: 14, fontWeight: '600' },
338
- tabTextActive: { color: '#e0e0e0' },
339
- subTab: {
340
- flex: 1,
341
- paddingVertical: 8,
342
- borderRadius: 8,
343
- alignItems: 'center',
344
- backgroundColor: 'rgba(255,255,255,0.05)',
345
- },
346
- subTabActive: { backgroundColor: 'rgba(99,102,241,0.2)' },
347
- subTabText: { color: '#e0e0e0', fontSize: 13 },
348
- section: { marginTop: 4 },
349
- hint: { color: '#9ca3af', fontSize: 13, lineHeight: 18 },
350
- codeBox: {
351
- backgroundColor: 'rgba(99,102,241,0.15)',
286
+ buttons: { gap: 12 },
287
+ button: {
288
+ backgroundColor: 'rgba(255,255,255,0.06)',
352
289
  borderWidth: 1,
353
- borderColor: 'rgba(99,102,241,0.35)',
354
- borderRadius: 14,
355
- paddingVertical: 24,
356
- alignItems: 'center',
357
- marginTop: 16,
358
- marginBottom: 16,
359
- },
360
- codeText: {
361
- color: '#e0e7ff',
362
- fontSize: 36,
363
- fontWeight: '800',
364
- letterSpacing: 6,
365
- fontVariant: ['tabular-nums'],
366
- },
367
- primaryButton: {
368
- backgroundColor: '#6366f1',
290
+ borderColor: 'rgba(255,255,255,0.12)',
369
291
  borderRadius: 12,
370
292
  paddingVertical: 14,
371
293
  alignItems: 'center',
372
- marginTop: 8,
294
+ justifyContent: 'center',
373
295
  },
374
- primaryButtonText: { color: '#fff', fontWeight: '700', fontSize: 15 },
375
- providerRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 10 },
376
- providerButton: {
377
- backgroundColor: 'rgba(255,255,255,0.08)',
378
- borderRadius: 10,
379
- paddingVertical: 10,
380
- paddingHorizontal: 14,
296
+ buttonPressed: { opacity: 0.7 },
297
+ buttonText: { color: '#e0e0e0', fontSize: 15, fontWeight: '600' },
298
+ divider: {
299
+ flexDirection: 'row',
300
+ alignItems: 'center',
301
+ marginTop: 16,
302
+ marginBottom: 8,
381
303
  },
382
- providerButtonText: { color: '#e0e0e0', fontSize: 13, fontWeight: '600' },
383
- label: { color: '#9ca3af', fontSize: 12, marginTop: 14, marginBottom: 6 },
304
+ dividerLine: { flex: 1, height: 1, backgroundColor: 'rgba(255,255,255,0.12)' },
305
+ dividerText: { marginHorizontal: 14, fontSize: 12, color: '#6b7280' },
384
306
  input: {
385
- backgroundColor: 'rgba(255,255,255,0.08)',
307
+ backgroundColor: 'rgba(255,255,255,0.06)',
386
308
  borderWidth: 1,
387
- borderColor: 'rgba(255,255,255,0.15)',
388
- borderRadius: 10,
309
+ borderColor: 'rgba(255,255,255,0.12)',
310
+ borderRadius: 12,
389
311
  paddingHorizontal: 14,
390
- paddingVertical: 12,
312
+ paddingVertical: 13,
391
313
  color: '#e0e0e0',
392
314
  fontSize: 15,
393
315
  },
394
- error: { color: '#ef4444', fontSize: 13, marginTop: 12 },
316
+ errorText: { color: '#ef4444', fontSize: 13, textAlign: 'center' },
317
+ submitButton: {
318
+ backgroundColor: '#6366f1',
319
+ borderRadius: 12,
320
+ paddingVertical: 14,
321
+ alignItems: 'center',
322
+ justifyContent: 'center',
323
+ marginTop: 4,
324
+ },
325
+ submitButtonText: { color: '#fff', fontSize: 15, fontWeight: '700' },
326
+ toggleText: {
327
+ color: '#818cf8',
328
+ fontSize: 14,
329
+ textAlign: 'center',
330
+ marginTop: 4,
331
+ },
395
332
  });