@oxyhq/services 5.13.22 → 5.13.23
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/lib/commonjs/ui/components/ErrorBoundary.js +145 -0
- package/lib/commonjs/ui/components/ErrorBoundary.js.map +1 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js +83 -30
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +19 -14
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SessionManagementScreen.js +90 -77
- package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/components/ErrorBoundary.js +139 -0
- package/lib/module/ui/components/ErrorBoundary.js.map +1 -0
- package/lib/module/ui/navigation/OxyRouter.js +83 -31
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/AccountCenterScreen.js +19 -14
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js +91 -78
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +1 -1
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/typescript/ui/components/ErrorBoundary.d.ts +31 -0
- package/lib/typescript/ui/components/ErrorBoundary.d.ts.map +1 -0
- package/lib/typescript/ui/navigation/OxyRouter.d.ts +3 -1
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ui/components/ErrorBoundary.tsx +154 -0
- package/src/ui/navigation/OxyRouter.tsx +90 -29
- package/src/ui/screens/AccountCenterScreen.tsx +17 -14
- package/src/ui/screens/SessionManagementScreen.tsx +81 -69
- package/src/ui/screens/SignInScreen.tsx +1 -1
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
Alert,
|
|
10
10
|
Platform,
|
|
11
11
|
} from 'react-native';
|
|
12
|
+
import { useCallback, useMemo } from 'react';
|
|
12
13
|
import type { BaseScreenProps } from '../navigation/types';
|
|
13
14
|
import { useOxy } from '../context/OxyContext';
|
|
14
15
|
import { packageInfo } from '../../constants/version';
|
|
@@ -39,7 +40,8 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
39
40
|
const primaryColor = '#0066CC';
|
|
40
41
|
const dangerColor = '#D32F2F';
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
// Memoized logout handler - prevents unnecessary re-renders
|
|
44
|
+
const handleLogout = useCallback(async () => {
|
|
43
45
|
try {
|
|
44
46
|
await logout();
|
|
45
47
|
if (onClose) {
|
|
@@ -49,14 +51,15 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
49
51
|
console.error('Logout failed:', error);
|
|
50
52
|
toast.error(t('common.errors.signOutFailed') || 'There was a problem signing you out. Please try again.');
|
|
51
53
|
}
|
|
52
|
-
};
|
|
54
|
+
}, [logout, onClose, t]);
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
// Memoized confirm logout handler - prevents unnecessary re-renders
|
|
57
|
+
const confirmLogout = useCallback(() => {
|
|
55
58
|
confirmAction(
|
|
56
59
|
t('common.confirms.signOut') || 'Are you sure you want to sign out?',
|
|
57
60
|
handleLogout
|
|
58
61
|
);
|
|
59
|
-
};
|
|
62
|
+
}, [handleLogout, t]);
|
|
60
63
|
|
|
61
64
|
if (!isAuthenticated) {
|
|
62
65
|
return (
|
|
@@ -91,14 +94,14 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
91
94
|
{/* Quick Actions */}
|
|
92
95
|
<Section title={t('accountCenter.sections.quickActions') || 'Quick Actions'} theme={theme} isFirst={true}>
|
|
93
96
|
<QuickActions
|
|
94
|
-
actions={[
|
|
97
|
+
actions={useMemo(() => [
|
|
95
98
|
{ id: 'overview', icon: 'person-circle', iconColor: '#007AFF', title: t('accountCenter.quickActions.overview') || 'Overview', onPress: () => navigate('AccountOverview') },
|
|
96
99
|
{ id: 'settings', icon: 'settings', iconColor: '#5856D6', title: t('accountCenter.quickActions.editProfile') || 'Edit Profile', onPress: () => navigate('EditProfile') },
|
|
97
100
|
{ id: 'sessions', icon: 'shield-checkmark', iconColor: '#30D158', title: t('accountCenter.quickActions.sessions') || 'Sessions', onPress: () => navigate('SessionManagement') },
|
|
98
101
|
{ id: 'premium', icon: 'star', iconColor: '#FFD700', title: t('accountCenter.quickActions.premium') || 'Premium', onPress: () => navigate('PremiumSubscription') },
|
|
99
102
|
...(user?.isPremium ? [{ id: 'billing', icon: 'card', iconColor: '#34C759', title: t('accountCenter.quickActions.billing') || 'Billing', onPress: () => navigate('PaymentGateway') }] : []),
|
|
100
103
|
...(sessions && sessions.length > 1 ? [{ id: 'switch', icon: 'swap-horizontal', iconColor: '#FF9500', title: t('accountCenter.quickActions.switch') || 'Switch', onPress: () => navigate('AccountSwitcher') }] : []),
|
|
101
|
-
]}
|
|
104
|
+
], [user?.isPremium, sessions, navigate, t])}
|
|
102
105
|
theme={theme}
|
|
103
106
|
/>
|
|
104
107
|
</Section>
|
|
@@ -106,7 +109,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
106
109
|
{/* Account Management */}
|
|
107
110
|
<Section title={t('accountCenter.sections.accountManagement') || 'Account Management'} theme={theme}>
|
|
108
111
|
<GroupedSection
|
|
109
|
-
items={[
|
|
112
|
+
items={useMemo(() => [
|
|
110
113
|
{
|
|
111
114
|
id: 'overview',
|
|
112
115
|
icon: 'person-circle',
|
|
@@ -155,7 +158,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
155
158
|
subtitle: t('accountCenter.items.billing.subtitle') || 'Payment methods and invoices',
|
|
156
159
|
onPress: () => navigate('PaymentGateway'),
|
|
157
160
|
}] : []),
|
|
158
|
-
]}
|
|
161
|
+
], [user?.isPremium, navigate, t])}
|
|
159
162
|
theme={theme}
|
|
160
163
|
/>
|
|
161
164
|
</Section>
|
|
@@ -164,7 +167,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
164
167
|
{sessions && sessions.length > 1 && (
|
|
165
168
|
<Section title={t('accountCenter.sections.multiAccount') || 'Multi-Account'} theme={theme}>
|
|
166
169
|
<GroupedSection
|
|
167
|
-
items={[
|
|
170
|
+
items={useMemo(() => [
|
|
168
171
|
{
|
|
169
172
|
id: 'switch',
|
|
170
173
|
icon: 'people',
|
|
@@ -181,7 +184,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
181
184
|
subtitle: t('accountCenter.items.addAccount.subtitle') || 'Sign in with a different account',
|
|
182
185
|
onPress: () => navigate('SignIn'),
|
|
183
186
|
},
|
|
184
|
-
]}
|
|
187
|
+
], [sessions.length, navigate, t])}
|
|
185
188
|
theme={theme}
|
|
186
189
|
/>
|
|
187
190
|
</Section>
|
|
@@ -191,7 +194,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
191
194
|
{(!sessions || sessions.length <= 1) && (
|
|
192
195
|
<Section title={t('accountCenter.sections.addAccount') || 'Add Account'} theme={theme}>
|
|
193
196
|
<GroupedSection
|
|
194
|
-
items={[
|
|
197
|
+
items={useMemo(() => [
|
|
195
198
|
{
|
|
196
199
|
id: 'add',
|
|
197
200
|
icon: 'person-add',
|
|
@@ -200,7 +203,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
200
203
|
subtitle: t('accountCenter.items.addAccount.subtitle') || 'Sign in with a different account',
|
|
201
204
|
onPress: () => navigate('SignIn'),
|
|
202
205
|
},
|
|
203
|
-
]}
|
|
206
|
+
], [navigate, t])}
|
|
204
207
|
theme={theme}
|
|
205
208
|
/>
|
|
206
209
|
</Section>
|
|
@@ -209,7 +212,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
209
212
|
{/* Additional Options */}
|
|
210
213
|
<Section title={t('accountCenter.sections.moreOptions') || 'More Options'} theme={theme}>
|
|
211
214
|
<GroupedSection
|
|
212
|
-
items={[
|
|
215
|
+
items={useMemo(() => [
|
|
213
216
|
...(Platform.OS !== 'web' ? [{
|
|
214
217
|
id: 'notifications',
|
|
215
218
|
icon: 'notifications',
|
|
@@ -242,7 +245,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
242
245
|
subtitle: t('accountCenter.items.appInfo.subtitle') || 'Version and system details',
|
|
243
246
|
onPress: () => navigate('AppInfo'),
|
|
244
247
|
},
|
|
245
|
-
]}
|
|
248
|
+
], [navigate, t])}
|
|
246
249
|
theme={theme}
|
|
247
250
|
/>
|
|
248
251
|
</Section>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
3
3
|
import {
|
|
4
4
|
View,
|
|
5
5
|
Text,
|
|
@@ -39,7 +39,8 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
|
|
|
39
39
|
const dangerColor = '#D32F2F';
|
|
40
40
|
const successColor = '#2E7D32';
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
// Memoized load sessions function - prevents unnecessary re-renders
|
|
43
|
+
const loadSessions = useCallback(async (isRefresh = false) => {
|
|
43
44
|
try {
|
|
44
45
|
if (isRefresh) {
|
|
45
46
|
setRefreshing(true);
|
|
@@ -64,9 +65,10 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
|
|
|
64
65
|
setLoading(false);
|
|
65
66
|
setRefreshing(false);
|
|
66
67
|
}
|
|
67
|
-
};
|
|
68
|
+
}, [refreshSessions]);
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
// Memoized logout session handler - prevents unnecessary re-renders
|
|
71
|
+
const handleLogoutSession = useCallback(async (sessionId: string) => {
|
|
70
72
|
confirmAction('Are you sure you want to logout this session?', async () => {
|
|
71
73
|
try {
|
|
72
74
|
setActionLoading(sessionId);
|
|
@@ -80,9 +82,10 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
|
|
|
80
82
|
setActionLoading(null);
|
|
81
83
|
}
|
|
82
84
|
});
|
|
83
|
-
};
|
|
85
|
+
}, [logout, refreshSessions]);
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
// Memoized logout other sessions handler - prevents unnecessary re-renders
|
|
88
|
+
const handleLogoutOtherSessions = useCallback(async () => {
|
|
86
89
|
const otherSessionsCount = userSessions.filter(s => s.sessionId !== activeSessionId).length;
|
|
87
90
|
if (otherSessionsCount === 0) {
|
|
88
91
|
toast.info('No other sessions to logout.');
|
|
@@ -108,9 +111,10 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
|
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
);
|
|
111
|
-
};
|
|
114
|
+
}, [userSessions, activeSessionId, logout, refreshSessions]);
|
|
112
115
|
|
|
113
|
-
|
|
116
|
+
// Memoized logout all sessions handler - prevents unnecessary re-renders
|
|
117
|
+
const handleLogoutAllSessions = useCallback(async () => {
|
|
114
118
|
confirmAction(
|
|
115
119
|
'This will logout all sessions including this one and you will need to sign in again. Continue?',
|
|
116
120
|
async () => {
|
|
@@ -124,10 +128,10 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
|
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
130
|
);
|
|
127
|
-
};
|
|
131
|
+
}, [logoutAll]);
|
|
128
132
|
|
|
129
|
-
//
|
|
130
|
-
const formatRelative = (dateString?: string) => {
|
|
133
|
+
// Memoized relative time formatter - prevents function recreation on every render
|
|
134
|
+
const formatRelative = useCallback((dateString?: string) => {
|
|
131
135
|
if (!dateString) return 'Unknown';
|
|
132
136
|
const date = new Date(dateString);
|
|
133
137
|
const now = new Date();
|
|
@@ -142,9 +146,10 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
|
|
|
142
146
|
const days = hrs / 24;
|
|
143
147
|
if (days < 7) return isFuture ? `in ${fmt(days)}d` : `${fmt(days)}d ago`;
|
|
144
148
|
return date.toLocaleDateString();
|
|
145
|
-
};
|
|
149
|
+
}, []);
|
|
146
150
|
|
|
147
|
-
|
|
151
|
+
// Memoized switch session handler - prevents unnecessary re-renders
|
|
152
|
+
const handleSwitchSession = useCallback(async (sessionId: string) => {
|
|
148
153
|
if (sessionId === activeSessionId) return;
|
|
149
154
|
setSwitchLoading(sessionId);
|
|
150
155
|
try {
|
|
@@ -156,11 +161,11 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
|
|
|
156
161
|
} finally {
|
|
157
162
|
setSwitchLoading(null);
|
|
158
163
|
}
|
|
159
|
-
};
|
|
164
|
+
}, [activeSessionId, switchSession]);
|
|
160
165
|
|
|
161
166
|
useEffect(() => {
|
|
162
167
|
loadSessions();
|
|
163
|
-
}, []);
|
|
168
|
+
}, [loadSessions]);
|
|
164
169
|
|
|
165
170
|
if (loading) {
|
|
166
171
|
return (
|
|
@@ -171,69 +176,76 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
|
|
|
171
176
|
);
|
|
172
177
|
}
|
|
173
178
|
|
|
174
|
-
//
|
|
175
|
-
const sessionItems =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
// Memoized session items - prevents unnecessary re-renders when dependencies haven't changed
|
|
180
|
+
const sessionItems = useMemo(() => {
|
|
181
|
+
return userSessions.map((session: ClientSession) => {
|
|
182
|
+
const isCurrent = session.sessionId === activeSessionId;
|
|
183
|
+
const subtitleParts: string[] = [];
|
|
184
|
+
if (session.deviceId) subtitleParts.push(`Device ${session.deviceId.substring(0, 10)}...`);
|
|
185
|
+
subtitleParts.push(`Last ${formatRelative(session.lastActive)}`);
|
|
186
|
+
subtitleParts.push(`Expires ${formatRelative(session.expiresAt)}`);
|
|
181
187
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
188
|
+
return {
|
|
189
|
+
id: session.sessionId,
|
|
190
|
+
icon: isCurrent ? 'shield-checkmark' : 'laptop-outline',
|
|
191
|
+
iconColor: isCurrent ? successColor : primaryColor,
|
|
192
|
+
title: isCurrent ? 'Current Session' : `Session ${session.sessionId.substring(0, 8)}...`,
|
|
193
|
+
subtitle: subtitleParts.join(' \u2022 '),
|
|
194
|
+
showChevron: false,
|
|
195
|
+
multiRow: true,
|
|
196
|
+
customContentBelow: !isCurrent ? (
|
|
197
|
+
<View style={styles.sessionActionsRow}>
|
|
198
|
+
<TouchableOpacity
|
|
199
|
+
onPress={() => handleSwitchSession(session.sessionId)}
|
|
200
|
+
style={[styles.sessionPillButton, { backgroundColor: isDarkTheme ? '#1E2A38' : '#E6F2FF', borderColor: primaryColor }]}
|
|
201
|
+
disabled={switchLoading === session.sessionId || actionLoading === session.sessionId}
|
|
202
|
+
>
|
|
203
|
+
{switchLoading === session.sessionId ? (
|
|
204
|
+
<ActivityIndicator size="small" color={primaryColor} />
|
|
205
|
+
) : (
|
|
206
|
+
<Text style={[styles.sessionPillText, { color: primaryColor }]}>Switch</Text>
|
|
207
|
+
)}
|
|
208
|
+
</TouchableOpacity>
|
|
209
|
+
<TouchableOpacity
|
|
210
|
+
onPress={() => handleLogoutSession(session.sessionId)}
|
|
211
|
+
style={[styles.sessionPillButton, { backgroundColor: isDarkTheme ? '#3A1E1E' : '#FFEBEE', borderColor: dangerColor }]}
|
|
212
|
+
disabled={actionLoading === session.sessionId || switchLoading === session.sessionId}
|
|
213
|
+
>
|
|
214
|
+
{actionLoading === session.sessionId ? (
|
|
215
|
+
<ActivityIndicator size="small" color={dangerColor} />
|
|
216
|
+
) : (
|
|
217
|
+
<Text style={[styles.sessionPillText, { color: dangerColor }]}>Logout</Text>
|
|
218
|
+
)}
|
|
219
|
+
</TouchableOpacity>
|
|
220
|
+
</View>
|
|
221
|
+
) : (
|
|
222
|
+
<View style={styles.sessionActionsRow}>
|
|
223
|
+
<Text style={[styles.currentBadgeText, { color: successColor }]}>Active</Text>
|
|
224
|
+
</View>
|
|
225
|
+
),
|
|
226
|
+
selected: isCurrent,
|
|
227
|
+
dense: true,
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
}, [userSessions, activeSessionId, formatRelative, successColor, primaryColor, isDarkTheme, switchLoading, actionLoading, handleSwitchSession, handleLogoutSession, dangerColor]);
|
|
231
|
+
|
|
232
|
+
// Memoized bulk action items - prevents unnecessary re-renders when dependencies haven't changed
|
|
233
|
+
const otherSessionsCount = useMemo(() =>
|
|
234
|
+
userSessions.filter(s => s.sessionId !== activeSessionId).length,
|
|
235
|
+
[userSessions, activeSessionId]
|
|
236
|
+
);
|
|
224
237
|
|
|
225
|
-
|
|
226
|
-
const bulkItems = [
|
|
238
|
+
const bulkItems = useMemo(() => [
|
|
227
239
|
{
|
|
228
240
|
id: 'logout-others',
|
|
229
241
|
icon: 'exit-outline',
|
|
230
242
|
iconColor: primaryColor,
|
|
231
243
|
title: 'Logout Other Sessions',
|
|
232
|
-
subtitle:
|
|
244
|
+
subtitle: otherSessionsCount === 0 ? 'No other sessions' : 'End all sessions except this one',
|
|
233
245
|
onPress: handleLogoutOtherSessions,
|
|
234
246
|
showChevron: false,
|
|
235
247
|
customContent: actionLoading === 'others' ? <ActivityIndicator size="small" color={primaryColor} /> : undefined,
|
|
236
|
-
disabled: actionLoading === 'others' ||
|
|
248
|
+
disabled: actionLoading === 'others' || otherSessionsCount === 0,
|
|
237
249
|
dense: true,
|
|
238
250
|
},
|
|
239
251
|
{
|
|
@@ -248,7 +260,7 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
|
|
|
248
260
|
disabled: actionLoading === 'all',
|
|
249
261
|
dense: true,
|
|
250
262
|
},
|
|
251
|
-
];
|
|
263
|
+
], [otherSessionsCount, primaryColor, dangerColor, handleLogoutOtherSessions, handleLogoutAllSessions, actionLoading]);
|
|
252
264
|
|
|
253
265
|
return (
|
|
254
266
|
<View style={[styles.container, { backgroundColor }]}>
|
|
@@ -129,7 +129,7 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
129
129
|
} finally {
|
|
130
130
|
setIsValidating(false);
|
|
131
131
|
}
|
|
132
|
-
}, [oxyServices]);
|
|
132
|
+
}, [oxyServices, sessions]);
|
|
133
133
|
|
|
134
134
|
// Input change handlers
|
|
135
135
|
const handleUsernameChange = useCallback((text: string) => {
|