@oxyhq/services 5.12.11 → 5.13.1
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/core/OxyServices.js +86 -8
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/i18n/index.js +37 -1
- package/lib/commonjs/i18n/index.js.map +1 -1
- package/lib/commonjs/i18n/locales/ar-SA.json +128 -0
- package/lib/commonjs/i18n/locales/ca-ES.json +128 -0
- package/lib/commonjs/i18n/locales/de-DE.json +128 -0
- package/lib/commonjs/i18n/locales/en-US.json +85 -12
- package/lib/commonjs/i18n/locales/es-ES.json +58 -6
- package/lib/commonjs/i18n/locales/fr-FR.json +128 -0
- package/lib/commonjs/i18n/locales/it-IT.json +128 -0
- package/lib/commonjs/i18n/locales/ja-JP.json +127 -0
- package/lib/commonjs/i18n/locales/ko-KR.json +128 -0
- package/lib/commonjs/i18n/locales/pt-PT.json +128 -0
- package/lib/commonjs/i18n/locales/zh-CN.json +128 -0
- package/lib/commonjs/ui/components/FontLoader.js +22 -42
- package/lib/commonjs/ui/components/FontLoader.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +5 -8
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/StepBasedScreen.js +64 -44
- package/lib/commonjs/ui/components/StepBasedScreen.js.map +1 -1
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +14 -35
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -1
- package/lib/commonjs/ui/components/internal/PinInput.js +2 -2
- package/lib/commonjs/ui/components/internal/PinInput.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +434 -321
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +56 -5
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +43 -39
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +139 -125
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +2 -4
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js +45 -25
- package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverResetPasswordStep.js +88 -53
- package/lib/commonjs/ui/screens/steps/RecoverResetPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js +79 -58
- package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js +61 -52
- package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +220 -31
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInTotpStep.js +77 -50
- package/lib/commonjs/ui/screens/steps/SignInTotpStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +527 -66
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js +55 -30
- package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js +64 -46
- package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js +84 -146
- package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js +113 -34
- package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js.map +1 -1
- package/lib/commonjs/ui/stores/authStore.js +16 -20
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/commonjs/ui/styles/authStyles.js +2 -1
- package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
- package/lib/commonjs/ui/styles/index.js +11 -0
- package/lib/commonjs/ui/styles/index.js.map +1 -1
- package/lib/commonjs/ui/styles/spacing.js +51 -0
- package/lib/commonjs/ui/styles/spacing.js.map +1 -0
- package/lib/commonjs/utils/validationUtils.js +1 -1
- package/lib/module/core/OxyServices.js +86 -8
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/i18n/index.js +37 -1
- package/lib/module/i18n/index.js.map +1 -1
- package/lib/module/i18n/locales/ar-SA.json +128 -0
- package/lib/module/i18n/locales/ca-ES.json +128 -0
- package/lib/module/i18n/locales/de-DE.json +128 -0
- package/lib/module/i18n/locales/en-US.json +85 -12
- package/lib/module/i18n/locales/es-ES.json +58 -6
- package/lib/module/i18n/locales/fr-FR.json +128 -0
- package/lib/module/i18n/locales/it-IT.json +128 -0
- package/lib/module/i18n/locales/ja-JP.json +127 -0
- package/lib/module/i18n/locales/ko-KR.json +128 -0
- package/lib/module/i18n/locales/pt-PT.json +128 -0
- package/lib/module/i18n/locales/zh-CN.json +128 -0
- package/lib/module/ui/components/FontLoader.js +23 -43
- package/lib/module/ui/components/FontLoader.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +6 -8
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/StepBasedScreen.js +65 -45
- package/lib/module/ui/components/StepBasedScreen.js.map +1 -1
- package/lib/module/ui/components/internal/GroupedPillButtons.js +14 -35
- package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -1
- package/lib/module/ui/components/internal/PinInput.js +2 -2
- package/lib/module/ui/components/internal/PinInput.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +434 -321
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +56 -5
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +44 -40
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +138 -126
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInUsernameStep.js +2 -4
- package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverRequestStep.js +45 -25
- package/lib/module/ui/screens/steps/RecoverRequestStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverResetPasswordStep.js +89 -54
- package/lib/module/ui/screens/steps/RecoverResetPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverSuccessStep.js +80 -59
- package/lib/module/ui/screens/steps/RecoverSuccessStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverVerifyStep.js +62 -53
- package/lib/module/ui/screens/steps/RecoverVerifyStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInPasswordStep.js +221 -32
- package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInTotpStep.js +78 -51
- package/lib/module/ui/screens/steps/SignInTotpStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInUsernameStep.js +530 -68
- package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpIdentityStep.js +55 -30
- package/lib/module/ui/screens/steps/SignUpIdentityStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpSecurityStep.js +65 -47
- package/lib/module/ui/screens/steps/SignUpSecurityStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpSummaryStep.js +84 -146
- package/lib/module/ui/screens/steps/SignUpSummaryStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpWelcomeStep.js +114 -35
- package/lib/module/ui/screens/steps/SignUpWelcomeStep.js.map +1 -1
- package/lib/module/ui/stores/authStore.js +16 -20
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/module/ui/styles/authStyles.js +2 -1
- package/lib/module/ui/styles/authStyles.js.map +1 -1
- package/lib/module/ui/styles/index.js +1 -0
- package/lib/module/ui/styles/index.js.map +1 -1
- package/lib/module/ui/styles/spacing.js +48 -0
- package/lib/module/ui/styles/spacing.js.map +1 -0
- package/lib/module/utils/validationUtils.js +1 -1
- package/lib/typescript/core/OxyServices.d.ts +38 -2
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/i18n/index.d.ts.map +1 -1
- package/lib/typescript/ui/components/FontLoader.d.ts +3 -3
- package/lib/typescript/ui/components/FontLoader.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts +2 -2
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/components/StepBasedScreen.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts +10 -0
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverRequestStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverResetPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverSuccessStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverVerifyStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts +2 -0
- package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInTotpStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpIdentityStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpSecurityStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpSummaryStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpWelcomeStep.d.ts.map +1 -1
- package/lib/typescript/ui/stores/authStore.d.ts +7 -3
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/ui/styles/authStyles.d.ts +1 -0
- package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
- package/lib/typescript/ui/styles/index.d.ts +1 -0
- package/lib/typescript/ui/styles/index.d.ts.map +1 -1
- package/lib/typescript/ui/styles/spacing.d.ts +43 -0
- package/lib/typescript/ui/styles/spacing.d.ts.map +1 -0
- package/lib/typescript/utils/validationUtils.d.ts +1 -1
- package/package.json +1 -1
- package/src/core/OxyServices.ts +96 -10
- package/src/i18n/index.ts +36 -0
- package/src/i18n/locales/ar-SA.json +128 -0
- package/src/i18n/locales/ca-ES.json +128 -0
- package/src/i18n/locales/de-DE.json +128 -0
- package/src/i18n/locales/en-US.json +85 -12
- package/src/i18n/locales/es-ES.json +58 -6
- package/src/i18n/locales/fr-FR.json +128 -0
- package/src/i18n/locales/it-IT.json +128 -0
- package/src/i18n/locales/ja-JP.json +127 -0
- package/src/i18n/locales/ko-KR.json +128 -0
- package/src/i18n/locales/pt-PT.json +128 -0
- package/src/i18n/locales/zh-CN.json +128 -0
- package/src/ui/components/FontLoader.tsx +17 -37
- package/src/ui/components/OxyProvider.tsx +14 -13
- package/src/ui/components/StepBasedScreen.tsx +66 -43
- package/src/ui/components/internal/GroupedPillButtons.tsx +15 -31
- package/src/ui/components/internal/PinInput.tsx +2 -2
- package/src/ui/context/OxyContext.tsx +404 -285
- package/src/ui/screens/FileManagementScreen.tsx +81 -4
- package/src/ui/screens/SignInScreen.tsx +59 -36
- package/src/ui/screens/WelcomeNewUserScreen.tsx +102 -91
- package/src/ui/screens/internal/SignInUsernameStep.tsx +1 -1
- package/src/ui/screens/steps/RecoverRequestStep.tsx +34 -24
- package/src/ui/screens/steps/RecoverResetPasswordStep.tsx +65 -36
- package/src/ui/screens/steps/RecoverSuccessStep.tsx +71 -47
- package/src/ui/screens/steps/RecoverVerifyStep.tsx +60 -50
- package/src/ui/screens/steps/SignInPasswordStep.tsx +191 -29
- package/src/ui/screens/steps/SignInTotpStep.tsx +68 -34
- package/src/ui/screens/steps/SignInUsernameStep.tsx +586 -57
- package/src/ui/screens/steps/SignUpIdentityStep.tsx +49 -35
- package/src/ui/screens/steps/SignUpSecurityStep.tsx +56 -39
- package/src/ui/screens/steps/SignUpSummaryStep.tsx +99 -89
- package/src/ui/screens/steps/SignUpWelcomeStep.tsx +88 -20
- package/src/ui/stores/authStore.ts +15 -19
- package/src/ui/styles/authStyles.ts +2 -1
- package/src/ui/styles/index.ts +1 -0
- package/src/ui/styles/spacing.ts +46 -0
- package/src/utils/validationUtils.ts +1 -1
|
@@ -55,6 +55,16 @@ export interface FileManagementScreenProps extends BaseScreenProps {
|
|
|
55
55
|
* Useful for third-party apps that want files to be public (e.g., GIF selector)
|
|
56
56
|
*/
|
|
57
57
|
defaultVisibility?: 'private' | 'public' | 'unlisted';
|
|
58
|
+
/**
|
|
59
|
+
* Link context for tracking file usage by third-party apps
|
|
60
|
+
* When provided, selected files will be linked to this entity
|
|
61
|
+
*/
|
|
62
|
+
linkContext?: {
|
|
63
|
+
app: string; // App identifier (e.g., 'chat-app', 'post-composer')
|
|
64
|
+
entityType: string; // Type of entity (e.g., 'message', 'post', 'profile')
|
|
65
|
+
entityId: string; // Unique ID of the entity using this file
|
|
66
|
+
webhookUrl?: string; // Optional webhook URL to receive file events
|
|
67
|
+
};
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
// Add this helper function near the top (after imports):
|
|
@@ -79,6 +89,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
79
89
|
afterSelect = 'close',
|
|
80
90
|
allowUploadInSelectMode = true,
|
|
81
91
|
defaultVisibility = 'private',
|
|
92
|
+
linkContext,
|
|
82
93
|
}) => {
|
|
83
94
|
const { user, oxyServices } = useOxy();
|
|
84
95
|
|
|
@@ -139,7 +150,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
139
150
|
}
|
|
140
151
|
}, [initialSelectedIds]);
|
|
141
152
|
|
|
142
|
-
const toggleSelect = useCallback((file: FileMetadata) => {
|
|
153
|
+
const toggleSelect = useCallback(async (file: FileMetadata) => {
|
|
143
154
|
if (!selectMode) return;
|
|
144
155
|
if (disabledMimeTypes.length) {
|
|
145
156
|
const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : mt + '/'));
|
|
@@ -148,6 +159,37 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
148
159
|
return;
|
|
149
160
|
}
|
|
150
161
|
}
|
|
162
|
+
|
|
163
|
+
// Update file visibility if it differs from defaultVisibility
|
|
164
|
+
const fileVisibility = (file.metadata as any)?.visibility || 'private';
|
|
165
|
+
if (fileVisibility !== defaultVisibility) {
|
|
166
|
+
try {
|
|
167
|
+
await oxyServices.assetUpdateVisibility(file.id, defaultVisibility);
|
|
168
|
+
console.log(`Updated file ${file.id} visibility from ${fileVisibility} to ${defaultVisibility}`);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Failed to update file visibility:', error);
|
|
171
|
+
// Continue anyway - selection shouldn't fail if visibility update fails
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Link file to entity if linkContext is provided
|
|
176
|
+
if (linkContext) {
|
|
177
|
+
try {
|
|
178
|
+
await oxyServices.assetLink(
|
|
179
|
+
file.id,
|
|
180
|
+
linkContext.app,
|
|
181
|
+
linkContext.entityType,
|
|
182
|
+
linkContext.entityId,
|
|
183
|
+
defaultVisibility,
|
|
184
|
+
(linkContext as any).webhookUrl
|
|
185
|
+
);
|
|
186
|
+
console.log(`Linked file ${file.id} to ${linkContext.app}/${linkContext.entityType}/${linkContext.entityId}`);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('Failed to link file:', error);
|
|
189
|
+
// Continue anyway - selection shouldn't fail if linking fails
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
151
193
|
if (!multiSelect) {
|
|
152
194
|
onSelect?.(file);
|
|
153
195
|
if (afterSelect === 'back') {
|
|
@@ -171,16 +213,51 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
171
213
|
}
|
|
172
214
|
return next;
|
|
173
215
|
});
|
|
174
|
-
}, [selectMode, multiSelect, onSelect, onClose, goBack, disabledMimeTypes, maxSelection, afterSelect]);
|
|
216
|
+
}, [selectMode, multiSelect, onSelect, onClose, goBack, disabledMimeTypes, maxSelection, afterSelect, defaultVisibility, oxyServices, linkContext]);
|
|
175
217
|
|
|
176
|
-
const confirmMultiSelection = useCallback(() => {
|
|
218
|
+
const confirmMultiSelection = useCallback(async () => {
|
|
177
219
|
if (!selectMode || !multiSelect) return;
|
|
178
220
|
const map: Record<string, FileMetadata> = {};
|
|
179
221
|
files.forEach(f => { map[f.id] = f; });
|
|
180
222
|
const chosen = Array.from(selectedIds).map(id => map[id]).filter(Boolean);
|
|
223
|
+
|
|
224
|
+
// Update visibility and link files if needed
|
|
225
|
+
const updatePromises = chosen.map(async (file) => {
|
|
226
|
+
// Update visibility if needed
|
|
227
|
+
const fileVisibility = (file.metadata as any)?.visibility || 'private';
|
|
228
|
+
if (fileVisibility !== defaultVisibility) {
|
|
229
|
+
try {
|
|
230
|
+
await oxyServices.assetUpdateVisibility(file.id, defaultVisibility);
|
|
231
|
+
console.log(`Updated file ${file.id} visibility from ${fileVisibility} to ${defaultVisibility}`);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error(`Failed to update visibility for ${file.id}:`, error);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Link file to entity if linkContext provided
|
|
238
|
+
if (linkContext) {
|
|
239
|
+
try {
|
|
240
|
+
await oxyServices.assetLink(
|
|
241
|
+
file.id,
|
|
242
|
+
linkContext.app,
|
|
243
|
+
linkContext.entityType,
|
|
244
|
+
linkContext.entityId,
|
|
245
|
+
defaultVisibility,
|
|
246
|
+
(linkContext as any).webhookUrl
|
|
247
|
+
);
|
|
248
|
+
console.log(`Linked file ${file.id} to ${linkContext.app}/${linkContext.entityType}/${linkContext.entityId}`);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error(`Failed to link file ${file.id}:`, error);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Wait for all updates (but don't block on failures)
|
|
256
|
+
await Promise.allSettled(updatePromises);
|
|
257
|
+
|
|
181
258
|
onConfirmSelection?.(chosen);
|
|
182
259
|
onClose?.();
|
|
183
|
-
}, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose]);
|
|
260
|
+
}, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose, defaultVisibility, oxyServices, linkContext]);
|
|
184
261
|
|
|
185
262
|
const endUpload = useCallback(() => {
|
|
186
263
|
const started = uploadStartRef.current;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import { useState,
|
|
2
|
+
import { useState, useMemo, useCallback } from 'react';
|
|
3
3
|
import type { BaseScreenProps } from '../navigation/types';
|
|
4
4
|
import { useOxy } from '../context/OxyContext';
|
|
5
5
|
import { useThemeColors } from '../styles';
|
|
@@ -29,11 +29,9 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
29
29
|
const [validationStatus, setValidationStatus] = useState<'idle' | 'validating' | 'valid' | 'invalid'>(
|
|
30
30
|
initialUserProfile ? 'valid' : 'idle'
|
|
31
31
|
);
|
|
32
|
+
const [existingSession, setExistingSession] = useState<any>(null);
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
const validationCache = useRef<Map<string, { profile: any }>>(new Map());
|
|
35
|
-
|
|
36
|
-
const { login, completeMfaLogin, isLoading, user, isAuthenticated, sessions, oxyServices } = useOxy();
|
|
34
|
+
const { login, completeMfaLogin, isLoading, user, isAuthenticated, sessions, oxyServices, switchSession } = useOxy();
|
|
37
35
|
|
|
38
36
|
// Only log props in development mode to reduce console noise
|
|
39
37
|
if (__DEV__) {
|
|
@@ -68,14 +66,13 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
68
66
|
return false;
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
if (__DEV__) console.log('
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return true;
|
|
69
|
+
const offlineDetected = typeof navigator !== 'undefined' && navigator.onLine === false;
|
|
70
|
+
|
|
71
|
+
if (offlineDetected) {
|
|
72
|
+
if (__DEV__) console.log('⚠️ Offline detected, skipping username validation');
|
|
73
|
+
setValidationStatus('invalid');
|
|
74
|
+
setErrorMessage('No connection. Check your internet connection and try again.');
|
|
75
|
+
return false;
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
if (__DEV__) console.log('🔄 Validating username with API...');
|
|
@@ -97,14 +94,24 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
97
94
|
|
|
98
95
|
if (__DEV__) console.log('✅ Username is valid:', profileData);
|
|
99
96
|
setUserProfile(profileData);
|
|
97
|
+
|
|
98
|
+
// Check if this account is already signed in
|
|
99
|
+
const profileUserId = profile.id?.toString();
|
|
100
|
+
const existing = sessions?.find(s => {
|
|
101
|
+
const sessionUserId = s.userId?.toString();
|
|
102
|
+
return sessionUserId === profileUserId;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (existing) {
|
|
106
|
+
setExistingSession(existing);
|
|
107
|
+
if (__DEV__) console.log('✅ Account already signed in:', existing);
|
|
108
|
+
} else {
|
|
109
|
+
setExistingSession(null);
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
setValidationStatus('valid');
|
|
101
113
|
setErrorMessage('');
|
|
102
114
|
|
|
103
|
-
// Cache the result
|
|
104
|
-
validationCache.current.set(usernameToValidate, {
|
|
105
|
-
profile: profileData
|
|
106
|
-
});
|
|
107
|
-
|
|
108
115
|
return true;
|
|
109
116
|
} else {
|
|
110
117
|
if (__DEV__) console.log('❌ Username not found');
|
|
@@ -116,25 +123,29 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
116
123
|
if (__DEV__) console.log('🚨 Validation error:', error);
|
|
117
124
|
|
|
118
125
|
// If user not found (404), username doesn't exist
|
|
119
|
-
if (error
|
|
126
|
+
if (error?.status === 404 || error?.code === 'USER_NOT_FOUND') {
|
|
120
127
|
console.log('❌ Username not found (404)');
|
|
121
128
|
setValidationStatus('invalid');
|
|
122
129
|
setErrorMessage('Username not found.');
|
|
123
130
|
return false;
|
|
124
131
|
}
|
|
125
132
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
const isNetworkError =
|
|
134
|
+
error?.status === 0 ||
|
|
135
|
+
error?.code === 'ECONNABORTED' ||
|
|
136
|
+
error?.code === 'ERR_NETWORK' ||
|
|
137
|
+
error?.message?.toLowerCase?.().includes('network request failed') ||
|
|
138
|
+
error?.message?.toLowerCase?.().includes('network error') ||
|
|
139
|
+
error?.name === 'AbortError' ||
|
|
140
|
+
error?.type === 'network';
|
|
133
141
|
|
|
134
|
-
// For other errors, show generic message
|
|
135
142
|
console.error('Username validation error:', error);
|
|
136
143
|
setValidationStatus('invalid');
|
|
137
|
-
setErrorMessage(
|
|
144
|
+
setErrorMessage(
|
|
145
|
+
isNetworkError
|
|
146
|
+
? 'No connection. Check your internet connection and try again.'
|
|
147
|
+
: 'Unable to validate username. Please try again.'
|
|
148
|
+
);
|
|
138
149
|
return false;
|
|
139
150
|
} finally {
|
|
140
151
|
setIsValidating(false);
|
|
@@ -177,6 +188,22 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
177
188
|
|
|
178
189
|
const [mfaToken, setMfaToken] = useState<string | null>(null);
|
|
179
190
|
|
|
191
|
+
const handleContinueWithExistingAccount = useCallback(async () => {
|
|
192
|
+
if (!existingSession) return;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
setErrorMessage('');
|
|
196
|
+
await switchSession(existingSession.sessionId);
|
|
197
|
+
// Get the user for the authenticated callback
|
|
198
|
+
const currentUser = await oxyServices.getUserBySession(existingSession.sessionId);
|
|
199
|
+
if (onAuthenticated) {
|
|
200
|
+
onAuthenticated(currentUser);
|
|
201
|
+
}
|
|
202
|
+
} catch (error: any) {
|
|
203
|
+
setErrorMessage(error.message || 'Failed to switch account');
|
|
204
|
+
}
|
|
205
|
+
}, [existingSession, switchSession, oxyServices, onAuthenticated]);
|
|
206
|
+
|
|
180
207
|
const handleSignIn = useCallback(async () => {
|
|
181
208
|
if (!password) {
|
|
182
209
|
setErrorMessage('Please enter your password.');
|
|
@@ -186,6 +213,7 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
186
213
|
setErrorMessage('Please enter a valid username first.');
|
|
187
214
|
return;
|
|
188
215
|
}
|
|
216
|
+
|
|
189
217
|
try {
|
|
190
218
|
setErrorMessage('');
|
|
191
219
|
const user = await login(username, password);
|
|
@@ -201,13 +229,6 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
201
229
|
}
|
|
202
230
|
}, [username, password, login, onAuthenticated, userProfile]);
|
|
203
231
|
|
|
204
|
-
// Simple cleanup on unmount - that's all we need for username validation
|
|
205
|
-
useEffect(() => {
|
|
206
|
-
return () => {
|
|
207
|
-
validationCache.current.clear();
|
|
208
|
-
};
|
|
209
|
-
}, []);
|
|
210
|
-
|
|
211
232
|
// Step configurations
|
|
212
233
|
const steps: StepConfig[] = useMemo(() => {
|
|
213
234
|
const base: StepConfig[] = [
|
|
@@ -259,6 +280,8 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
259
280
|
handleInputBlur,
|
|
260
281
|
handleSignIn, // Add sign-in function for password step
|
|
261
282
|
mfaToken,
|
|
283
|
+
existingSession,
|
|
284
|
+
handleContinueWithExistingAccount,
|
|
262
285
|
},
|
|
263
286
|
...(mfaToken ? [{
|
|
264
287
|
username,
|
|
@@ -272,7 +295,7 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
272
295
|
username, password, errorMessage, validationStatus, userProfile, mfaToken,
|
|
273
296
|
isValidating, isInputFocused, isAddAccountMode, user, showPassword,
|
|
274
297
|
isLoading, handleUsernameChange, handlePasswordChange, handleInputFocus, handleInputBlur,
|
|
275
|
-
validateUsername, handleSignIn, completeMfaLogin
|
|
298
|
+
validateUsername, handleSignIn, completeMfaLogin, existingSession, handleContinueWithExistingAccount
|
|
276
299
|
]);
|
|
277
300
|
|
|
278
301
|
return (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react';
|
|
2
2
|
import { View, Text, TouchableOpacity, StyleSheet, Platform, Animated, ScrollView } from 'react-native';
|
|
3
|
+
import AnimatedReanimated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';
|
|
3
4
|
import type { BaseScreenProps } from '../navigation/types';
|
|
4
5
|
import { useOxy } from '../context/OxyContext';
|
|
5
6
|
import Avatar from '../components/Avatar';
|
|
@@ -10,6 +11,41 @@ import { useThemeColors } from '../styles';
|
|
|
10
11
|
import GroupedPillButtons from '../components/internal/GroupedPillButtons';
|
|
11
12
|
import { useI18n } from '../hooks/useI18n';
|
|
12
13
|
|
|
14
|
+
const GAP = 12;
|
|
15
|
+
const INNER_GAP = 8;
|
|
16
|
+
|
|
17
|
+
// Individual animated progress dot
|
|
18
|
+
const AnimatedProgressDot: React.FC<{
|
|
19
|
+
isActive: boolean;
|
|
20
|
+
colors: any;
|
|
21
|
+
styles: any;
|
|
22
|
+
}> = ({ isActive, colors, styles }) => {
|
|
23
|
+
const width = useSharedValue(isActive ? 12 : 6);
|
|
24
|
+
const backgroundColor = useSharedValue(isActive ? colors.primary : colors.border);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
width.value = withTiming(isActive ? 12 : 6, { duration: 300 });
|
|
28
|
+
backgroundColor.value = withTiming(
|
|
29
|
+
isActive ? colors.primary : colors.border,
|
|
30
|
+
{ duration: 300 }
|
|
31
|
+
);
|
|
32
|
+
}, [isActive, colors.primary, colors.border, width, backgroundColor]);
|
|
33
|
+
|
|
34
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
35
|
+
width: width.value,
|
|
36
|
+
backgroundColor: backgroundColor.value,
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<AnimatedReanimated.View
|
|
41
|
+
style={[
|
|
42
|
+
styles.progressDot,
|
|
43
|
+
animatedStyle,
|
|
44
|
+
]}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
13
49
|
/**
|
|
14
50
|
* Post-signup welcome & onboarding screen.
|
|
15
51
|
* - Greets the newly registered user
|
|
@@ -138,33 +174,44 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
|
|
|
138
174
|
<View style={styles.container}>
|
|
139
175
|
<View style={styles.progressContainer}>
|
|
140
176
|
{steps.map((s, i) => (
|
|
141
|
-
<
|
|
177
|
+
<AnimatedProgressDot
|
|
178
|
+
key={s.key}
|
|
179
|
+
isActive={i === currentStep}
|
|
180
|
+
colors={colors}
|
|
181
|
+
styles={styles}
|
|
182
|
+
/>
|
|
142
183
|
))}
|
|
143
184
|
</View>
|
|
144
185
|
<Animated.View style={{ opacity: fadeAnim, transform: [{ translateX: slideAnim }] }}>
|
|
145
186
|
<ScrollView contentContainerStyle={styles.scrollInner} showsVerticalScrollIndicator={false}>
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
{step.bullets.map(b => (
|
|
151
|
-
<View key={b} style={styles.bulletRow}>
|
|
152
|
-
<Ionicons name="ellipse" size={8} color={colors.primary} style={{ marginTop: 6 }} />
|
|
153
|
-
<Text style={styles.bulletText}>{b}</Text>
|
|
154
|
-
</View>
|
|
155
|
-
))}
|
|
187
|
+
<View style={styles.contentContainer}>
|
|
188
|
+
<View style={[styles.header, styles.sectionSpacing]}>
|
|
189
|
+
<Text style={[styles.title, { color: colors.text }]}>{step.title}</Text>
|
|
190
|
+
{step.body && <Text style={[styles.body, { color: colors.secondaryText }]}>{step.body}</Text>}
|
|
156
191
|
</View>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
192
|
+
{Array.isArray(step.bullets) && step.bullets.length > 0 && (
|
|
193
|
+
<View style={[styles.bulletContainer, styles.sectionSpacing]}>
|
|
194
|
+
{step.bullets.map(b => (
|
|
195
|
+
<View key={b} style={styles.bulletRow}>
|
|
196
|
+
<Ionicons name="ellipse" size={8} color={colors.primary} style={{ marginTop: 6 }} />
|
|
197
|
+
<Text style={[styles.bulletText, { color: colors.secondaryText }]}>{b}</Text>
|
|
198
|
+
</View>
|
|
199
|
+
))}
|
|
200
|
+
</View>
|
|
201
|
+
)}
|
|
202
|
+
{step.showAvatar && (
|
|
203
|
+
<View style={[styles.avatarSection, styles.sectionSpacing]}>
|
|
204
|
+
<Avatar size={120} name={currentUser?.username} uri={avatarUri} theme={theme} style={styles.avatar} />
|
|
205
|
+
<TouchableOpacity style={[styles.changeAvatarButton, { backgroundColor: colors.primary }]} onPress={openAvatarPicker}>
|
|
206
|
+
<Ionicons name="image-outline" size={18} color="#FFFFFF" />
|
|
207
|
+
<Text style={styles.changeAvatarText}>{avatarUri ? (t('welcomeNew.avatar.change') || 'Change Avatar') : (t('welcomeNew.avatar.add') || 'Add Avatar')}</Text>
|
|
208
|
+
</TouchableOpacity>
|
|
209
|
+
</View>
|
|
210
|
+
)}
|
|
211
|
+
<View style={styles.sectionSpacing}>
|
|
212
|
+
<GroupedPillButtons buttons={pillButtons} colors={colors} />
|
|
165
213
|
</View>
|
|
166
|
-
|
|
167
|
-
<GroupedPillButtons buttons={pillButtons} colors={colors} />
|
|
214
|
+
</View>
|
|
168
215
|
</ScrollView>
|
|
169
216
|
</Animated.View>
|
|
170
217
|
</View>
|
|
@@ -174,11 +221,7 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
|
|
|
174
221
|
|
|
175
222
|
const createStyles = (theme: string) => {
|
|
176
223
|
const isDark = theme === 'dark';
|
|
177
|
-
const textColor = isDark ? '#FFFFFF' : '#000000';
|
|
178
|
-
const secondary = isDark ? '#CCCCCC' : '#555555';
|
|
179
|
-
const cardBg = isDark ? '#1E1E1E' : '#FFFFFF';
|
|
180
224
|
const border = isDark ? '#333333' : '#E0E0E0';
|
|
181
|
-
const primary = '#007AFF';
|
|
182
225
|
return StyleSheet.create({
|
|
183
226
|
container: {
|
|
184
227
|
width: '100%',
|
|
@@ -186,25 +229,38 @@ const createStyles = (theme: string) => {
|
|
|
186
229
|
},
|
|
187
230
|
scrollInner: {
|
|
188
231
|
paddingBottom: 12,
|
|
232
|
+
paddingTop: 0,
|
|
233
|
+
},
|
|
234
|
+
contentContainer: {
|
|
235
|
+
width: '100%',
|
|
236
|
+
maxWidth: 420,
|
|
237
|
+
alignSelf: 'center',
|
|
238
|
+
},
|
|
239
|
+
sectionSpacing: {
|
|
240
|
+
marginBottom: GAP,
|
|
241
|
+
},
|
|
242
|
+
header: {
|
|
243
|
+
alignItems: 'flex-start',
|
|
244
|
+
width: '100%',
|
|
245
|
+
gap: INNER_GAP / 2,
|
|
189
246
|
},
|
|
190
247
|
title: {
|
|
191
248
|
fontSize: 42,
|
|
192
249
|
fontFamily: Platform.OS === 'web' ? 'Phudu' : 'Phudu-Bold',
|
|
193
250
|
fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
|
|
194
251
|
letterSpacing: -1,
|
|
195
|
-
|
|
196
|
-
marginBottom: 12,
|
|
252
|
+
textAlign: 'left',
|
|
197
253
|
},
|
|
198
254
|
body: {
|
|
199
255
|
fontSize: 16,
|
|
200
256
|
lineHeight: 22,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
257
|
+
textAlign: 'left',
|
|
258
|
+
maxWidth: 320,
|
|
259
|
+
alignSelf: 'flex-start',
|
|
204
260
|
},
|
|
205
261
|
bulletContainer: {
|
|
206
|
-
gap:
|
|
207
|
-
|
|
262
|
+
gap: INNER_GAP,
|
|
263
|
+
width: '100%',
|
|
208
264
|
},
|
|
209
265
|
bulletRow: {
|
|
210
266
|
flexDirection: 'row',
|
|
@@ -215,26 +271,26 @@ const createStyles = (theme: string) => {
|
|
|
215
271
|
flex: 1,
|
|
216
272
|
fontSize: 15,
|
|
217
273
|
lineHeight: 20,
|
|
218
|
-
color: secondary,
|
|
219
274
|
},
|
|
220
275
|
avatarSection: {
|
|
221
276
|
width: '100%',
|
|
222
277
|
alignItems: 'center',
|
|
223
|
-
marginBottom: 40,
|
|
224
278
|
},
|
|
225
279
|
avatar: {
|
|
226
|
-
marginBottom:
|
|
227
|
-
borderWidth: 4,
|
|
228
|
-
borderColor: primary + '40',
|
|
280
|
+
marginBottom: INNER_GAP,
|
|
229
281
|
},
|
|
230
282
|
changeAvatarButton: {
|
|
231
283
|
flexDirection: 'row',
|
|
232
284
|
alignItems: 'center',
|
|
233
|
-
backgroundColor: primary,
|
|
234
285
|
paddingHorizontal: 18,
|
|
235
286
|
paddingVertical: 10,
|
|
236
|
-
borderRadius:
|
|
287
|
+
borderRadius: 28,
|
|
237
288
|
gap: 8,
|
|
289
|
+
shadowOpacity: 0,
|
|
290
|
+
shadowRadius: 0,
|
|
291
|
+
shadowOffset: { width: 0, height: 0 },
|
|
292
|
+
elevation: 0,
|
|
293
|
+
...(Platform.OS === 'web' ? { boxShadow: 'none' } : null),
|
|
238
294
|
},
|
|
239
295
|
changeAvatarText: {
|
|
240
296
|
color: '#FFFFFF',
|
|
@@ -245,61 +301,16 @@ const createStyles = (theme: string) => {
|
|
|
245
301
|
flexDirection: 'row',
|
|
246
302
|
width: '100%',
|
|
247
303
|
justifyContent: 'center',
|
|
248
|
-
|
|
249
|
-
|
|
304
|
+
marginTop: 24, // Space for bottom sheet handle (~20px) + small buffer
|
|
305
|
+
marginBottom: 24, // Equal spacing below dots
|
|
250
306
|
},
|
|
251
307
|
progressDot: {
|
|
252
|
-
height:
|
|
253
|
-
width:
|
|
254
|
-
borderRadius:
|
|
255
|
-
marginHorizontal:
|
|
308
|
+
height: 6,
|
|
309
|
+
width: 6,
|
|
310
|
+
borderRadius: 3,
|
|
311
|
+
marginHorizontal: 3,
|
|
256
312
|
backgroundColor: border,
|
|
257
313
|
},
|
|
258
|
-
navBar: {
|
|
259
|
-
flexDirection: 'row',
|
|
260
|
-
alignItems: 'center',
|
|
261
|
-
width: '100%',
|
|
262
|
-
gap: 12,
|
|
263
|
-
marginTop: 8,
|
|
264
|
-
},
|
|
265
|
-
navButton: {
|
|
266
|
-
flexDirection: 'row',
|
|
267
|
-
alignItems: 'center',
|
|
268
|
-
gap: 6,
|
|
269
|
-
paddingHorizontal: 14,
|
|
270
|
-
paddingVertical: 10,
|
|
271
|
-
borderRadius: 12,
|
|
272
|
-
},
|
|
273
|
-
backButton: {
|
|
274
|
-
backgroundColor: cardBg,
|
|
275
|
-
borderWidth: 1,
|
|
276
|
-
borderColor: border,
|
|
277
|
-
},
|
|
278
|
-
skipButton: {
|
|
279
|
-
marginLeft: 'auto',
|
|
280
|
-
backgroundColor: 'transparent',
|
|
281
|
-
paddingHorizontal: 4,
|
|
282
|
-
},
|
|
283
|
-
navText: {
|
|
284
|
-
fontSize: 14,
|
|
285
|
-
fontWeight: '500',
|
|
286
|
-
},
|
|
287
|
-
primaryButton: {
|
|
288
|
-
flexDirection: 'row',
|
|
289
|
-
alignItems: 'center',
|
|
290
|
-
justifyContent: 'center',
|
|
291
|
-
gap: 8,
|
|
292
|
-
backgroundColor: primary,
|
|
293
|
-
paddingVertical: 18,
|
|
294
|
-
borderRadius: 16,
|
|
295
|
-
width: '100%',
|
|
296
|
-
},
|
|
297
|
-
primaryButtonText: {
|
|
298
|
-
color: '#FFFFFF',
|
|
299
|
-
fontSize: 16,
|
|
300
|
-
fontWeight: '600',
|
|
301
|
-
letterSpacing: 0.5,
|
|
302
|
-
},
|
|
303
314
|
});
|
|
304
315
|
};
|
|
305
316
|
|
|
@@ -95,7 +95,7 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
|
|
|
95
95
|
<View style={[styles.modernInfoCard, { backgroundColor: colors.inputBackground }]}>
|
|
96
96
|
<Ionicons name="information-circle" size={20} color={colors.primary} />
|
|
97
97
|
<Text style={[styles.modernInfoText, { color: colors.text }]}>
|
|
98
|
-
{t('signin.currentlySignedInAs'
|
|
98
|
+
{t('signin.currentlySignedInAs') || 'Currently signed in as'} <Text style={{ fontWeight: 'bold' }}>{user?.name?.full || user?.username}</Text>
|
|
99
99
|
</Text>
|
|
100
100
|
</View>
|
|
101
101
|
)}
|