@oxyhq/services 6.9.43 → 6.9.44

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 (60) hide show
  1. package/lib/commonjs/index.js +9 -0
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/components/ActingAsBanner.js +143 -0
  4. package/lib/commonjs/ui/components/ActingAsBanner.js.map +1 -0
  5. package/lib/commonjs/ui/context/OxyContext.js +78 -3
  6. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  7. package/lib/commonjs/ui/navigation/routes.js +3 -2
  8. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  9. package/lib/commonjs/ui/screens/AccountCenterScreen.js +21 -1
  10. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  11. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +235 -1
  12. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  13. package/lib/commonjs/ui/screens/CreateManagedAccountScreen.js +346 -0
  14. package/lib/commonjs/ui/screens/CreateManagedAccountScreen.js.map +1 -0
  15. package/lib/module/index.js +3 -0
  16. package/lib/module/index.js.map +1 -1
  17. package/lib/module/ui/components/ActingAsBanner.js +140 -0
  18. package/lib/module/ui/components/ActingAsBanner.js.map +1 -0
  19. package/lib/module/ui/context/OxyContext.js +78 -3
  20. package/lib/module/ui/context/OxyContext.js.map +1 -1
  21. package/lib/module/ui/navigation/routes.js +3 -2
  22. package/lib/module/ui/navigation/routes.js.map +1 -1
  23. package/lib/module/ui/screens/AccountCenterScreen.js +21 -1
  24. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  25. package/lib/module/ui/screens/AccountSwitcherScreen.js +235 -1
  26. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  27. package/lib/module/ui/screens/CreateManagedAccountScreen.js +342 -0
  28. package/lib/module/ui/screens/CreateManagedAccountScreen.js.map +1 -0
  29. package/lib/typescript/commonjs/index.d.ts +1 -0
  30. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/ui/components/ActingAsBanner.d.ts +4 -0
  32. package/lib/typescript/commonjs/ui/components/ActingAsBanner.d.ts.map +1 -0
  33. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +6 -0
  34. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  36. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/ui/screens/CreateManagedAccountScreen.d.ts +5 -0
  40. package/lib/typescript/commonjs/ui/screens/CreateManagedAccountScreen.d.ts.map +1 -0
  41. package/lib/typescript/module/index.d.ts +1 -0
  42. package/lib/typescript/module/index.d.ts.map +1 -1
  43. package/lib/typescript/module/ui/components/ActingAsBanner.d.ts +4 -0
  44. package/lib/typescript/module/ui/components/ActingAsBanner.d.ts.map +1 -0
  45. package/lib/typescript/module/ui/context/OxyContext.d.ts +6 -0
  46. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  47. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  48. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  49. package/lib/typescript/module/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  50. package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  51. package/lib/typescript/module/ui/screens/CreateManagedAccountScreen.d.ts +5 -0
  52. package/lib/typescript/module/ui/screens/CreateManagedAccountScreen.d.ts.map +1 -0
  53. package/package.json +1 -1
  54. package/src/index.ts +3 -0
  55. package/src/ui/components/ActingAsBanner.tsx +135 -0
  56. package/src/ui/context/OxyContext.tsx +85 -0
  57. package/src/ui/navigation/routes.ts +3 -1
  58. package/src/ui/screens/AccountCenterScreen.tsx +21 -1
  59. package/src/ui/screens/AccountSwitcherScreen.tsx +209 -0
  60. package/src/ui/screens/CreateManagedAccountScreen.tsx +338 -0
