@oxyhq/services 10.2.10 → 10.2.11
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/ActingAsBanner.js +2 -1
- package/lib/commonjs/ui/components/ActingAsBanner.js.map +1 -1
- package/lib/commonjs/ui/components/StepBasedScreen.js +19 -10
- package/lib/commonjs/ui/components/StepBasedScreen.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +38 -122
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +9 -7
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/commonjs/ui/hooks/useDeviceAccounts.js +20 -25
- package/lib/commonjs/ui/hooks/useDeviceAccounts.js.map +1 -1
- package/lib/commonjs/ui/hooks/useProfileEditing.js +19 -10
- package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/commonjs/ui/screens/AppInfoScreen.js +1 -2
- package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ConnectedAppsScreen.js +1 -2
- package/lib/commonjs/ui/screens/ConnectedAppsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +11 -13
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FeedbackScreen.js +2 -3
- package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/HelpSupportScreen.js +2 -2
- package/lib/commonjs/ui/screens/HelpSupportScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/HistoryViewScreen.js +2 -2
- package/lib/commonjs/ui/screens/HistoryViewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +2 -3
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LegalDocumentsScreen.js +2 -2
- package/lib/commonjs/ui/screens/LegalDocumentsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +1 -2
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/NotificationsScreen.js +2 -2
- package/lib/commonjs/ui/screens/NotificationsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PaymentGatewayScreen.js +2 -2
- package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PreferencesScreen.js +2 -2
- package/lib/commonjs/ui/screens/PreferencesScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +4 -3
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SavesCollectionsScreen.js +3 -4
- package/lib/commonjs/ui/screens/SavesCollectionsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js +2 -2
- package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/trust/TrustFAQScreen.js +2 -2
- package/lib/commonjs/ui/screens/trust/TrustFAQScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js +2 -2
- package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js.map +1 -1
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/commonjs/ui/utils/avatarUtils.js +1 -1
- package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
- package/lib/module/ui/components/ActingAsBanner.js +2 -1
- package/lib/module/ui/components/ActingAsBanner.js.map +1 -1
- package/lib/module/ui/components/StepBasedScreen.js +19 -10
- package/lib/module/ui/components/StepBasedScreen.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +38 -122
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +9 -7
- package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/module/ui/hooks/useDeviceAccounts.js +20 -25
- package/lib/module/ui/hooks/useDeviceAccounts.js.map +1 -1
- package/lib/module/ui/hooks/useProfileEditing.js +19 -10
- package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/module/ui/screens/AppInfoScreen.js +1 -1
- package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/module/ui/screens/ConnectedAppsScreen.js +1 -1
- package/lib/module/ui/screens/ConnectedAppsScreen.js.map +1 -1
- package/lib/module/ui/screens/EditProfileFieldScreen.js +10 -11
- package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/module/ui/screens/FeedbackScreen.js +1 -1
- package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
- package/lib/module/ui/screens/HelpSupportScreen.js +1 -1
- package/lib/module/ui/screens/HelpSupportScreen.js.map +1 -1
- package/lib/module/ui/screens/HistoryViewScreen.js +1 -1
- package/lib/module/ui/screens/HistoryViewScreen.js.map +1 -1
- package/lib/module/ui/screens/LanguageSelectorScreen.js +1 -1
- package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/module/ui/screens/LegalDocumentsScreen.js +1 -1
- package/lib/module/ui/screens/LegalDocumentsScreen.js.map +1 -1
- package/lib/module/ui/screens/ManageAccountScreen.js +1 -1
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/NotificationsScreen.js +1 -1
- package/lib/module/ui/screens/NotificationsScreen.js.map +1 -1
- package/lib/module/ui/screens/PaymentGatewayScreen.js +1 -1
- package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
- package/lib/module/ui/screens/PreferencesScreen.js +1 -1
- package/lib/module/ui/screens/PreferencesScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +5 -4
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/SavesCollectionsScreen.js +2 -3
- package/lib/module/ui/screens/SavesCollectionsScreen.js.map +1 -1
- package/lib/module/ui/screens/trust/TrustCenterScreen.js +1 -1
- package/lib/module/ui/screens/trust/TrustCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/trust/TrustFAQScreen.js +1 -1
- package/lib/module/ui/screens/trust/TrustFAQScreen.js.map +1 -1
- package/lib/module/ui/screens/trust/TrustRewardsScreen.js +1 -1
- package/lib/module/ui/screens/trust/TrustRewardsScreen.js.map +1 -1
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/module/ui/utils/avatarUtils.js +1 -1
- package/lib/module/ui/utils/avatarUtils.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/ActingAsBanner.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/StepBasedScreen.d.ts +5 -3
- package/lib/typescript/commonjs/ui/components/StepBasedScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +52 -6
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts +22 -18
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/SavesCollectionsScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/stores/authStore.d.ts +2 -10
- package/lib/typescript/commonjs/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/navigation.d.ts +0 -1
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts +2 -1
- package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ActingAsBanner.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/StepBasedScreen.d.ts +5 -3
- package/lib/typescript/module/ui/components/StepBasedScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +52 -6
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts +22 -18
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/SavesCollectionsScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/stores/authStore.d.ts +2 -10
- package/lib/typescript/module/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/navigation.d.ts +0 -1
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/avatarUtils.d.ts +2 -1
- package/lib/typescript/module/ui/utils/avatarUtils.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/ui/components/ActingAsBanner.tsx +2 -4
- package/src/ui/components/StepBasedScreen.tsx +37 -22
- package/src/ui/context/hooks/useAuthOperations.ts +51 -144
- package/src/ui/hooks/mutations/useAccountMutations.ts +9 -8
- package/src/ui/hooks/useDeviceAccounts.ts +20 -24
- package/src/ui/hooks/useProfileEditing.ts +43 -30
- package/src/ui/screens/AppInfoScreen.tsx +1 -1
- package/src/ui/screens/ConnectedAppsScreen.tsx +1 -1
- package/src/ui/screens/EditProfileFieldScreen.tsx +24 -14
- package/src/ui/screens/FeedbackScreen.tsx +1 -1
- package/src/ui/screens/HelpSupportScreen.tsx +1 -1
- package/src/ui/screens/HistoryViewScreen.tsx +1 -1
- package/src/ui/screens/LanguageSelectorScreen.tsx +1 -1
- package/src/ui/screens/LegalDocumentsScreen.tsx +1 -1
- package/src/ui/screens/ManageAccountScreen.tsx +1 -1
- package/src/ui/screens/NotificationsScreen.tsx +1 -1
- package/src/ui/screens/PaymentGatewayScreen.tsx +1 -1
- package/src/ui/screens/PreferencesScreen.tsx +1 -1
- package/src/ui/screens/ProfileScreen.tsx +4 -4
- package/src/ui/screens/SavesCollectionsScreen.tsx +3 -6
- package/src/ui/screens/trust/TrustCenterScreen.tsx +1 -1
- package/src/ui/screens/trust/TrustFAQScreen.tsx +1 -1
- package/src/ui/screens/trust/TrustRewardsScreen.tsx +1 -1
- package/src/ui/stores/authStore.ts +4 -13
- package/src/ui/types/navigation.ts +0 -4
- package/src/ui/utils/avatarUtils.ts +3 -2
- package/lib/commonjs/ui/styles/spacing.js +0 -68
- package/lib/commonjs/ui/styles/spacing.js.map +0 -1
- package/lib/commonjs/ui/utils/themeUtils.js +0 -37
- package/lib/commonjs/ui/utils/themeUtils.js.map +0 -1
- package/lib/module/ui/styles/spacing.js +0 -16
- package/lib/module/ui/styles/spacing.js.map +0 -1
- package/lib/module/ui/utils/themeUtils.js +0 -13
- package/lib/module/ui/utils/themeUtils.js.map +0 -1
- package/lib/typescript/commonjs/ui/styles/spacing.d.ts +0 -13
- package/lib/typescript/commonjs/ui/styles/spacing.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/utils/themeUtils.d.ts +0 -11
- package/lib/typescript/commonjs/ui/utils/themeUtils.d.ts.map +0 -1
- package/lib/typescript/module/ui/styles/spacing.d.ts +0 -13
- package/lib/typescript/module/ui/styles/spacing.d.ts.map +0 -1
- package/lib/typescript/module/ui/utils/themeUtils.d.ts +0 -11
- package/lib/typescript/module/ui/utils/themeUtils.d.ts.map +0 -1
- package/src/ui/styles/spacing.ts +0 -22
- package/src/ui/utils/themeUtils.ts +0 -18
|
@@ -11,11 +11,6 @@ import { SignatureService } from '@oxyhq/core';
|
|
|
11
11
|
import { isWebBrowser } from '../../hooks/useWebSSO';
|
|
12
12
|
import { clearActiveAuthuser, clearSsoBounceState } from '../../utils/activeAuthuser';
|
|
13
13
|
|
|
14
|
-
/** Type guard for error objects with optional code and status properties */
|
|
15
|
-
function isErrorWithCodeOrStatus(error: unknown): error is { code?: string; status?: number; message?: string } {
|
|
16
|
-
return typeof error === 'object' && error !== null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
export interface UseAuthOperationsOptions {
|
|
20
15
|
oxyServices: OxyServices;
|
|
21
16
|
storage: StorageInterface | null;
|
|
@@ -55,7 +50,6 @@ const LOGOUT_ALL_ERROR_CODE = 'LOGOUT_ALL_ERROR';
|
|
|
55
50
|
*/
|
|
56
51
|
export const useAuthOperations = ({
|
|
57
52
|
oxyServices,
|
|
58
|
-
storage,
|
|
59
53
|
sessions,
|
|
60
54
|
activeSessionId,
|
|
61
55
|
setActiveSessionId,
|
|
@@ -77,7 +71,7 @@ export const useAuthOperations = ({
|
|
|
77
71
|
sessionsRef.current = sessions;
|
|
78
72
|
|
|
79
73
|
/**
|
|
80
|
-
* Internal function to perform challenge-response sign in
|
|
74
|
+
* Internal function to perform challenge-response sign in.
|
|
81
75
|
*/
|
|
82
76
|
const performSignIn = useCallback(
|
|
83
77
|
async (publicKey: string): Promise<User> => {
|
|
@@ -86,36 +80,8 @@ export const useAuthOperations = ({
|
|
|
86
80
|
const deviceInfo = await DeviceManager.getDeviceInfo();
|
|
87
81
|
const deviceName = deviceInfo.deviceName || DeviceManager.getDefaultDeviceName();
|
|
88
82
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Try to request challenge from server (online)
|
|
93
|
-
try {
|
|
94
|
-
const challengeResponse = await oxyServices.requestChallenge(publicKey);
|
|
95
|
-
challenge = challengeResponse.challenge;
|
|
96
|
-
} catch (error) {
|
|
97
|
-
// Network error - generate challenge locally for offline sign-in
|
|
98
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
99
|
-
const isNetworkError =
|
|
100
|
-
errorMessage.includes('Network') ||
|
|
101
|
-
errorMessage.includes('network') ||
|
|
102
|
-
errorMessage.includes('Failed to fetch') ||
|
|
103
|
-
errorMessage.includes('fetch failed') ||
|
|
104
|
-
(isErrorWithCodeOrStatus(error) && error.code === 'NETWORK_ERROR') ||
|
|
105
|
-
(isErrorWithCodeOrStatus(error) && error.status === 0);
|
|
106
|
-
|
|
107
|
-
if (isNetworkError) {
|
|
108
|
-
if (__DEV__ && logger) {
|
|
109
|
-
logger('Network unavailable, performing offline sign-in');
|
|
110
|
-
}
|
|
111
|
-
// Generate challenge locally
|
|
112
|
-
challenge = await SignatureService.generateChallenge();
|
|
113
|
-
isOffline = true;
|
|
114
|
-
} else {
|
|
115
|
-
// Re-throw non-network errors
|
|
116
|
-
throw error;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
83
|
+
const challengeResponse = await oxyServices.requestChallenge(publicKey);
|
|
84
|
+
const challenge = challengeResponse.challenge;
|
|
119
85
|
|
|
120
86
|
// Note: Biometric authentication check should be handled by the app layer
|
|
121
87
|
// (e.g., accounts app) before calling signIn. The biometric preference is stored
|
|
@@ -127,123 +93,65 @@ export const useAuthOperations = ({
|
|
|
127
93
|
let fullUser: User;
|
|
128
94
|
let sessionResponse: SessionLoginResponse;
|
|
129
95
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
user: {
|
|
155
|
-
id: publicKey,
|
|
156
|
-
username: '',
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// Store offline session locally
|
|
161
|
-
const offlineSession: ClientSession = {
|
|
162
|
-
sessionId: localSessionId,
|
|
163
|
-
deviceId: localDeviceId,
|
|
164
|
-
expiresAt,
|
|
165
|
-
lastActive: new Date().toISOString(),
|
|
166
|
-
userId: publicKey,
|
|
167
|
-
isCurrent: true,
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
setActiveSessionId(localSessionId);
|
|
171
|
-
await saveActiveSessionId(localSessionId);
|
|
172
|
-
updateSessions([offlineSession], { merge: true });
|
|
173
|
-
|
|
174
|
-
// Mark session as offline for later sync
|
|
175
|
-
if (storage) {
|
|
176
|
-
await storage.setItem(`oxy_session_${localSessionId}_offline`, 'true');
|
|
177
|
-
}
|
|
178
|
-
|
|
96
|
+
// `verifyChallenge` plants the first access token internally, mirroring
|
|
97
|
+
// `claimSessionByToken`, so the client is authenticated as soon as this
|
|
98
|
+
// resolves.
|
|
99
|
+
sessionResponse = await oxyServices.verifyChallenge(
|
|
100
|
+
publicKey,
|
|
101
|
+
challenge,
|
|
102
|
+
signature,
|
|
103
|
+
timestamp,
|
|
104
|
+
deviceName,
|
|
105
|
+
deviceFingerprint,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Get full user data
|
|
109
|
+
fullUser = await oxyServices.getUserBySession(sessionResponse.sessionId);
|
|
110
|
+
|
|
111
|
+
// Fetch device sessions
|
|
112
|
+
let allDeviceSessions: ClientSession[] = [];
|
|
113
|
+
try {
|
|
114
|
+
allDeviceSessions = await fetchSessionsWithFallback(oxyServices, sessionResponse.sessionId, {
|
|
115
|
+
fallbackDeviceId: sessionResponse.deviceId,
|
|
116
|
+
fallbackUserId: fullUser.id,
|
|
117
|
+
logger,
|
|
118
|
+
});
|
|
119
|
+
} catch (error) {
|
|
179
120
|
if (__DEV__ && logger) {
|
|
180
|
-
logger('
|
|
121
|
+
logger('Failed to fetch device sessions after login', error);
|
|
181
122
|
}
|
|
182
|
-
}
|
|
183
|
-
// Online sign-in: use normal flow.
|
|
184
|
-
// Verify and create session. `verifyChallenge` plants the first
|
|
185
|
-
// access token (and refresh token) from the `/auth/verify` response
|
|
186
|
-
// body internally — mirroring `claimSessionByToken` — so the client is
|
|
187
|
-
// authenticated as soon as this resolves. Session IDs are not public
|
|
188
|
-
// token-minting credentials; a token-less verify response simply leaves
|
|
189
|
-
// the client without a bearer here.
|
|
190
|
-
sessionResponse = await oxyServices.verifyChallenge(
|
|
191
|
-
publicKey,
|
|
192
|
-
challenge,
|
|
193
|
-
signature,
|
|
194
|
-
timestamp,
|
|
195
|
-
deviceName,
|
|
196
|
-
deviceFingerprint,
|
|
197
|
-
);
|
|
123
|
+
}
|
|
198
124
|
|
|
199
|
-
|
|
200
|
-
|
|
125
|
+
// Check for existing session for same user and switch to it to avoid duplicates
|
|
126
|
+
const existingSession = allDeviceSessions.find(
|
|
127
|
+
(session) =>
|
|
128
|
+
session.userId?.toString() === fullUser.id?.toString() &&
|
|
129
|
+
session.sessionId !== sessionResponse.sessionId,
|
|
130
|
+
);
|
|
201
131
|
|
|
202
|
-
|
|
203
|
-
|
|
132
|
+
if (existingSession) {
|
|
133
|
+
// Switch to existing session instead of creating duplicate
|
|
204
134
|
try {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
logger,
|
|
209
|
-
});
|
|
210
|
-
} catch (error) {
|
|
135
|
+
await oxyServices.logoutSession(sessionResponse.sessionId, sessionResponse.sessionId);
|
|
136
|
+
} catch (logoutError) {
|
|
137
|
+
// Non-critical - continue to switch session even if logout fails
|
|
211
138
|
if (__DEV__ && logger) {
|
|
212
|
-
logger('Failed to
|
|
139
|
+
logger('Failed to logout duplicate session, continuing with switch', logoutError);
|
|
213
140
|
}
|
|
214
141
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
session.userId?.toString() === fullUser.id?.toString() &&
|
|
220
|
-
session.sessionId !== sessionResponse.sessionId,
|
|
142
|
+
await switchSession(existingSession.sessionId);
|
|
143
|
+
updateSessions(
|
|
144
|
+
allDeviceSessions.filter((session) => session.sessionId !== sessionResponse.sessionId),
|
|
145
|
+
{ merge: false },
|
|
221
146
|
);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// Switch to existing session instead of creating duplicate
|
|
225
|
-
try {
|
|
226
|
-
await oxyServices.logoutSession(sessionResponse.sessionId, sessionResponse.sessionId);
|
|
227
|
-
} catch (logoutError) {
|
|
228
|
-
// Non-critical - continue to switch session even if logout fails
|
|
229
|
-
if (__DEV__ && logger) {
|
|
230
|
-
logger('Failed to logout duplicate session, continuing with switch', logoutError);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
await switchSession(existingSession.sessionId);
|
|
234
|
-
updateSessions(
|
|
235
|
-
allDeviceSessions.filter((session) => session.sessionId !== sessionResponse.sessionId),
|
|
236
|
-
{ merge: false },
|
|
237
|
-
);
|
|
238
|
-
onAuthStateChange?.(fullUser);
|
|
239
|
-
return fullUser;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
setActiveSessionId(sessionResponse.sessionId);
|
|
243
|
-
await saveActiveSessionId(sessionResponse.sessionId);
|
|
244
|
-
updateSessions(allDeviceSessions, { merge: true });
|
|
147
|
+
onAuthStateChange?.(fullUser);
|
|
148
|
+
return fullUser;
|
|
245
149
|
}
|
|
246
150
|
|
|
151
|
+
setActiveSessionId(sessionResponse.sessionId);
|
|
152
|
+
await saveActiveSessionId(sessionResponse.sessionId);
|
|
153
|
+
updateSessions(allDeviceSessions, { merge: true });
|
|
154
|
+
|
|
247
155
|
await applyLanguagePreference(fullUser);
|
|
248
156
|
loginSuccess(fullUser);
|
|
249
157
|
onAuthStateChange?.(fullUser);
|
|
@@ -260,7 +168,6 @@ export const useAuthOperations = ({
|
|
|
260
168
|
setActiveSessionId,
|
|
261
169
|
switchSession,
|
|
262
170
|
updateSessions,
|
|
263
|
-
storage,
|
|
264
171
|
],
|
|
265
172
|
);
|
|
266
173
|
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
User,
|
|
8
8
|
UserPreferences,
|
|
9
9
|
} from '@oxyhq/core';
|
|
10
|
+
import type { UserProfileUpdate } from '@oxyhq/contracts';
|
|
10
11
|
import {
|
|
11
12
|
queryKeys,
|
|
12
13
|
invalidateAccountQueries,
|
|
@@ -29,7 +30,7 @@ export const useUpdateProfile = () => {
|
|
|
29
30
|
|
|
30
31
|
return useMutation({
|
|
31
32
|
mutationKey: [...mutationKeys.account.updateProfile],
|
|
32
|
-
mutationFn: async (updates:
|
|
33
|
+
mutationFn: async (updates: UserProfileUpdate) => {
|
|
33
34
|
return authenticatedApiCall<User>(
|
|
34
35
|
oxyServices,
|
|
35
36
|
activeSessionId,
|
|
@@ -46,17 +47,18 @@ export const useUpdateProfile = () => {
|
|
|
46
47
|
|
|
47
48
|
// Optimistically update
|
|
48
49
|
if (previousUser) {
|
|
49
|
-
|
|
50
|
+
const optimisticUser: User = {
|
|
50
51
|
...previousUser,
|
|
51
52
|
...updates,
|
|
52
|
-
|
|
53
|
+
name: updates.name
|
|
54
|
+
? { ...previousUser.name, ...updates.name }
|
|
55
|
+
: previousUser.name,
|
|
56
|
+
};
|
|
57
|
+
queryClient.setQueryData<User>(queryKeys.accounts.current(), optimisticUser);
|
|
53
58
|
|
|
54
59
|
// Also update profile query if sessionId is available
|
|
55
60
|
if (activeSessionId) {
|
|
56
|
-
queryClient.setQueryData<User>(queryKeys.users.profile(activeSessionId),
|
|
57
|
-
...previousUser,
|
|
58
|
-
...updates,
|
|
59
|
-
});
|
|
61
|
+
queryClient.setQueryData<User>(queryKeys.users.profile(activeSessionId), optimisticUser);
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -684,4 +686,3 @@ export const useUploadFile = () => {
|
|
|
684
686
|
},
|
|
685
687
|
});
|
|
686
688
|
};
|
|
687
|
-
|
|
@@ -252,19 +252,16 @@ export function useDeviceAccounts(): UseDeviceAccountsResult {
|
|
|
252
252
|
|
|
253
253
|
if (fromSharedApex) {
|
|
254
254
|
// Shared apex path: every entry carries a real per-account user.
|
|
255
|
-
built = sharedAccounts.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const accountUser: DeviceAccountUser = entry.user
|
|
260
|
-
id: '',
|
|
261
|
-
username: '',
|
|
262
|
-
};
|
|
255
|
+
built = sharedAccounts.flatMap((entry): DeviceAccount[] => {
|
|
256
|
+
if (!entry.user) {
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
const accountUser: DeviceAccountUser = entry.user;
|
|
263
260
|
const displayName = getAccountDisplayName(accountUser, locale);
|
|
264
261
|
const handle = getAccountFallbackHandle(accountUser);
|
|
265
|
-
const email = entry.user
|
|
262
|
+
const email = entry.user.email ?? null;
|
|
266
263
|
const secondaryHandle = handle ? `@${handle}` : null;
|
|
267
|
-
return {
|
|
264
|
+
return [{
|
|
268
265
|
sessionId: entry.sessionId,
|
|
269
266
|
authuser: entry.authuser,
|
|
270
267
|
// Provisional; finalised by `markCurrentAccount` below so the
|
|
@@ -274,37 +271,36 @@ export function useDeviceAccounts(): UseDeviceAccountsResult {
|
|
|
274
271
|
// Real email, or null (NEVER synthesized). The UI uses the
|
|
275
272
|
// `@handle` line only when email is genuinely absent.
|
|
276
273
|
email: email ?? secondaryHandle,
|
|
277
|
-
avatarUrl: resolveAvatarUrl(entry.user
|
|
278
|
-
color: entry.user
|
|
274
|
+
avatarUrl: resolveAvatarUrl(entry.user.avatar),
|
|
275
|
+
color: entry.user.color ?? null,
|
|
279
276
|
user: accountUser,
|
|
280
|
-
};
|
|
277
|
+
}];
|
|
281
278
|
});
|
|
282
279
|
} else {
|
|
283
280
|
// Local fallback path: build from the SDK's multi-session store. The
|
|
284
281
|
// active session row gets the full loaded `user`; inactive fallback
|
|
285
282
|
// rows carry only what the `ClientSession` exposes (no synthesized
|
|
286
283
|
// identity — they show the active user's data only when active).
|
|
287
|
-
built = (sessions ?? []).
|
|
284
|
+
built = (sessions ?? []).flatMap((session: ClientSession): DeviceAccount[] => {
|
|
288
285
|
const isCurrent = session.sessionId === activeSessionId;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
286
|
+
if (!isCurrent || !user) {
|
|
287
|
+
return [];
|
|
288
|
+
}
|
|
289
|
+
const accountUser: DeviceAccountUser = user;
|
|
292
290
|
const displayName = getAccountDisplayName(accountUser, locale);
|
|
293
291
|
const handle = getAccountFallbackHandle(accountUser);
|
|
294
|
-
const email =
|
|
292
|
+
const email = user.email ?? null;
|
|
295
293
|
const secondaryHandle = handle ? `@${handle}` : null;
|
|
296
|
-
|
|
297
|
-
const color = isCurrent && user?.color ? user.color : null;
|
|
298
|
-
return {
|
|
294
|
+
return [{
|
|
299
295
|
sessionId: session.sessionId,
|
|
300
296
|
authuser: session.authuser,
|
|
301
297
|
isCurrent,
|
|
302
298
|
displayName,
|
|
303
299
|
email: email ?? secondaryHandle,
|
|
304
|
-
avatarUrl: resolveAvatarUrl(avatar),
|
|
305
|
-
color,
|
|
300
|
+
avatarUrl: resolveAvatarUrl(user.avatar),
|
|
301
|
+
color: user.color ?? null,
|
|
306
302
|
user: accountUser,
|
|
307
|
-
};
|
|
303
|
+
}];
|
|
308
304
|
});
|
|
309
305
|
}
|
|
310
306
|
|
|
@@ -1,38 +1,51 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
|
-
import { useI18n } from './useI18n';
|
|
3
2
|
import { useUpdateProfile } from './mutations/useAccountMutations';
|
|
4
3
|
import { useAuthStore } from '../stores/authStore';
|
|
4
|
+
import type { UserProfileUpdate } from '@oxyhq/contracts';
|
|
5
|
+
|
|
6
|
+
interface ProfileLocation {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
coordinates?: { lat: number; lon: number };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ProfileLinkMetadata {
|
|
14
|
+
url: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
image?: string;
|
|
18
|
+
id: string;
|
|
19
|
+
}
|
|
5
20
|
|
|
6
21
|
export interface ProfileUpdateData {
|
|
7
|
-
|
|
22
|
+
firstName?: string;
|
|
8
23
|
lastName?: string;
|
|
9
24
|
username?: string;
|
|
10
25
|
email?: string;
|
|
11
26
|
bio?: string;
|
|
12
27
|
location?: string;
|
|
13
|
-
locations?:
|
|
14
|
-
id: string;
|
|
15
|
-
name: string;
|
|
16
|
-
label?: string;
|
|
17
|
-
coordinates?: { lat: number; lon: number };
|
|
18
|
-
}>;
|
|
28
|
+
locations?: ProfileLocation[];
|
|
19
29
|
links?: string[];
|
|
20
|
-
linksMetadata?:
|
|
21
|
-
url: string;
|
|
22
|
-
title?: string;
|
|
23
|
-
description?: string;
|
|
24
|
-
image?: string;
|
|
25
|
-
id: string;
|
|
26
|
-
}>;
|
|
30
|
+
linksMetadata?: ProfileLinkMetadata[];
|
|
27
31
|
avatar?: string;
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
type ProfileFieldValue = string | ProfileLocation[] | ProfileLinkMetadata[];
|
|
35
|
+
|
|
36
|
+
function isProfileLocationArray(value: ProfileFieldValue): value is ProfileLocation[] {
|
|
37
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'object' && item !== null && 'name' in item);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isProfileLinkMetadataArray(value: ProfileFieldValue): value is ProfileLinkMetadata[] {
|
|
41
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'object' && item !== null && 'url' in item);
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
/**
|
|
31
45
|
* Hook for managing profile editing operations
|
|
32
46
|
* Provides functions to update profile fields and handle saving
|
|
33
47
|
*/
|
|
34
48
|
export const useProfileEditing = () => {
|
|
35
|
-
const { t } = useI18n();
|
|
36
49
|
const updateProfileMutation = useUpdateProfile();
|
|
37
50
|
|
|
38
51
|
/**
|
|
@@ -40,7 +53,7 @@ export const useProfileEditing = () => {
|
|
|
40
53
|
*/
|
|
41
54
|
const saveProfile = useCallback(async (updates: ProfileUpdateData) => {
|
|
42
55
|
// Prepare update object
|
|
43
|
-
const updateData:
|
|
56
|
+
const updateData: UserProfileUpdate = {};
|
|
44
57
|
|
|
45
58
|
if (updates.username !== undefined) {
|
|
46
59
|
updateData.username = updates.username;
|
|
@@ -70,11 +83,11 @@ export const useProfileEditing = () => {
|
|
|
70
83
|
}
|
|
71
84
|
|
|
72
85
|
// Handle name field
|
|
73
|
-
if (updates.
|
|
86
|
+
if (updates.firstName !== undefined || updates.lastName !== undefined) {
|
|
74
87
|
const currentUser = useAuthStore.getState().user;
|
|
75
88
|
const currentName = currentUser?.name;
|
|
76
89
|
updateData.name = {
|
|
77
|
-
first: updates.
|
|
90
|
+
first: updates.firstName ?? (typeof currentName === 'object' ? currentName?.first : '') ?? '',
|
|
78
91
|
last: updates.lastName ?? (typeof currentName === 'object' ? currentName?.last : '') ?? '',
|
|
79
92
|
};
|
|
80
93
|
}
|
|
@@ -86,33 +99,39 @@ export const useProfileEditing = () => {
|
|
|
86
99
|
// Error toast is handled by the mutation
|
|
87
100
|
return false;
|
|
88
101
|
}
|
|
89
|
-
}, [updateProfileMutation
|
|
102
|
+
}, [updateProfileMutation]);
|
|
90
103
|
|
|
91
104
|
/**
|
|
92
105
|
* Update a single profile field
|
|
93
106
|
*/
|
|
94
|
-
const updateField = useCallback(async (field: string, value:
|
|
107
|
+
const updateField = useCallback(async (field: string, value: ProfileFieldValue) => {
|
|
95
108
|
const updates: ProfileUpdateData = {};
|
|
96
109
|
|
|
97
110
|
switch (field) {
|
|
98
|
-
case '
|
|
99
|
-
|
|
111
|
+
case 'firstName':
|
|
112
|
+
if (typeof value !== 'string') return false;
|
|
113
|
+
updates.firstName = value;
|
|
100
114
|
break;
|
|
101
115
|
case 'username':
|
|
116
|
+
if (typeof value !== 'string') return false;
|
|
102
117
|
updates.username = value;
|
|
103
118
|
break;
|
|
104
119
|
case 'email':
|
|
120
|
+
if (typeof value !== 'string') return false;
|
|
105
121
|
updates.email = value;
|
|
106
122
|
break;
|
|
107
123
|
case 'bio':
|
|
124
|
+
if (typeof value !== 'string') return false;
|
|
108
125
|
updates.bio = value;
|
|
109
126
|
break;
|
|
110
127
|
case 'location':
|
|
128
|
+
if (!isProfileLocationArray(value)) return false;
|
|
111
129
|
updates.locations = value;
|
|
112
130
|
break;
|
|
113
131
|
case 'links':
|
|
132
|
+
if (!isProfileLinkMetadataArray(value)) return false;
|
|
114
133
|
updates.linksMetadata = value;
|
|
115
|
-
updates.links = value.map((link
|
|
134
|
+
updates.links = value.map((link) => link.url);
|
|
116
135
|
break;
|
|
117
136
|
default:
|
|
118
137
|
return false;
|
|
@@ -129,9 +148,3 @@ export const useProfileEditing = () => {
|
|
|
129
148
|
};
|
|
130
149
|
|
|
131
150
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
@@ -20,7 +20,7 @@ import { SettingsIcon } from '../components/SettingsIcon';
|
|
|
20
20
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
21
21
|
import { useColorScheme } from '../hooks/useColorScheme';
|
|
22
22
|
import { Colors } from '../constants/theme';
|
|
23
|
-
import { normalizeColorScheme } from '
|
|
23
|
+
import { normalizeColorScheme } from '@oxyhq/core';
|
|
24
24
|
import { useOxy } from '../context/OxyContext';
|
|
25
25
|
import { useI18n } from '../hooks/useI18n';
|
|
26
26
|
import { SettingsListGroup, SettingsListItem } from '@oxyhq/bloom/settings-list';
|
|
@@ -15,7 +15,7 @@ import { useAuthorizedApps } from '../hooks/queries/useAccountQueries';
|
|
|
15
15
|
import { useRevokeAuthorizedApp } from '../hooks/mutations/useAccountMutations';
|
|
16
16
|
import { useColorScheme } from '../hooks/useColorScheme';
|
|
17
17
|
import { Colors } from '../constants/theme';
|
|
18
|
-
import { normalizeColorScheme, normalizeTheme } from '
|
|
18
|
+
import { normalizeColorScheme, normalizeTheme } from '@oxyhq/core';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Format an ISO-8601 timestamp as a human-readable relative time. Mirrors the
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { Ionicons } from '@expo/vector-icons';
|
|
15
15
|
import type { BaseScreenProps } from '../types/navigation';
|
|
16
16
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
17
|
-
import { normalizeTheme } from '
|
|
17
|
+
import { normalizeTheme } from '@oxyhq/core';
|
|
18
18
|
import Header from '../components/Header';
|
|
19
19
|
import { useI18n } from '../hooks/useI18n';
|
|
20
20
|
import { useOxy } from '../context/OxyContext';
|
|
@@ -59,6 +59,17 @@ interface EditProfileFieldScreenProps extends BaseScreenProps {
|
|
|
59
59
|
fieldType?: ProfileFieldType;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
type EditableListItem = {
|
|
63
|
+
id: string;
|
|
64
|
+
name?: string;
|
|
65
|
+
label?: string;
|
|
66
|
+
url?: string;
|
|
67
|
+
title?: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
image?: string;
|
|
70
|
+
coordinates?: { lat: number; lon: number };
|
|
71
|
+
};
|
|
72
|
+
|
|
62
73
|
/**
|
|
63
74
|
* EditProfileFieldScreen - A dedicated screen for editing profile fields
|
|
64
75
|
*
|
|
@@ -84,7 +95,7 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
84
95
|
const [fieldErrors, setFieldErrors] = useState<Record<string, string | undefined>>({});
|
|
85
96
|
|
|
86
97
|
// State for list fields (locations, links)
|
|
87
|
-
const [listItems, setListItems] = useState<
|
|
98
|
+
const [listItems, setListItems] = useState<EditableListItem[]>([]);
|
|
88
99
|
const [newItemValue, setNewItemValue] = useState('');
|
|
89
100
|
|
|
90
101
|
// Get field configuration based on fieldType
|
|
@@ -96,7 +107,7 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
96
107
|
subtitle: t('editProfile.items.displayName.subtitle') || 'This is how your name will appear to others',
|
|
97
108
|
fields: [
|
|
98
109
|
{
|
|
99
|
-
key: '
|
|
110
|
+
key: 'firstName',
|
|
100
111
|
label: t('editProfile.items.displayName.firstName') || 'First Name',
|
|
101
112
|
placeholder: t('editProfile.items.displayName.firstNamePlaceholder') || 'Enter first name',
|
|
102
113
|
},
|
|
@@ -255,30 +266,29 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
255
266
|
|
|
256
267
|
if (fieldConfig.isList) {
|
|
257
268
|
if (fieldType === 'locations') {
|
|
258
|
-
const locations = Array.isArray(userData.locations) ? userData.locations
|
|
269
|
+
const locations = Array.isArray(userData.locations) ? userData.locations : [];
|
|
259
270
|
setListItems(locations.map((loc, i) => ({
|
|
260
271
|
id: String(loc.id || `location-${i}`),
|
|
261
272
|
name: String(loc.name || ''),
|
|
262
273
|
...loc,
|
|
263
274
|
})));
|
|
264
275
|
} else if (fieldType === 'links') {
|
|
265
|
-
const linksMetadata = Array.isArray(userData.linksMetadata) ? userData.linksMetadata
|
|
276
|
+
const linksMetadata = Array.isArray(userData.linksMetadata) ? userData.linksMetadata : [];
|
|
266
277
|
const links = Array.isArray(userData.links) ? userData.links : [];
|
|
267
278
|
// Use linksMetadata if available, otherwise convert links array
|
|
268
279
|
if (linksMetadata.length > 0) {
|
|
269
280
|
setListItems(linksMetadata.map((link, i) => ({
|
|
281
|
+
...link,
|
|
270
282
|
id: String(link.id || `link-${i}`),
|
|
271
|
-
url: String(link.url ||
|
|
283
|
+
url: String(link.url || ''),
|
|
272
284
|
title: String(link.title || ''),
|
|
273
|
-
...link,
|
|
274
285
|
})));
|
|
275
286
|
} else {
|
|
276
287
|
setListItems(links.map((item, i) => {
|
|
277
|
-
const url = typeof item === 'string' ? item : (item.link || '');
|
|
278
288
|
return {
|
|
279
289
|
id: `link-${i}`,
|
|
280
|
-
url,
|
|
281
|
-
title:
|
|
290
|
+
url: item,
|
|
291
|
+
title: item.replace(/^https?:\/\//, '').replace(/\/$/, ''),
|
|
282
292
|
};
|
|
283
293
|
}));
|
|
284
294
|
}
|
|
@@ -286,8 +296,8 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
286
296
|
} else {
|
|
287
297
|
const initialValues: Record<string, string> = {};
|
|
288
298
|
fieldConfig.fields.forEach(field => {
|
|
289
|
-
if (field.key === '
|
|
290
|
-
initialValues[field.key] = String(userData.
|
|
299
|
+
if (field.key === 'firstName') {
|
|
300
|
+
initialValues[field.key] = String(userData.name?.first || '');
|
|
291
301
|
} else if (field.key === 'lastName') {
|
|
292
302
|
initialValues[field.key] = String(userData.lastName || userData.name?.last || '');
|
|
293
303
|
} else if (field.key === 'birthday') {
|
|
@@ -390,7 +400,7 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
390
400
|
let success = false;
|
|
391
401
|
if (fieldType === 'displayName') {
|
|
392
402
|
success = await saveProfile({
|
|
393
|
-
|
|
403
|
+
firstName: fieldValues.firstName,
|
|
394
404
|
lastName: fieldValues.lastName,
|
|
395
405
|
});
|
|
396
406
|
} else {
|
|
@@ -496,7 +506,7 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
496
506
|
<Text style={[styles.listTitle, { color: bloomTheme.colors.text }]}>
|
|
497
507
|
{listTitle} ({listItems.length})
|
|
498
508
|
</Text>
|
|
499
|
-
{listItems.map((item
|
|
509
|
+
{listItems.map((item) => (
|
|
500
510
|
<View
|
|
501
511
|
key={item.id}
|
|
502
512
|
style={[
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from 'react-native';
|
|
14
14
|
import type { BaseScreenProps } from '../types/navigation';
|
|
15
15
|
import { useThemeColors } from '../styles/theme';
|
|
16
|
-
import { normalizeTheme } from '
|
|
16
|
+
import { normalizeTheme } from '@oxyhq/core';
|
|
17
17
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
18
18
|
import { Ionicons } from '@expo/vector-icons';
|
|
19
19
|
import { toast } from '@oxyhq/bloom';
|
|
@@ -13,7 +13,7 @@ import { useI18n } from '../hooks/useI18n';
|
|
|
13
13
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
14
14
|
import { useColorScheme } from '../hooks/useColorScheme';
|
|
15
15
|
import { Colors } from '../constants/theme';
|
|
16
|
-
import { normalizeColorScheme } from '
|
|
16
|
+
import { normalizeColorScheme } from '@oxyhq/core';
|
|
17
17
|
import { SettingsListGroup, SettingsListItem } from '@oxyhq/bloom/settings-list';
|
|
18
18
|
|
|
19
19
|
const HelpSupportScreen: React.FC<BaseScreenProps> = ({
|