@oxyhq/services 5.13.1 → 5.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -0
- package/lib/commonjs/core/HttpClient.js +238 -0
- package/lib/commonjs/core/HttpClient.js.map +1 -0
- package/lib/commonjs/core/OxyServices.js +538 -332
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/core/RequestManager.js +199 -0
- package/lib/commonjs/core/RequestManager.js.map +1 -0
- package/lib/commonjs/core/index.js +38 -1
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/index.js +36 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/Avatar.js +94 -27
- package/lib/commonjs/ui/components/Avatar.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +1 -0
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/internal/TextField.js +13 -8
- package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +183 -224
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +80 -22
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/commonjs/ui/index.js +4 -1
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +32 -2
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +101 -59
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -2
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +75 -117
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +0 -11
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignUpScreen.js +14 -16
- package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +50 -18
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +10 -10
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +16 -26
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +104 -212
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/stores/accountStore.js +237 -0
- package/lib/commonjs/ui/stores/accountStore.js.map +1 -0
- package/lib/commonjs/ui/stores/authStore.js +2 -1
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/commonjs/ui/styles/authStyles.js +14 -7
- package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
- package/lib/commonjs/utils/asyncUtils.js +9 -22
- package/lib/commonjs/utils/asyncUtils.js.map +1 -1
- package/lib/commonjs/utils/cache.js +259 -0
- package/lib/commonjs/utils/cache.js.map +1 -0
- package/lib/commonjs/utils/index.js +99 -0
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/commonjs/utils/languageUtils.js +159 -0
- package/lib/commonjs/utils/languageUtils.js.map +1 -0
- package/lib/commonjs/utils/requestUtils.js +217 -0
- package/lib/commonjs/utils/requestUtils.js.map +1 -0
- package/lib/commonjs/utils/sessionUtils.js +191 -0
- package/lib/commonjs/utils/sessionUtils.js.map +1 -0
- package/lib/module/core/HttpClient.js +232 -0
- package/lib/module/core/HttpClient.js.map +1 -0
- package/lib/module/core/OxyServices.js +536 -326
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/core/RequestManager.js +194 -0
- package/lib/module/core/RequestManager.js.map +1 -0
- package/lib/module/core/index.js +2 -0
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/Avatar.js +94 -27
- package/lib/module/ui/components/Avatar.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +1 -0
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/internal/TextField.js +13 -8
- package/lib/module/ui/components/internal/TextField.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +182 -223
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useSessionSocket.js +80 -22
- package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/module/ui/index.js +4 -2
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +33 -2
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +102 -60
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +3 -2
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/LanguageSelectorScreen.js +73 -117
- package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +0 -11
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/SignUpScreen.js +14 -16
- package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +50 -18
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInPasswordStep.js +10 -10
- package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInPasswordStep.js +16 -26
- package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInUsernameStep.js +105 -214
- package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/stores/accountStore.js +229 -0
- package/lib/module/ui/stores/accountStore.js.map +1 -0
- package/lib/module/ui/stores/authStore.js +2 -1
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/module/ui/styles/authStyles.js +14 -7
- package/lib/module/ui/styles/authStyles.js.map +1 -1
- package/lib/module/utils/asyncUtils.js +10 -22
- package/lib/module/utils/asyncUtils.js.map +1 -1
- package/lib/module/utils/cache.js +250 -0
- package/lib/module/utils/cache.js.map +1 -0
- package/lib/module/utils/index.js +7 -0
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/languageUtils.js +151 -0
- package/lib/module/utils/languageUtils.js.map +1 -0
- package/lib/module/utils/requestUtils.js +210 -0
- package/lib/module/utils/requestUtils.js.map +1 -0
- package/lib/module/utils/sessionUtils.js +180 -0
- package/lib/module/utils/sessionUtils.js.map +1 -0
- package/lib/typescript/core/HttpClient.d.ts +64 -0
- package/lib/typescript/core/HttpClient.d.ts.map +1 -0
- package/lib/typescript/core/OxyServices.d.ts +88 -71
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/core/RequestManager.d.ts +67 -0
- package/lib/typescript/core/RequestManager.d.ts.map +1 -0
- package/lib/typescript/core/index.d.ts +2 -0
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +15 -0
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/models/session.d.ts +1 -0
- package/lib/typescript/models/session.d.ts.map +1 -1
- package/lib/typescript/ui/components/Avatar.d.ts +6 -7
- package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +4 -0
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
- package/lib/typescript/ui/index.d.ts +2 -2
- package/lib/typescript/ui/index.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +3 -3
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
- package/lib/typescript/ui/stores/accountStore.d.ts +34 -0
- package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -0
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/ui/styles/authStyles.d.ts +18 -2
- package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
- package/lib/typescript/utils/asyncUtils.d.ts +2 -0
- package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
- package/lib/typescript/utils/cache.d.ts +128 -0
- package/lib/typescript/utils/cache.d.ts.map +1 -0
- package/lib/typescript/utils/index.d.ts +4 -0
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/lib/typescript/utils/languageUtils.d.ts +38 -0
- package/lib/typescript/utils/languageUtils.d.ts.map +1 -0
- package/lib/typescript/utils/requestUtils.d.ts +122 -0
- package/lib/typescript/utils/requestUtils.d.ts.map +1 -0
- package/lib/typescript/utils/sessionUtils.d.ts +55 -0
- package/lib/typescript/utils/sessionUtils.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/core/HttpClient.ts +277 -0
- package/src/core/OxyServices.ts +466 -351
- package/src/core/RequestManager.ts +240 -0
- package/src/core/index.ts +10 -0
- package/src/index.ts +10 -0
- package/src/models/interfaces.ts +19 -0
- package/src/models/session.ts +1 -1
- package/src/ui/components/Avatar.tsx +151 -35
- package/src/ui/components/FollowButton.tsx +1 -0
- package/src/ui/components/internal/TextField.tsx +7 -6
- package/src/ui/context/OxyContext.tsx +213 -217
- package/src/ui/hooks/useSessionSocket.ts +72 -18
- package/src/ui/index.ts +4 -1
- package/src/ui/screens/AccountSettingsScreen.tsx +34 -2
- package/src/ui/screens/AccountSwitcherScreen.tsx +102 -68
- package/src/ui/screens/FileManagementScreen.tsx +1 -1
- package/src/ui/screens/LanguageSelectorScreen.tsx +86 -143
- package/src/ui/screens/SignInScreen.tsx +0 -7
- package/src/ui/screens/SignUpScreen.tsx +14 -15
- package/src/ui/screens/WelcomeNewUserScreen.tsx +52 -15
- package/src/ui/screens/internal/SignInPasswordStep.tsx +4 -6
- package/src/ui/screens/steps/SignInPasswordStep.tsx +4 -8
- package/src/ui/screens/steps/SignInUsernameStep.tsx +110 -256
- package/src/ui/stores/accountStore.ts +285 -0
- package/src/ui/stores/authStore.ts +2 -1
- package/src/ui/styles/authStyles.ts +14 -7
- package/src/utils/asyncUtils.ts +10 -24
- package/src/utils/cache.ts +264 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/languageUtils.ts +174 -0
- package/src/utils/requestUtils.ts +234 -0
- package/src/utils/sessionUtils.ts +206 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { useState } from 'react';
|
|
1
|
+
import React, { useState, useMemo, useCallback } from 'react';
|
|
3
2
|
import {
|
|
4
3
|
View,
|
|
5
4
|
Text,
|
|
@@ -15,101 +14,18 @@ import { Ionicons } from '@expo/vector-icons';
|
|
|
15
14
|
import { toast } from '../../lib/sonner';
|
|
16
15
|
import { Header, Section, GroupedSection } from '../components';
|
|
17
16
|
import { useI18n } from '../hooks/useI18n';
|
|
18
|
-
|
|
19
|
-
// Supported languages with their metadata
|
|
20
|
-
const SUPPORTED_LANGUAGES = [
|
|
21
|
-
{
|
|
22
|
-
id: 'en-US',
|
|
23
|
-
name: 'English',
|
|
24
|
-
nativeName: 'English',
|
|
25
|
-
flag: '🇺🇸',
|
|
26
|
-
icon: 'language-outline',
|
|
27
|
-
color: '#007AFF',
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: 'es-ES',
|
|
31
|
-
name: 'Spanish',
|
|
32
|
-
nativeName: 'Español',
|
|
33
|
-
flag: '🇪🇸',
|
|
34
|
-
icon: 'language-outline',
|
|
35
|
-
color: '#FF3B30',
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
id: 'ca-ES',
|
|
39
|
-
name: 'Catalan',
|
|
40
|
-
nativeName: 'Català',
|
|
41
|
-
flag: '🇪🇸',
|
|
42
|
-
icon: 'language-outline',
|
|
43
|
-
color: '#0CA678',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
id: 'fr-FR',
|
|
47
|
-
name: 'French',
|
|
48
|
-
nativeName: 'Français',
|
|
49
|
-
flag: '🇫🇷',
|
|
50
|
-
icon: 'language-outline',
|
|
51
|
-
color: '#5856D6',
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
id: 'de-DE',
|
|
55
|
-
name: 'German',
|
|
56
|
-
nativeName: 'Deutsch',
|
|
57
|
-
flag: '🇩🇪',
|
|
58
|
-
icon: 'language-outline',
|
|
59
|
-
color: '#FF9500',
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
id: 'it-IT',
|
|
63
|
-
name: 'Italian',
|
|
64
|
-
nativeName: 'Italiano',
|
|
65
|
-
flag: '🇮🇹',
|
|
66
|
-
icon: 'language-outline',
|
|
67
|
-
color: '#34C759',
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
id: 'pt-PT',
|
|
71
|
-
name: 'Portuguese',
|
|
72
|
-
nativeName: 'Português',
|
|
73
|
-
flag: '🇵🇹',
|
|
74
|
-
icon: 'language-outline',
|
|
75
|
-
color: '#AF52DE',
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
id: 'ja-JP',
|
|
79
|
-
name: 'Japanese',
|
|
80
|
-
nativeName: '日本語',
|
|
81
|
-
flag: '🇯🇵',
|
|
82
|
-
icon: 'language-outline',
|
|
83
|
-
color: '#FF2D92',
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
id: 'ko-KR',
|
|
87
|
-
name: 'Korean',
|
|
88
|
-
nativeName: '한국어',
|
|
89
|
-
flag: '🇰🇷',
|
|
90
|
-
icon: 'language-outline',
|
|
91
|
-
color: '#32D74B',
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
id: 'zh-CN',
|
|
95
|
-
name: 'Chinese',
|
|
96
|
-
nativeName: '中文',
|
|
97
|
-
flag: '🇨🇳',
|
|
98
|
-
icon: 'language-outline',
|
|
99
|
-
color: '#FF9F0A',
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
id: 'ar-SA',
|
|
103
|
-
name: 'Arabic',
|
|
104
|
-
nativeName: 'العربية',
|
|
105
|
-
flag: '🇸🇦',
|
|
106
|
-
icon: 'language-outline',
|
|
107
|
-
color: '#30B0C7',
|
|
108
|
-
},
|
|
109
|
-
];
|
|
17
|
+
import { SUPPORTED_LANGUAGES } from '../../utils/languageUtils';
|
|
110
18
|
|
|
111
19
|
interface LanguageSelectorScreenProps extends BaseScreenProps { }
|
|
112
20
|
|
|
21
|
+
/**
|
|
22
|
+
* LanguageSelectorScreen - Optimized for performance
|
|
23
|
+
*
|
|
24
|
+
* Performance optimizations:
|
|
25
|
+
* - useMemo for language items to prevent recreation on every render
|
|
26
|
+
* - useCallback for handlers to prevent unnecessary re-renders
|
|
27
|
+
* - Memoized current language section
|
|
28
|
+
*/
|
|
113
29
|
const LanguageSelectorScreen: React.FC<LanguageSelectorScreenProps> = ({
|
|
114
30
|
goBack,
|
|
115
31
|
onClose,
|
|
@@ -121,20 +37,27 @@ const LanguageSelectorScreen: React.FC<LanguageSelectorScreenProps> = ({
|
|
|
121
37
|
const colors = useThemeColors(theme);
|
|
122
38
|
const [isLoading, setIsLoading] = useState(false);
|
|
123
39
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
40
|
+
// Memoize the language select handler to prevent recreation on every render
|
|
41
|
+
const handleLanguageSelect = useCallback(async (languageId: string) => {
|
|
42
|
+
if (languageId === currentLanguage || isLoading) {
|
|
43
|
+
return; // Already selected or loading
|
|
127
44
|
}
|
|
128
45
|
|
|
129
46
|
setIsLoading(true);
|
|
130
47
|
|
|
131
48
|
try {
|
|
49
|
+
let serverSyncFailed = false;
|
|
50
|
+
|
|
132
51
|
// If signed in, persist preference to backend user settings
|
|
133
52
|
if (isAuthenticated && user?.id) {
|
|
134
53
|
try {
|
|
135
54
|
await oxyServices.updateProfile({ language: languageId });
|
|
136
55
|
} catch (e: any) {
|
|
137
|
-
|
|
56
|
+
// Server sync failed, but we'll save locally anyway
|
|
57
|
+
serverSyncFailed = true;
|
|
58
|
+
if (__DEV__) {
|
|
59
|
+
console.warn('Failed to sync language to server (will save locally only):', e?.message || e);
|
|
60
|
+
}
|
|
138
61
|
}
|
|
139
62
|
}
|
|
140
63
|
|
|
@@ -142,34 +65,71 @@ const LanguageSelectorScreen: React.FC<LanguageSelectorScreenProps> = ({
|
|
|
142
65
|
await setLanguage(languageId);
|
|
143
66
|
|
|
144
67
|
const selectedLang = SUPPORTED_LANGUAGES.find(lang => lang.id === languageId);
|
|
68
|
+
|
|
69
|
+
// Show success message (language is saved locally regardless of server sync)
|
|
145
70
|
toast.success(t('language.changed', { lang: selectedLang?.name || languageId }));
|
|
71
|
+
|
|
72
|
+
// Log server sync failure only in dev mode (user experience is still good - saved locally)
|
|
73
|
+
if (serverSyncFailed && __DEV__) {
|
|
74
|
+
console.warn('Language saved locally but server sync failed');
|
|
75
|
+
}
|
|
146
76
|
|
|
147
77
|
setIsLoading(false);
|
|
148
78
|
// Close the bottom sheet if possible; otherwise, go back
|
|
149
79
|
if (onClose) onClose(); else goBack();
|
|
150
80
|
|
|
151
81
|
} catch (error) {
|
|
82
|
+
// Only show error if local storage also failed
|
|
152
83
|
console.error('Error saving language preference:', error);
|
|
153
84
|
toast.error('Failed to save language preference');
|
|
154
85
|
setIsLoading(false);
|
|
155
86
|
}
|
|
156
|
-
};
|
|
87
|
+
}, [currentLanguage, isLoading, isAuthenticated, user?.id, oxyServices, setLanguage, t, onClose, goBack]);
|
|
88
|
+
|
|
89
|
+
// Memoize language items to prevent recreation on every render
|
|
90
|
+
const languageItems = useMemo(() =>
|
|
91
|
+
SUPPORTED_LANGUAGES.map(language => ({
|
|
92
|
+
id: language.id,
|
|
93
|
+
title: language.name,
|
|
94
|
+
subtitle: language.nativeName,
|
|
95
|
+
customIcon: (
|
|
96
|
+
<View style={[styles.languageFlag, { backgroundColor: `${language.color}20` }]}>
|
|
97
|
+
<Text style={styles.flagEmoji}>{language.flag}</Text>
|
|
98
|
+
</View>
|
|
99
|
+
),
|
|
100
|
+
iconColor: language.color,
|
|
101
|
+
selected: currentLanguage === language.id,
|
|
102
|
+
onPress: () => handleLanguageSelect(language.id),
|
|
103
|
+
dense: true,
|
|
104
|
+
})),
|
|
105
|
+
[currentLanguage, handleLanguageSelect]
|
|
106
|
+
);
|
|
157
107
|
|
|
158
|
-
//
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
108
|
+
// Memoize current language data to prevent recalculation
|
|
109
|
+
const currentLanguageData = useMemo(() => {
|
|
110
|
+
if (!currentLanguage) return null;
|
|
111
|
+
return SUPPORTED_LANGUAGES.find(lang => lang.id === currentLanguage);
|
|
112
|
+
}, [currentLanguage]);
|
|
113
|
+
|
|
114
|
+
// Memoize current language section items
|
|
115
|
+
const currentLanguageItems = useMemo(() => {
|
|
116
|
+
if (!currentLanguageData) return [];
|
|
117
|
+
return [{
|
|
118
|
+
id: `current-${currentLanguageData.id}`,
|
|
119
|
+
title: currentLanguageData.name,
|
|
120
|
+
subtitle: currentLanguageData.nativeName,
|
|
121
|
+
customIcon: (
|
|
122
|
+
<View style={[styles.languageFlag, { backgroundColor: `${currentLanguageData.color}20` }]}>
|
|
123
|
+
<Text style={styles.flagEmoji}>{currentLanguageData.flag}</Text>
|
|
124
|
+
</View>
|
|
125
|
+
),
|
|
126
|
+
iconColor: currentLanguageData.color,
|
|
127
|
+
selected: false,
|
|
128
|
+
showChevron: false,
|
|
129
|
+
dense: true,
|
|
130
|
+
disabled: true,
|
|
131
|
+
}];
|
|
132
|
+
}, [currentLanguageData]);
|
|
173
133
|
|
|
174
134
|
return (
|
|
175
135
|
<View style={[styles.container, { backgroundColor: '#f2f2f2' }]}>
|
|
@@ -182,36 +142,18 @@ const LanguageSelectorScreen: React.FC<LanguageSelectorScreenProps> = ({
|
|
|
182
142
|
elevation="subtle"
|
|
183
143
|
/>
|
|
184
144
|
|
|
185
|
-
<ScrollView
|
|
145
|
+
<ScrollView
|
|
146
|
+
style={styles.content}
|
|
147
|
+
showsVerticalScrollIndicator={false}
|
|
148
|
+
removeClippedSubviews={true}
|
|
149
|
+
>
|
|
186
150
|
{/* Current selection */}
|
|
187
|
-
{currentLanguage && (
|
|
151
|
+
{currentLanguage && currentLanguageItems.length > 0 && (
|
|
188
152
|
<Section title={t('language.current')} theme={theme} isFirst={true}>
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
<GroupedSection
|
|
194
|
-
items={[
|
|
195
|
-
{
|
|
196
|
-
id: `current-${current.id}`,
|
|
197
|
-
title: current.name,
|
|
198
|
-
subtitle: current.nativeName,
|
|
199
|
-
customIcon: (
|
|
200
|
-
<View style={[styles.languageFlag, { backgroundColor: `${current.color}20` }]}>
|
|
201
|
-
<Text style={styles.flagEmoji}>{current.flag}</Text>
|
|
202
|
-
</View>
|
|
203
|
-
),
|
|
204
|
-
iconColor: current.color,
|
|
205
|
-
selected: false,
|
|
206
|
-
showChevron: false,
|
|
207
|
-
dense: true,
|
|
208
|
-
disabled: true,
|
|
209
|
-
},
|
|
210
|
-
]}
|
|
211
|
-
theme={theme}
|
|
212
|
-
/>
|
|
213
|
-
);
|
|
214
|
-
})()}
|
|
153
|
+
<GroupedSection
|
|
154
|
+
items={currentLanguageItems}
|
|
155
|
+
theme={theme}
|
|
156
|
+
/>
|
|
215
157
|
</Section>
|
|
216
158
|
)}
|
|
217
159
|
|
|
@@ -348,4 +290,5 @@ const styles = StyleSheet.create({
|
|
|
348
290
|
},
|
|
349
291
|
});
|
|
350
292
|
|
|
351
|
-
|
|
293
|
+
// Export memoized component to prevent unnecessary re-renders
|
|
294
|
+
export default React.memo(LanguageSelectorScreen);
|
|
@@ -32,13 +32,6 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
32
32
|
const [existingSession, setExistingSession] = useState<any>(null);
|
|
33
33
|
|
|
34
34
|
const { login, completeMfaLogin, isLoading, user, isAuthenticated, sessions, oxyServices, switchSession } = useOxy();
|
|
35
|
-
|
|
36
|
-
// Only log props in development mode to reduce console noise
|
|
37
|
-
if (__DEV__) {
|
|
38
|
-
console.log('SignInScreen props:', { initialStep, initialUsername, initialUserProfile });
|
|
39
|
-
console.log('🔧 oxyServices available:', !!oxyServices);
|
|
40
|
-
console.log('🔧 getProfileByUsername available:', typeof oxyServices?.getProfileByUsername);
|
|
41
|
-
}
|
|
42
35
|
const colors = useThemeColors(theme);
|
|
43
36
|
|
|
44
37
|
// Check if this should be treated as "Add Account" mode
|
|
@@ -9,6 +9,7 @@ import SignUpWelcomeStep from './steps/SignUpWelcomeStep';
|
|
|
9
9
|
import SignUpIdentityStep from './steps/SignUpIdentityStep';
|
|
10
10
|
import SignUpSecurityStep from './steps/SignUpSecurityStep';
|
|
11
11
|
import SignUpSummaryStep from './steps/SignUpSummaryStep';
|
|
12
|
+
import { TTLCache, registerCacheForCleanup } from '../../utils/cache';
|
|
12
13
|
|
|
13
14
|
// Types for better type safety
|
|
14
15
|
interface ValidationState {
|
|
@@ -51,8 +52,16 @@ const SignUpScreen: React.FC<BaseScreenProps> = ({
|
|
|
51
52
|
// Error message state
|
|
52
53
|
const [errorMessage, setErrorMessage] = useState('');
|
|
53
54
|
|
|
54
|
-
// Username validation with caching
|
|
55
|
-
const usernameCache = useRef<
|
|
55
|
+
// Username validation with caching - uses centralized cache
|
|
56
|
+
const usernameCache = useRef(new TTLCache<boolean>(5 * 60 * 1000)); // 5 minutes cache
|
|
57
|
+
|
|
58
|
+
// Register cache for cleanup on mount
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
registerCacheForCleanup(usernameCache.current);
|
|
61
|
+
return () => {
|
|
62
|
+
usernameCache.current.clear();
|
|
63
|
+
};
|
|
64
|
+
}, []);
|
|
56
65
|
|
|
57
66
|
const validateUsername = useCallback(async (usernameToValidate: string): Promise<boolean> => {
|
|
58
67
|
if (!usernameToValidate || usernameToValidate.length < USERNAME_MIN_LENGTH) {
|
|
@@ -62,9 +71,8 @@ const SignUpScreen: React.FC<BaseScreenProps> = ({
|
|
|
62
71
|
|
|
63
72
|
// Check cache first
|
|
64
73
|
const cached = usernameCache.current.get(usernameToValidate);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const isValid = cached.available;
|
|
74
|
+
if (cached !== null) {
|
|
75
|
+
const isValid = cached;
|
|
68
76
|
setValidationState({
|
|
69
77
|
status: isValid ? 'valid' : 'invalid',
|
|
70
78
|
message: isValid ? '' : 'Username is already taken'
|
|
@@ -79,10 +87,7 @@ const SignUpScreen: React.FC<BaseScreenProps> = ({
|
|
|
79
87
|
const isValid = result.available;
|
|
80
88
|
|
|
81
89
|
// Cache the result
|
|
82
|
-
usernameCache.current.set(usernameToValidate,
|
|
83
|
-
available: isValid,
|
|
84
|
-
timestamp: now
|
|
85
|
-
});
|
|
90
|
+
usernameCache.current.set(usernameToValidate, isValid);
|
|
86
91
|
|
|
87
92
|
setValidationState({
|
|
88
93
|
status: isValid ? 'valid' : 'invalid',
|
|
@@ -146,12 +151,6 @@ const SignUpScreen: React.FC<BaseScreenProps> = ({
|
|
|
146
151
|
}
|
|
147
152
|
}, [username, email, password, confirmPassword, validateEmail, validatePassword, signUp, navigate]);
|
|
148
153
|
|
|
149
|
-
// Cleanup cache on unmount
|
|
150
|
-
useEffect(() => {
|
|
151
|
-
return () => {
|
|
152
|
-
usernameCache.current.clear();
|
|
153
|
-
};
|
|
154
|
-
}, []);
|
|
155
154
|
|
|
156
155
|
// Step configurations
|
|
157
156
|
const steps: StepConfig[] = useMemo(() => [
|
|
@@ -69,8 +69,19 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
|
|
|
69
69
|
const fadeAnim = useRef(new Animated.Value(1)).current;
|
|
70
70
|
const slideAnim = useRef(new Animated.Value(0)).current;
|
|
71
71
|
const [currentStep, setCurrentStep] = useState(0);
|
|
72
|
+
// Track avatar separately to ensure it updates immediately after selection
|
|
73
|
+
const [selectedAvatarId, setSelectedAvatarId] = useState<string | undefined>(currentUser?.avatar);
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
// Update selectedAvatarId when user changes
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (user?.avatar) {
|
|
78
|
+
setSelectedAvatarId(user.avatar);
|
|
79
|
+
} else if (newUser?.avatar) {
|
|
80
|
+
setSelectedAvatarId(newUser.avatar);
|
|
81
|
+
}
|
|
82
|
+
}, [user?.avatar, newUser?.avatar]);
|
|
83
|
+
|
|
84
|
+
const avatarUri = selectedAvatarId ? oxyServices.getFileDownloadUrl(selectedAvatarId, 'thumb') : undefined;
|
|
74
85
|
|
|
75
86
|
// Steps content
|
|
76
87
|
const welcomeTitle = currentUser?.username
|
|
@@ -100,7 +111,7 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
|
|
|
100
111
|
const totalSteps = steps.length;
|
|
101
112
|
const avatarStepIndex = steps.findIndex(s => s.showAvatar);
|
|
102
113
|
|
|
103
|
-
const
|
|
114
|
+
const animateToStepCallback = useCallback((next: number) => {
|
|
104
115
|
Animated.timing(fadeAnim, { toValue: 0, duration: 180, useNativeDriver: Platform.OS !== 'web' }).start(() => {
|
|
105
116
|
setCurrentStep(next);
|
|
106
117
|
slideAnim.setValue(-40);
|
|
@@ -109,40 +120,59 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
|
|
|
109
120
|
Animated.spring(slideAnim, { toValue: 0, useNativeDriver: Platform.OS !== 'web', friction: 9 })
|
|
110
121
|
]).start();
|
|
111
122
|
});
|
|
112
|
-
};
|
|
123
|
+
}, [fadeAnim, slideAnim]);
|
|
113
124
|
|
|
114
|
-
const nextStep = useCallback(() => { if (currentStep < totalSteps - 1)
|
|
115
|
-
const prevStep = useCallback(() => { if (currentStep > 0)
|
|
116
|
-
const skipToAvatar = useCallback(() => { if (avatarStepIndex >= 0)
|
|
125
|
+
const nextStep = useCallback(() => { if (currentStep < totalSteps - 1) animateToStepCallback(currentStep + 1); }, [currentStep, totalSteps, animateToStepCallback]);
|
|
126
|
+
const prevStep = useCallback(() => { if (currentStep > 0) animateToStepCallback(currentStep - 1); }, [currentStep, animateToStepCallback]);
|
|
127
|
+
const skipToAvatar = useCallback(() => { if (avatarStepIndex >= 0) animateToStepCallback(avatarStepIndex); }, [avatarStepIndex, animateToStepCallback]);
|
|
117
128
|
const finish = useCallback(() => { if (onAuthenticated && currentUser) onAuthenticated(currentUser); }, [onAuthenticated, currentUser]);
|
|
118
129
|
const openAvatarPicker = useCallback(() => {
|
|
130
|
+
// Ensure we're on the avatar step before opening picker
|
|
131
|
+
if (avatarStepIndex >= 0 && currentStep !== avatarStepIndex) {
|
|
132
|
+
animateToStepCallback(avatarStepIndex);
|
|
133
|
+
}
|
|
134
|
+
|
|
119
135
|
navigate('FileManagement', {
|
|
120
136
|
selectMode: true,
|
|
121
137
|
multiSelect: false,
|
|
122
138
|
disabledMimeTypes: ['video/', 'audio/', 'application/pdf'],
|
|
123
|
-
afterSelect: '
|
|
139
|
+
afterSelect: 'none', // Don't navigate away - stay on current screen
|
|
124
140
|
onSelect: async (file: any) => {
|
|
125
141
|
if (!file.contentType.startsWith('image/')) {
|
|
126
142
|
toast.error(t('editProfile.toasts.selectImage') || 'Please select an image file');
|
|
127
143
|
return;
|
|
128
144
|
}
|
|
129
145
|
try {
|
|
130
|
-
// Update file visibility to public for avatar
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
146
|
+
// Update file visibility to public for avatar (skip if temporary asset ID)
|
|
147
|
+
if (file.id && !file.id.startsWith('temp-')) {
|
|
148
|
+
try {
|
|
149
|
+
await oxyServices.assetUpdateVisibility(file.id, 'public');
|
|
150
|
+
console.log('[WelcomeNewUser] Avatar visibility updated to public');
|
|
151
|
+
} catch (visError: any) {
|
|
152
|
+
// Only log non-404 errors (404 means asset doesn't exist yet, which is OK)
|
|
153
|
+
if (visError?.response?.status !== 404) {
|
|
154
|
+
console.warn('[WelcomeNewUser] Failed to update avatar visibility, continuing anyway:', visError);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
136
157
|
}
|
|
137
158
|
|
|
159
|
+
// Update the avatar immediately in local state
|
|
160
|
+
setSelectedAvatarId(file.id);
|
|
161
|
+
|
|
162
|
+
// Update user in store
|
|
138
163
|
await updateUser({ avatar: file.id }, oxyServices);
|
|
139
164
|
toast.success(t('editProfile.toasts.avatarUpdated') || 'Avatar updated');
|
|
165
|
+
|
|
166
|
+
// Ensure we stay on the avatar step
|
|
167
|
+
if (avatarStepIndex >= 0 && currentStep !== avatarStepIndex) {
|
|
168
|
+
animateToStepCallback(avatarStepIndex);
|
|
169
|
+
}
|
|
140
170
|
} catch (e: any) {
|
|
141
171
|
toast.error(e.message || t('editProfile.toasts.updateAvatarFailed') || 'Failed to update avatar');
|
|
142
172
|
}
|
|
143
173
|
}
|
|
144
174
|
});
|
|
145
|
-
}, [navigate, updateUser, oxyServices]);
|
|
175
|
+
}, [navigate, updateUser, oxyServices, currentStep, avatarStepIndex, animateToStepCallback, t]);
|
|
146
176
|
|
|
147
177
|
const step = steps[currentStep];
|
|
148
178
|
const pillButtons = useMemo(() => {
|
|
@@ -201,7 +231,14 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
|
|
|
201
231
|
)}
|
|
202
232
|
{step.showAvatar && (
|
|
203
233
|
<View style={[styles.avatarSection, styles.sectionSpacing]}>
|
|
204
|
-
<Avatar
|
|
234
|
+
<Avatar
|
|
235
|
+
size={120}
|
|
236
|
+
name={currentUser?.name?.full || currentUser?.name?.first || currentUser?.username}
|
|
237
|
+
uri={avatarUri}
|
|
238
|
+
theme={theme}
|
|
239
|
+
backgroundColor={colors.primary + '20'}
|
|
240
|
+
style={styles.avatar}
|
|
241
|
+
/>
|
|
205
242
|
<TouchableOpacity style={[styles.changeAvatarButton, { backgroundColor: colors.primary }]} onPress={openAvatarPicker}>
|
|
206
243
|
<Ionicons name="image-outline" size={18} color="#FFFFFF" />
|
|
207
244
|
<Text style={styles.changeAvatarText}>{avatarUri ? (t('welcomeNew.avatar.change') || 'Change Avatar') : (t('welcomeNew.avatar.add') || 'Add Avatar')}</Text>
|
|
@@ -9,6 +9,7 @@ import Animated, {
|
|
|
9
9
|
import { Ionicons } from '@expo/vector-icons';
|
|
10
10
|
import Avatar from '../../components/Avatar';
|
|
11
11
|
import { useI18n } from '../../hooks/useI18n';
|
|
12
|
+
import { useOxy } from '../../context/OxyContext';
|
|
12
13
|
import GroupedPillButtons from '../../components/internal/GroupedPillButtons';
|
|
13
14
|
import TextField from '../../components/internal/TextField';
|
|
14
15
|
|
|
@@ -59,6 +60,7 @@ const SignInPasswordStep: React.FC<SignInPasswordStepProps> = ({
|
|
|
59
60
|
}) => {
|
|
60
61
|
const inputRef = useRef<TextInput>(null);
|
|
61
62
|
const { t } = useI18n();
|
|
63
|
+
const { oxyServices } = useOxy();
|
|
62
64
|
|
|
63
65
|
// Animated styles - properly memoized to prevent re-renders
|
|
64
66
|
const containerAnimatedStyle = useAnimatedStyle(() => {
|
|
@@ -101,18 +103,14 @@ const SignInPasswordStep: React.FC<SignInPasswordStepProps> = ({
|
|
|
101
103
|
containerAnimatedStyle
|
|
102
104
|
]}>
|
|
103
105
|
<View style={styles.modernUserProfileContainer}>
|
|
104
|
-
<Animated.View style={
|
|
105
|
-
styles.avatarContainer,
|
|
106
|
-
logoAnimatedStyle
|
|
107
|
-
]}>
|
|
106
|
+
<Animated.View style={logoAnimatedStyle}>
|
|
108
107
|
<Avatar
|
|
109
108
|
name={userProfile?.displayName || userProfile?.name || username}
|
|
110
109
|
size={100}
|
|
111
110
|
theme={theme as 'light' | 'dark'}
|
|
112
|
-
style={styles.modernUserAvatar}
|
|
113
111
|
backgroundColor={colors.primary + '20'}
|
|
112
|
+
uri={userProfile?.avatar && oxyServices ? oxyServices.getFileDownloadUrl(userProfile.avatar, 'thumb') : undefined}
|
|
114
113
|
/>
|
|
115
|
-
<View style={[styles.statusIndicator, { backgroundColor: colors.primary }]} />
|
|
116
114
|
</Animated.View>
|
|
117
115
|
<Text style={[styles.modernUserDisplayName, { color: colors.text }]}>
|
|
118
116
|
{userProfile?.displayName || userProfile?.name || username}
|
|
@@ -6,6 +6,7 @@ import Avatar from '../../components/Avatar';
|
|
|
6
6
|
import GroupedPillButtons from '../../components/internal/GroupedPillButtons';
|
|
7
7
|
import TextField from '../../components/internal/TextField';
|
|
8
8
|
import { useI18n } from '../../hooks/useI18n';
|
|
9
|
+
import { useOxy } from '../../context/OxyContext';
|
|
9
10
|
import { STEP_GAP, STEP_INNER_GAP, stepStyles } from '../../styles/spacing';
|
|
10
11
|
|
|
11
12
|
interface SignInPasswordStepProps {
|
|
@@ -69,6 +70,7 @@ const SignInPasswordStep: React.FC<SignInPasswordStepProps> = ({
|
|
|
69
70
|
}) => {
|
|
70
71
|
const inputRef = useRef<any>(null);
|
|
71
72
|
const { t } = useI18n();
|
|
73
|
+
const { oxyServices } = useOxy();
|
|
72
74
|
const baseStyles = stepStyles;
|
|
73
75
|
const webShadowReset = Platform.OS === 'web' ? ({ boxShadow: 'none' } as any) : null;
|
|
74
76
|
|
|
@@ -114,16 +116,13 @@ const SignInPasswordStep: React.FC<SignInPasswordStepProps> = ({
|
|
|
114
116
|
return (
|
|
115
117
|
<>
|
|
116
118
|
<View style={[baseStyles.container, baseStyles.sectionSpacing, stylesheet.userProfileContainer]}>
|
|
117
|
-
<View style={stylesheet.avatarContainer}>
|
|
118
119
|
<Avatar
|
|
119
120
|
name={userProfile?.displayName || userProfile?.name || username}
|
|
120
121
|
size={100}
|
|
121
122
|
theme={theme as 'light' | 'dark'}
|
|
122
|
-
style={styles.modernUserAvatar}
|
|
123
123
|
backgroundColor={colors.primary + '20'}
|
|
124
|
+
uri={userProfile?.avatar && oxyServices ? oxyServices.getFileDownloadUrl(userProfile.avatar, 'thumb') : undefined}
|
|
124
125
|
/>
|
|
125
|
-
<View style={[styles.statusIndicator, { backgroundColor: colors.primary }]} />
|
|
126
|
-
</View>
|
|
127
126
|
<Text style={[styles.modernUserDisplayName, stylesheet.displayName, { color: colors.text, marginBottom: 0, marginTop: 0 }]}>
|
|
128
127
|
{userProfile?.displayName || userProfile?.name || username}
|
|
129
128
|
</Text>
|
|
@@ -186,16 +185,13 @@ const SignInPasswordStep: React.FC<SignInPasswordStepProps> = ({
|
|
|
186
185
|
return (
|
|
187
186
|
<>
|
|
188
187
|
<View style={[baseStyles.container, baseStyles.sectionSpacing, stylesheet.userProfileContainer]}>
|
|
189
|
-
<View style={stylesheet.avatarContainer}>
|
|
190
188
|
<Avatar
|
|
191
189
|
name={userProfile?.displayName || userProfile?.name || username}
|
|
192
190
|
size={100}
|
|
193
191
|
theme={theme as 'light' | 'dark'}
|
|
194
|
-
style={styles.modernUserAvatar}
|
|
195
192
|
backgroundColor={colors.primary + '20'}
|
|
193
|
+
uri={userProfile?.avatar && oxyServices ? oxyServices.getFileDownloadUrl(userProfile.avatar, 'thumb') : undefined}
|
|
196
194
|
/>
|
|
197
|
-
<View style={[styles.statusIndicator, { backgroundColor: colors.primary }]} />
|
|
198
|
-
</View>
|
|
199
195
|
<Text style={[styles.modernUserDisplayName, stylesheet.displayName, { color: colors.text, marginBottom: 0, marginTop: 0 }]}>
|
|
200
196
|
{userProfile?.displayName || userProfile?.name || username}
|
|
201
197
|
</Text>
|