@@ -60,11 +60,15 @@ const ModernAccountSwitcherScreen: React.FC<BaseScreenProps> = ({
60
60
  refreshSessions,
61
61
  isLoading = false,
62
62
  isAuthenticated = false,
63
+ actingAs,
64
+ managedAccounts,
65
+ setActingAs,
63
66
  } = useOxy();
64
67
 
65
68
  const [sessionsWithUsers, setSessionsWithUsers] = useState<SessionWithUser[]>([]);
66
69
  const [switchingToUserId, setSwitchingToUserId] = useState<string | null>(null);
67
70
  const [removingUserId, setRemovingUserId] = useState<string | null>(null);
71
+ const [switchingManagedId, setSwitchingManagedId] = useState<string | null>(null);
68
72
 
69
73
  // Device session management state
70
74
  const [showDeviceManagement, setShowDeviceManagement] = useState(false);
@@ -210,6 +214,30 @@ const ModernAccountSwitcherScreen: React.FC<BaseScreenProps> = ({
210
214
  );
211
215
  }, [logoutAll, onClose, t]);
212
216
 
217
+ const handleSwitchToManagedAccount = useCallback(async (accountId: string) => {
218
+ if (actingAs === accountId) return; // Already acting as this account
219
+ if (switchingManagedId) return; // Already switching
220
+
221
+ setSwitchingManagedId(accountId);
222
+ try {
223
+ setActingAs(accountId);
224
+ toast.success(t('accountSwitcher.toasts.switchSuccess') || 'Switched identity successfully!');
225
+ onClose?.();
226
+ } catch (error) {
227
+ if (__DEV__) {
228
+ console.error('Switch managed account failed:', error);
229
+ }
230
+ toast.error('Failed to switch identity. Please try again.');
231
+ } finally {
232
+ setSwitchingManagedId(null);
233
+ }
234
+ }, [actingAs, switchingManagedId, setActingAs, t, onClose]);
235
+
236
+ const handleSwitchBackToPrimary = useCallback(() => {
237
+ setActingAs(null);
238
+ toast.success('Switched back to primary account');
239
+ }, [setActingAs]);
240
+
213
241
  // Device session management functions - optimized with useCallback
214
242
  const loadAllDeviceSessions = useCallback(async () => {
215
243
  const currentActiveSessionId = activeSessionId ?? null;
@@ -428,6 +456,151 @@ const ModernAccountSwitcherScreen: React.FC<BaseScreenProps> = ({
428
456
  </View>
429
457
  )}
430
458
 
459
+ {/* Acting as banner - show switch-back when acting as a managed account */}
460
+ {actingAs && (
461
+ <View style={styles.section}>
462
+ <TouchableOpacity
463
+ style={[styles.settingItem, styles.firstSettingItem, styles.lastSettingItem, styles.actingAsBanner]}
464
+ onPress={handleSwitchBackToPrimary}
465
+ activeOpacity={0.7}
466
+ >
467
+ <View style={styles.settingInfo}>
468
+ <Text style={styles.settingLabel}>Switch back to primary account</Text>
469
+ <Text style={styles.settingDescription}>You are currently acting as another identity</Text>
470
+ </View>
471
+ <View style={styles.switchBackButton}>
472
+ <Text style={styles.switchBackButtonText}>Switch Back</Text>
473
+ </View>
474
+ </TouchableOpacity>
475
+ </View>
476
+ )}
477
+
478
+ {/* Managed Accounts */}
479
+ {managedAccounts.length > 0 && (
480
+ <View style={styles.section}>
481
+ <Text style={styles.sectionTitle}>Managed Accounts</Text>
482
+ <Text style={styles.sectionSubtitle}>Identities you manage</Text>
483
+
484
+ {managedAccounts.map((managed, index) => {
485
+ const account = managed.account;
486
+ if (!account) return null;
487
+
488
+ const isActive = actingAs === managed.accountId;
489
+ const isSwitching = switchingManagedId === managed.accountId;
490
+ const isFirst = index === 0;
491
+ const isLast = index === managedAccounts.length - 1;
492
+
493
+ const managedDisplayName = typeof account.name === 'object'
494
+ ? account.name.full || account.name.first || account.username
495
+ : account.name || account.username || 'Unknown';
496
+
497
+ // Determine the manager role for badge display
498
+ const myRole = managed.managers?.find(
499
+ (m) => m.userId === user?.id
500
+ )?.role ?? 'owner';
501
+
502
+ return (
503
+ <TouchableOpacity
504
+ key={`managed-${managed.accountId}`}
505
+ style={[
506
+ styles.settingItem,
507
+ isFirst && styles.firstSettingItem,
508
+ isLast && styles.lastSettingItem,
509
+ isActive && styles.currentAccountCard,
510
+ ]}
511
+ onPress={() => handleSwitchToManagedAccount(managed.accountId)}
512
+ disabled={isActive || isSwitching}
513
+ activeOpacity={0.7}
514
+ >
515
+ <View style={styles.userIcon}>
516
+ {account.avatar ? (
517
+ <Image source={{ uri: oxyServices.getFileDownloadUrl(account.avatar, 'thumb') }} style={styles.accountAvatarImage} />
518
+ ) : (
519
+ <View style={[styles.accountAvatarFallback, styles.managedAvatarFallback]}>
520
+ <Text style={styles.accountAvatarText}>
521
+ {managedDisplayName.charAt(0).toUpperCase()}
522
+ </Text>
523
+ </View>
524
+ )}
525
+ {isActive && (
526
+ <View style={styles.activeBadge}>
527
+ <OxyIcon name="checkmark" size={12} color="#fff" />
528
+ </View>
529
+ )}
530
+ </View>
531
+ <View style={styles.settingInfo}>
532
+ <View>
533
+ <Text style={styles.settingLabel}>{managedDisplayName}</Text>
534
+ <Text style={styles.settingDescription}>@{account.username}</Text>
535
+ </View>
536
+ </View>
537
+ <View style={styles.accountActions}>
538
+ <View style={styles.roleBadge}>
539
+ <Text style={styles.roleBadgeText}>{myRole}</Text>
540
+ </View>
541
+ {isActive ? (
542
+ <View style={styles.currentBadge}>
543
+ <Text style={styles.currentBadgeText}>Current</Text>
544
+ </View>
545
+ ) : (
546
+ <TouchableOpacity
547
+ style={styles.switchButton}
548
+ onPress={() => handleSwitchToManagedAccount(managed.accountId)}
549
+ disabled={isSwitching}
550
+ >
551
+ {isSwitching ? (
552
+ <ActivityIndicator size="small" color="#fff" />
553
+ ) : (
554
+ <Text style={styles.switchButtonText}>Act As</Text>
555
+ )}
556
+ </TouchableOpacity>
557
+ )}
558
+ </View>
559
+ </TouchableOpacity>
560
+ );
561
+ })}
562
+
563
+ {/* Create New Identity */}
564
+ <TouchableOpacity
565
+ style={[styles.settingItem, styles.firstSettingItem, styles.lastSettingItem, { marginTop: 8 }]}
566
+ onPress={() => navigate?.('CreateManagedAccount')}
567
+ activeOpacity={0.7}
568
+ >
569
+ <View style={styles.userIcon}>
570
+ <View style={[styles.accountAvatarFallback, { backgroundColor: '#007AFF20' }]}>
571
+ <OxyIcon name="add" size={20} color="#007AFF" />
572
+ </View>
573
+ </View>
574
+ <View style={styles.settingInfo}>
575
+ <Text style={[styles.settingLabel, { color: '#007AFF' }]}>Create New Identity</Text>
576
+ <Text style={styles.settingDescription}>Add a managed sub-account</Text>
577
+ </View>
578
+ </TouchableOpacity>
579
+ </View>
580
+ )}
581
+
582
+ {/* Create first managed account (when none exist yet) */}
583
+ {managedAccounts.length === 0 && isAuthenticated && (
584
+ <View style={styles.section}>
585
+ <Text style={styles.sectionTitle}>Managed Accounts</Text>
586
+ <TouchableOpacity
587
+ style={[styles.settingItem, styles.firstSettingItem, styles.lastSettingItem]}
588
+ onPress={() => navigate?.('CreateManagedAccount')}
589
+ activeOpacity={0.7}
590
+ >
591
+ <View style={styles.userIcon}>
592
+ <View style={[styles.accountAvatarFallback, { backgroundColor: '#007AFF20' }]}>
593
+ <OxyIcon name="add" size={20} color="#007AFF" />
594
+ </View>
595
+ </View>
596
+ <View style={styles.settingInfo}>
597
+ <Text style={[styles.settingLabel, { color: '#007AFF' }]}>Create New Identity</Text>
598
+ <Text style={styles.settingDescription}>Create a managed sub-account you control</Text>
599
+ </View>
600
+ </TouchableOpacity>
601
+ </View>
602
+ )}
603
+
431
604
  {/* Quick Actions */}
432
605
  <View style={styles.section}>
433
606
  <Text style={styles.sectionTitle}>Quick Actions</Text>
@@ -729,6 +902,42 @@ const styles = StyleSheet.create({
729
902
  fontSize: 16,
730
903
  fontWeight: '600',
731
904
  },
905
+ sectionSubtitle: {
906
+ fontSize: 13,
907
+ color: '#888',
908
+ marginBottom: 12,
909
+ },
910
+ managedAvatarFallback: {
911
+ backgroundColor: '#5856D6',
912
+ },
913
+ roleBadge: {
914
+ backgroundColor: '#F2F2F7',
915
+ paddingHorizontal: 8,
916
+ paddingVertical: 3,
917
+ borderRadius: 8,
918
+ },
919
+ roleBadgeText: {
920
+ color: '#666',
921
+ fontSize: 11,
922
+ fontWeight: '500',
923
+ textTransform: 'capitalize',
924
+ },
925
+ actingAsBanner: {
926
+ borderWidth: 2,
927
+ borderColor: '#FF9500',
928
+ backgroundColor: '#FF950010',
929
+ },
930
+ switchBackButton: {
931
+ backgroundColor: '#FF9500',
932
+ paddingHorizontal: 14,
933
+ paddingVertical: 8,
934
+ borderRadius: 16,
935
+ },
936
+ switchBackButtonText: {
937
+ color: '#fff',
938
+ fontSize: 13,
939
+ fontWeight: '600',
940
+ },
732
941
  });
733
942
 
734
943
  export default ModernAccountSwitcherScreen;
@@ -0,0 +1,338 @@
1
+ import type React from 'react';
2
+ import { useState, useCallback, useRef, useEffect } from 'react';
3
+ import {
4
+ View,
5
+ Text,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ StyleSheet,
9
+ ActivityIndicator,
10
+ ScrollView,
11
+ Platform,
12
+ KeyboardAvoidingView,
13
+ } from 'react-native';
14
+ import type { BaseScreenProps } from '../types/navigation';
15
+ import { fontFamilies } from '../styles/fonts';
16
+ import { Header } from '../components';
17
+ import { useI18n } from '../hooks/useI18n';
18
+ import { useTheme } from '@oxyhq/bloom/theme';
19
+ import { useOxy } from '../context/OxyContext';
20
+ import { toast } from '../../lib/sonner';
21
+ import { screenContentStyle } from '../constants/spacing';
22
+
23
+ type UsernameStatus = 'idle' | 'checking' | 'available' | 'taken' | 'invalid';
24
+
25
+ const USERNAME_REGEX = /^[a-zA-Z0-9_-]{3,30}$/;
26
+ const DEBOUNCE_MS = 400;
27
+
28
+ const CreateManagedAccountScreen: React.FC<BaseScreenProps> = ({
29
+ onClose,
30
+ goBack,
31
+ }) => {
32
+ const bloomTheme = useTheme();
33
+ const { oxyServices, createManagedAccount, setActingAs } = useOxy();
34
+ const { t } = useI18n();
35
+
36
+ const [username, setUsername] = useState('');
37
+ const [displayName, setDisplayName] = useState('');
38
+ const [bio, setBio] = useState('');
39
+ const [usernameStatus, setUsernameStatus] = useState<UsernameStatus>('idle');
40
+ const [usernameMessage, setUsernameMessage] = useState('');
41
+ const [isCreating, setIsCreating] = useState(false);
42
+
43
+ const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
44
+
45
+ // Debounced username availability check
46
+ const checkUsername = useCallback((value: string) => {
47
+ if (debounceTimerRef.current) {
48
+ clearTimeout(debounceTimerRef.current);
49
+ }
50
+
51
+ if (!value || value.length < 3) {
52
+ setUsernameStatus(value.length > 0 ? 'invalid' : 'idle');
53
+ setUsernameMessage(value.length > 0 ? 'Username must be at least 3 characters' : '');
54
+ return;
55
+ }
56
+
57
+ if (!USERNAME_REGEX.test(value)) {
58
+ setUsernameStatus('invalid');
59
+ setUsernameMessage('Only letters, numbers, hyphens, and underscores');
60
+ return;
61
+ }
62
+
63
+ setUsernameStatus('checking');
64
+ setUsernameMessage('');
65
+
66
+ debounceTimerRef.current = setTimeout(async () => {
67
+ try {
68
+ const result = await oxyServices.checkUsernameAvailability(value);
69
+ setUsernameStatus(result.available ? 'available' : 'taken');
70
+ setUsernameMessage(result.message || (result.available ? 'Username is available' : 'Username is taken'));
71
+ } catch {
72
+ setUsernameStatus('idle');
73
+ setUsernameMessage('Could not check availability');
74
+ }
75
+ }, DEBOUNCE_MS);
76
+ }, [oxyServices]);
77
+
78
+ const handleUsernameChange = useCallback((value: string) => {
79
+ const cleaned = value.toLowerCase().replace(/[^a-z0-9_-]/g, '');
80
+ setUsername(cleaned);
81
+ checkUsername(cleaned);
82
+ }, [checkUsername]);
83
+
84
+ // Cleanup debounce timer
85
+ useEffect(() => {
86
+ return () => {
87
+ if (debounceTimerRef.current) {
88
+ clearTimeout(debounceTimerRef.current);
89
+ }
90
+ };
91
+ }, []);
92
+
93
+ const canCreate = usernameStatus === 'available' && displayName.trim().length > 0 && !isCreating;
94
+
95
+ const handleCreate = useCallback(async () => {
96
+ if (!canCreate) return;
97
+
98
+ setIsCreating(true);
99
+ try {
100
+ // Split display name into first/last
101
+ const nameParts = displayName.trim().split(/\s+/);
102
+ const firstName = nameParts[0] || '';
103
+ const lastName = nameParts.length > 1 ? nameParts.slice(1).join(' ') : undefined;
104
+
105
+ const account = await createManagedAccount({
106
+ username,
107
+ name: { first: firstName, last: lastName },
108
+ bio: bio.trim() || undefined,
109
+ });
110
+
111
+ toast.success('Identity created successfully');
112
+
113
+ // Switch to the new managed account
114
+ if (account.accountId) {
115
+ setActingAs(account.accountId);
116
+ }
117
+
118
+ onClose?.();
119
+ } catch (error) {
120
+ const message = error instanceof Error ? error.message : 'Failed to create identity';
121
+ toast.error(message);
122
+ } finally {
123
+ setIsCreating(false);
124
+ }
125
+ }, [canCreate, username, displayName, bio, createManagedAccount, setActingAs, onClose]);
126
+
127
+ const getStatusColor = (): string => {
128
+ switch (usernameStatus) {
129
+ case 'available': return '#34C759';
130
+ case 'taken':
131
+ case 'invalid': return bloomTheme.colors.error;
132
+ case 'checking': return bloomTheme.colors.primary;
133
+ default: return 'transparent';
134
+ }
135
+ };
136
+
137
+ return (
138
+ <View style={[styles.container, { backgroundColor: bloomTheme.colors.background }]}>
139
+ <Header
140
+ title="Create Identity"
141
+ onBack={goBack}
142
+ onClose={onClose}
143
+ showBackButton={true}
144
+ showCloseButton={true}
145
+ elevation="subtle"
146
+ />
147
+
148
+ <KeyboardAvoidingView
149
+ style={styles.flex}
150
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
151
+ keyboardVerticalOffset={Platform.OS === 'ios' ? 88 : 0}
152
+ >
153
+ <ScrollView
154
+ style={styles.flex}
155
+ contentContainerStyle={screenContentStyle}
156
+ keyboardShouldPersistTaps="handled"
157
+ >
158
+ <Text style={[styles.description, { color: bloomTheme.colors.text }]}>
159
+ Create a managed identity that you control. It will have its own profile, posts, and interactions.
160
+ </Text>
161
+
162
+ {/* Username */}
163
+ <View style={styles.fieldGroup}>
164
+ <Text style={[styles.label, { color: bloomTheme.colors.text }]}>Username</Text>
165
+ <View style={[
166
+ styles.inputContainer,
167
+ { borderColor: usernameStatus !== 'idle' ? getStatusColor() : bloomTheme.colors.border },
168
+ ]}>
169
+ <Text style={[styles.inputPrefix, { color: bloomTheme.colors.text + '80' }]}>@</Text>
170
+ <TextInput
171
+ style={[styles.input, { color: bloomTheme.colors.text }]}
172
+ value={username}
173
+ onChangeText={handleUsernameChange}
174
+ placeholder="username"
175
+ placeholderTextColor={bloomTheme.colors.text + '40'}
176
+ autoCapitalize="none"
177
+ autoCorrect={false}
178
+ autoComplete="off"
179
+ maxLength={30}
180
+ />
181
+ {usernameStatus === 'checking' && (
182
+ <ActivityIndicator size="small" color={bloomTheme.colors.primary} />
183
+ )}
184
+ {usernameStatus === 'available' && (
185
+ <Text style={styles.statusIcon}>OK</Text>
186
+ )}
187
+ </View>
188
+ {usernameMessage ? (
189
+ <Text style={[styles.statusMessage, { color: getStatusColor() }]}>
190
+ {usernameMessage}
191
+ </Text>
192
+ ) : null}
193
+ </View>
194
+
195
+ {/* Display Name */}
196
+ <View style={styles.fieldGroup}>
197
+ <Text style={[styles.label, { color: bloomTheme.colors.text }]}>Display Name</Text>
198
+ <View style={[styles.inputContainer, { borderColor: bloomTheme.colors.border }]}>
199
+ <TextInput
200
+ style={[styles.input, { color: bloomTheme.colors.text }]}
201
+ value={displayName}
202
+ onChangeText={setDisplayName}
203
+ placeholder="Display name"
204
+ placeholderTextColor={bloomTheme.colors.text + '40'}
205
+ maxLength={50}
206
+ />
207
+ </View>
208
+ </View>
209
+
210
+ {/* Bio */}
211
+ <View style={styles.fieldGroup}>
212
+ <Text style={[styles.label, { color: bloomTheme.colors.text }]}>Bio (optional)</Text>
213
+ <View style={[styles.inputContainer, styles.bioContainer, { borderColor: bloomTheme.colors.border }]}>
214
+ <TextInput
215
+ style={[styles.input, styles.bioInput, { color: bloomTheme.colors.text }]}
216
+ value={bio}
217
+ onChangeText={setBio}
218
+ placeholder="Tell people about this identity"
219
+ placeholderTextColor={bloomTheme.colors.text + '40'}
220
+ maxLength={160}
221
+ multiline
222
+ numberOfLines={3}
223
+ textAlignVertical="top"
224
+ />
225
+ </View>
226
+ <Text style={[styles.charCount, { color: bloomTheme.colors.text + '60' }]}>
227
+ {bio.length}/160
228
+ </Text>
229
+ </View>
230
+
231
+ {/* Create Button */}
232
+ <TouchableOpacity
233
+ style={[
234
+ styles.createButton,
235
+ { backgroundColor: bloomTheme.colors.primary },
236
+ !canCreate && styles.createButtonDisabled,
237
+ ]}
238
+ onPress={handleCreate}
239
+ disabled={!canCreate}
240
+ activeOpacity={0.7}
241
+ >
242
+ {isCreating ? (
243
+ <ActivityIndicator size="small" color="#fff" />
244
+ ) : (
245
+ <Text style={styles.createButtonText}>Create Identity</Text>
246
+ )}
247
+ </TouchableOpacity>
248
+ </ScrollView>
249
+ </KeyboardAvoidingView>
250
+ </View>
251
+ );
252
+ };
253
+
254
+ const styles = StyleSheet.create({
255
+ container: {
256
+ flex: 1,
257
+ },
258
+ flex: {
259
+ flex: 1,
260
+ },
261
+ description: {
262
+ fontSize: 15,
263
+ lineHeight: 22,
264
+ marginBottom: 24,
265
+ fontFamily: fontFamilies.inter,
266
+ },
267
+ fieldGroup: {
268
+ marginBottom: 20,
269
+ },
270
+ label: {
271
+ fontSize: 14,
272
+ fontFamily: fontFamilies.interSemiBold,
273
+ fontWeight: Platform.OS === 'web' ? '600' : undefined,
274
+ marginBottom: 8,
275
+ },
276
+ inputContainer: {
277
+ flexDirection: 'row',
278
+ alignItems: 'center',
279
+ borderWidth: 1,
280
+ borderRadius: 12,
281
+ paddingHorizontal: 14,
282
+ height: 48,
283
+ },
284
+ inputPrefix: {
285
+ fontSize: 16,
286
+ fontFamily: fontFamilies.inter,
287
+ marginRight: 2,
288
+ },
289
+ input: {
290
+ flex: 1,
291
+ fontSize: 16,
292
+ fontFamily: fontFamilies.inter,
293
+ paddingVertical: 0,
294
+ },
295
+ bioContainer: {
296
+ height: 88,
297
+ alignItems: 'flex-start',
298
+ paddingVertical: 12,
299
+ },
300
+ bioInput: {
301
+ height: 64,
302
+ },
303
+ statusIcon: {
304
+ fontSize: 13,
305
+ fontFamily: fontFamilies.interSemiBold,
306
+ fontWeight: Platform.OS === 'web' ? '600' : undefined,
307
+ color: '#34C759',
308
+ },
309
+ statusMessage: {
310
+ fontSize: 13,
311
+ fontFamily: fontFamilies.inter,
312
+ marginTop: 6,
313
+ },
314
+ charCount: {
315
+ fontSize: 12,
316
+ fontFamily: fontFamilies.inter,
317
+ textAlign: 'right',
318
+ marginTop: 4,
319
+ },
320
+ createButton: {
321
+ height: 50,
322
+ borderRadius: 25,
323
+ alignItems: 'center',
324
+ justifyContent: 'center',
325
+ marginTop: 12,
326
+ },
327
+ createButtonDisabled: {
328
+ opacity: 0.5,
329
+ },
330
+ createButtonText: {
331
+ color: '#fff',
332
+ fontSize: 16,
333
+ fontFamily: fontFamilies.interSemiBold,
334
+ fontWeight: Platform.OS === 'web' ? '600' : undefined,
335
+ },
336
+ });
337
+
338
+ export default CreateManagedAccountScreen;