@oxyhq/services 14.1.0 → 16.0.0
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/index.js +4 -21
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/SignInAccountChooser.js +189 -0
- package/lib/commonjs/ui/components/SignInAccountChooser.js.map +1 -0
- package/lib/commonjs/ui/components/SignInModal.js +350 -131
- package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +340 -1547
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +61 -64
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/mutationKeys.js +1 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +9 -9
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/queryKeys.js +1 -1
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +7 -8
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAuth.js +14 -33
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js +170 -0
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +1 -0
- package/lib/commonjs/ui/hooks/useProfileEditing.js +2 -5
- package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionManagement.js +2 -2
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/navigation/routes.js +1 -0
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/screens/ConnectedAppsScreen.js +17 -18
- package/lib/commonjs/ui/screens/ConnectedAppsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +93 -68
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/EditProfileScreen.js +187 -0
- package/lib/commonjs/ui/screens/EditProfileScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +3 -7
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +231 -95
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +15 -2
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/linkFormat.js +38 -0
- package/lib/commonjs/ui/screens/linkFormat.js.map +1 -0
- package/lib/commonjs/ui/session/authStore.js +146 -0
- package/lib/commonjs/ui/session/authStore.js.map +1 -0
- package/lib/commonjs/ui/session/createSessionClient.js +23 -17
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
- package/lib/commonjs/ui/session/index.js +13 -7
- package/lib/commonjs/ui/session/index.js.map +1 -1
- package/lib/commonjs/ui/session/tokenTransport.js +19 -31
- package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
- package/lib/commonjs/ui/utils/isWebBrowser.js +13 -0
- package/lib/commonjs/ui/utils/isWebBrowser.js.map +1 -0
- package/lib/commonjs/utils/deviceFlowSignIn.js +13 -16
- package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/module/index.js +2 -7
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/SignInAccountChooser.js +183 -0
- package/lib/module/ui/components/SignInAccountChooser.js.map +1 -0
- package/lib/module/ui/components/SignInModal.js +352 -134
- package/lib/module/ui/components/SignInModal.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +342 -1551
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +60 -63
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/hooks/mutations/mutationKeys.js +1 -1
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +7 -7
- package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/module/ui/hooks/queries/queryKeys.js +1 -1
- package/lib/module/ui/hooks/queries/useAccountQueries.js +5 -6
- package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/module/ui/hooks/useAuth.js +14 -33
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/ui/hooks/usePasswordSignIn.js +165 -0
- package/lib/module/ui/hooks/usePasswordSignIn.js.map +1 -0
- package/lib/module/ui/hooks/useProfileEditing.js +2 -5
- package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/navigation/routes.js +1 -0
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/screens/ConnectedAppsScreen.js +19 -20
- package/lib/module/ui/screens/ConnectedAppsScreen.js.map +1 -1
- package/lib/module/ui/screens/EditProfileFieldScreen.js +91 -66
- package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/module/ui/screens/EditProfileScreen.js +182 -0
- package/lib/module/ui/screens/EditProfileScreen.js.map +1 -0
- package/lib/module/ui/screens/ManageAccountScreen.js +3 -7
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +233 -97
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +15 -2
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/linkFormat.js +31 -0
- package/lib/module/ui/screens/linkFormat.js.map +1 -0
- package/lib/module/ui/session/authStore.js +143 -0
- package/lib/module/ui/session/authStore.js.map +1 -0
- package/lib/module/ui/session/createSessionClient.js +22 -16
- package/lib/module/ui/session/createSessionClient.js.map +1 -1
- package/lib/module/ui/session/index.js +9 -9
- package/lib/module/ui/session/index.js.map +1 -1
- package/lib/module/ui/session/tokenTransport.js +20 -33
- package/lib/module/ui/session/tokenTransport.js.map +1 -1
- package/lib/module/ui/utils/isWebBrowser.js +11 -0
- package/lib/module/ui/utils/isWebBrowser.js.map +1 -0
- package/lib/module/utils/deviceFlowSignIn.js +14 -17
- package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +2 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +29 -0
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +15 -10
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +36 -42
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +18 -23
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +4 -6
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts +6 -7
- package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts +31 -0
- package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts +0 -1
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts +14 -0
- package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +13 -12
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/linkFormat.d.ts +23 -0
- package/lib/typescript/commonjs/ui/screens/linkFormat.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/authStore.d.ts +33 -0
- package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +19 -15
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/index.d.ts +9 -9
- package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +13 -19
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/isWebBrowser.d.ts +9 -0
- package/lib/typescript/commonjs/ui/utils/isWebBrowser.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/utils/storageHelpers.d.ts +7 -7
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +15 -13
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +2 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +29 -0
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/SignInModal.d.ts +15 -10
- package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +36 -42
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +18 -23
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +4 -6
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts +6 -7
- package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts +31 -0
- package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts +0 -1
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts +14 -0
- package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +13 -12
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/linkFormat.d.ts +23 -0
- package/lib/typescript/module/ui/screens/linkFormat.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/authStore.d.ts +33 -0
- package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +19 -15
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/index.d.ts +9 -9
- package/lib/typescript/module/ui/session/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/tokenTransport.d.ts +13 -19
- package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/isWebBrowser.d.ts +9 -0
- package/lib/typescript/module/ui/utils/isWebBrowser.d.ts.map +1 -0
- package/lib/typescript/module/ui/utils/storageHelpers.d.ts +7 -7
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +15 -13
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +2 -6
- package/src/ui/components/SignInAccountChooser.tsx +162 -0
- package/src/ui/components/SignInModal.tsx +309 -139
- package/src/ui/context/OxyContext.tsx +532 -1785
- package/src/ui/context/hooks/useAuthOperations.ts +65 -76
- package/src/ui/hooks/mutations/mutationKeys.ts +1 -1
- package/src/ui/hooks/mutations/useAccountMutations.ts +7 -7
- package/src/ui/hooks/queries/queryKeys.ts +1 -1
- package/src/ui/hooks/queries/useAccountQueries.ts +8 -9
- package/src/ui/hooks/useAuth.ts +14 -35
- package/src/ui/hooks/usePasswordSignIn.ts +207 -0
- package/src/ui/hooks/useProfileEditing.ts +2 -8
- package/src/ui/hooks/useSessionManagement.ts +1 -1
- package/src/ui/navigation/routes.ts +3 -1
- package/src/ui/screens/ConnectedAppsScreen.tsx +23 -24
- package/src/ui/screens/EditProfileFieldScreen.tsx +102 -66
- package/src/ui/screens/EditProfileScreen.tsx +155 -0
- package/src/ui/screens/ManageAccountScreen.tsx +3 -7
- package/src/ui/screens/OxyAuthScreen.tsx +259 -112
- package/src/ui/screens/ProfileScreen.tsx +15 -2
- package/src/ui/screens/__tests__/linkFormat.test.ts +73 -0
- package/src/ui/screens/linkFormat.ts +37 -0
- package/src/ui/session/__tests__/createSessionClient.test.ts +22 -7
- package/src/ui/session/__tests__/tokenTransport.test.ts +37 -49
- package/src/ui/session/authStore.ts +164 -0
- package/src/ui/session/createSessionClient.ts +30 -17
- package/src/ui/session/index.ts +10 -9
- package/src/ui/session/tokenTransport.ts +26 -36
- package/src/ui/utils/isWebBrowser.ts +8 -0
- package/src/ui/utils/storageHelpers.ts +7 -7
- package/src/utils/__tests__/deviceFlowSignIn.test.ts +50 -131
- package/src/utils/deviceFlowSignIn.ts +19 -26
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js +0 -243
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js.map +0 -1
- package/lib/commonjs/ui/context/silentSessionRestore.js +0 -56
- package/lib/commonjs/ui/context/silentSessionRestore.js.map +0 -1
- package/lib/commonjs/ui/hooks/useWebSSO.js +0 -28
- package/lib/commonjs/ui/hooks/useWebSSO.js.map +0 -1
- package/lib/commonjs/ui/session/projectSessionState.js +0 -86
- package/lib/commonjs/ui/session/projectSessionState.js.map +0 -1
- package/lib/commonjs/ui/session/sessionClientHost.js +0 -30
- package/lib/commonjs/ui/session/sessionClientHost.js.map +0 -1
- package/lib/commonjs/ui/utils/activeAuthuser.js +0 -142
- package/lib/commonjs/ui/utils/activeAuthuser.js.map +0 -1
- package/lib/commonjs/utils/crossApex.js +0 -74
- package/lib/commonjs/utils/crossApex.js.map +0 -1
- package/lib/module/ui/context/inSessionTokenRefresh.js +0 -238
- package/lib/module/ui/context/inSessionTokenRefresh.js.map +0 -1
- package/lib/module/ui/context/silentSessionRestore.js +0 -53
- package/lib/module/ui/context/silentSessionRestore.js.map +0 -1
- package/lib/module/ui/hooks/useWebSSO.js +0 -24
- package/lib/module/ui/hooks/useWebSSO.js.map +0 -1
- package/lib/module/ui/session/projectSessionState.js +0 -79
- package/lib/module/ui/session/projectSessionState.js.map +0 -1
- package/lib/module/ui/session/sessionClientHost.js +0 -26
- package/lib/module/ui/session/sessionClientHost.js.map +0 -1
- package/lib/module/ui/utils/activeAuthuser.js +0 -134
- package/lib/module/ui/utils/activeAuthuser.js.map +0 -1
- package/lib/module/utils/crossApex.js +0 -69
- package/lib/module/utils/crossApex.js.map +0 -1
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts +0 -106
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts +0 -36
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +0 -19
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts +0 -45
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts +0 -14
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts +0 -67
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/crossApex.d.ts +0 -55
- package/lib/typescript/commonjs/utils/crossApex.d.ts.map +0 -1
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts +0 -106
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts.map +0 -1
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts +0 -36
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +0 -19
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +0 -1
- package/lib/typescript/module/ui/session/projectSessionState.d.ts +0 -45
- package/lib/typescript/module/ui/session/projectSessionState.d.ts.map +0 -1
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts +0 -14
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts.map +0 -1
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts +0 -67
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts.map +0 -1
- package/lib/typescript/module/utils/crossApex.d.ts +0 -55
- package/lib/typescript/module/utils/crossApex.d.ts.map +0 -1
- package/src/ui/context/inSessionTokenRefresh.ts +0 -264
- package/src/ui/context/silentSessionRestore.ts +0 -53
- package/src/ui/hooks/useWebSSO.ts +0 -23
- package/src/ui/session/__tests__/projectSessionState.test.ts +0 -114
- package/src/ui/session/__tests__/sessionClientHost.test.ts +0 -43
- package/src/ui/session/projectSessionState.ts +0 -85
- package/src/ui/session/sessionClientHost.ts +0 -27
- package/src/ui/utils/activeAuthuser.ts +0 -142
- package/src/utils/crossApex.ts +0 -75
|
@@ -26,6 +26,7 @@ export type RouteName =
|
|
|
26
26
|
| 'UserLinks'
|
|
27
27
|
| 'HistoryView'
|
|
28
28
|
| 'SavesCollections'
|
|
29
|
+
| 'EditProfile' // Profile-editing hub: one row per editable field
|
|
29
30
|
| 'EditProfileField' // Dedicated screen for editing a single profile field
|
|
30
31
|
| 'LearnMoreUsernames' // Informational screen about usernames
|
|
31
32
|
| 'TrustCenter'
|
|
@@ -42,7 +43,7 @@ export type RouteName =
|
|
|
42
43
|
| 'AccountSettings' // Per-account profile edit + members + danger zone
|
|
43
44
|
| 'AvatarCrop' // Square-crop editor presented before avatar upload
|
|
44
45
|
| 'Notifications' // Per-channel notification preferences
|
|
45
|
-
| 'ConnectedApps' //
|
|
46
|
+
| 'ConnectedApps' // OAuth-authorized third-party apps the user can revoke
|
|
46
47
|
| 'Preferences'; // General user preferences (theme, reduce-motion, etc.)
|
|
47
48
|
|
|
48
49
|
// Lazy screen loaders - functions that return screen components on-demand
|
|
@@ -67,6 +68,7 @@ const screenLoaders: Record<RouteName, () => ComponentType<BaseScreenProps>> = {
|
|
|
67
68
|
UserLinks: () => require('../screens/UserLinksScreen').default,
|
|
68
69
|
HistoryView: () => require('../screens/HistoryViewScreen').default,
|
|
69
70
|
SavesCollections: () => require('../screens/SavesCollectionsScreen').default,
|
|
71
|
+
EditProfile: () => require('../screens/EditProfileScreen').default,
|
|
70
72
|
EditProfileField: () => require('../screens/EditProfileFieldScreen').default,
|
|
71
73
|
// Informational screens
|
|
72
74
|
LearnMoreUsernames: () => require('../screens/LearnMoreUsernamesScreen').default,
|
|
@@ -3,7 +3,7 @@ import { ActivityIndicator, FlatList, RefreshControl, StyleSheet, View } from 'r
|
|
|
3
3
|
import { Dialog, toast, useDialogControl } from '@oxyhq/bloom';
|
|
4
4
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
5
5
|
import { SettingsListGroup, SettingsListItem } from '@oxyhq/bloom/settings-list';
|
|
6
|
-
import type {
|
|
6
|
+
import type { ConnectedApp } from '@oxyhq/core';
|
|
7
7
|
import { logger as loggerUtil } from '@oxyhq/core';
|
|
8
8
|
import type { BaseScreenProps } from '../types/navigation';
|
|
9
9
|
import Header from '../components/Header';
|
|
@@ -12,8 +12,8 @@ import EmptyState from '../components/EmptyState';
|
|
|
12
12
|
import LoadingState from '../components/LoadingState';
|
|
13
13
|
import { useI18n } from '../hooks/useI18n';
|
|
14
14
|
import { useOxy } from '../context/OxyContext';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { useConnectedApps } from '../hooks/queries/useAccountQueries';
|
|
16
|
+
import { useRevokeConnectedApp } from '../hooks/mutations/useAccountMutations';
|
|
17
17
|
|
|
18
18
|
const APP_ICON_SIZE = 40;
|
|
19
19
|
|
|
@@ -39,12 +39,11 @@ const formatRelative = (iso: string): string => {
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* ConnectedAppsScreen — list and revoke
|
|
42
|
+
* ConnectedAppsScreen — list and revoke connected OAuth applications.
|
|
43
43
|
*
|
|
44
|
-
* Fetches via `
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* refreshes immediately.
|
|
44
|
+
* Fetches via `useConnectedApps` (drives `GET /auth/grants`) and exposes a
|
|
45
|
+
* "Revoke" action that hits `DELETE /auth/grants/:applicationId`. Each revoke
|
|
46
|
+
* invalidates the connected-apps query so the list refreshes immediately.
|
|
48
47
|
*/
|
|
49
48
|
const ConnectedAppsScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) => {
|
|
50
49
|
const bloomTheme = useTheme();
|
|
@@ -55,14 +54,14 @@ const ConnectedAppsScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) =>
|
|
|
55
54
|
isLoading,
|
|
56
55
|
refetch,
|
|
57
56
|
isRefetching,
|
|
58
|
-
} =
|
|
59
|
-
const revokeMutation =
|
|
57
|
+
} = useConnectedApps({ enabled: isAuthenticated });
|
|
58
|
+
const revokeMutation = useRevokeConnectedApp();
|
|
60
59
|
const revokeDialog = useDialogControl();
|
|
61
|
-
const [pendingRevoke, setPendingRevoke] = useState<
|
|
62
|
-
const [
|
|
60
|
+
const [pendingRevoke, setPendingRevoke] = useState<ConnectedApp | null>(null);
|
|
61
|
+
const [revokingAppId, setRevokingAppId] = useState<string | null>(null);
|
|
63
62
|
|
|
64
63
|
const confirmRevoke = useCallback(
|
|
65
|
-
(app:
|
|
64
|
+
(app: ConnectedApp) => {
|
|
66
65
|
setPendingRevoke(app);
|
|
67
66
|
revokeDialog.open();
|
|
68
67
|
},
|
|
@@ -74,16 +73,16 @@ const ConnectedAppsScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) =>
|
|
|
74
73
|
return;
|
|
75
74
|
}
|
|
76
75
|
const target = pendingRevoke;
|
|
77
|
-
|
|
76
|
+
setRevokingAppId(target.applicationId);
|
|
78
77
|
try {
|
|
79
|
-
await revokeMutation.mutateAsync(target.
|
|
78
|
+
await revokeMutation.mutateAsync(target.applicationId);
|
|
80
79
|
toast.success(
|
|
81
80
|
t('connectedApps.toasts.revoked', { name: target.name })
|
|
82
81
|
|| `Revoked access for ${target.name}`,
|
|
83
82
|
);
|
|
84
83
|
} catch (error) {
|
|
85
84
|
loggerUtil.warn(
|
|
86
|
-
'Revoke
|
|
85
|
+
'Revoke connected app failed',
|
|
87
86
|
{ component: 'ConnectedAppsScreen' },
|
|
88
87
|
error,
|
|
89
88
|
);
|
|
@@ -92,7 +91,7 @@ const ConnectedAppsScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) =>
|
|
|
92
91
|
|| 'Failed to revoke access',
|
|
93
92
|
);
|
|
94
93
|
} finally {
|
|
95
|
-
|
|
94
|
+
setRevokingAppId(null);
|
|
96
95
|
setPendingRevoke(null);
|
|
97
96
|
}
|
|
98
97
|
}, [pendingRevoke, revokeMutation, t]);
|
|
@@ -113,18 +112,18 @@ const ConnectedAppsScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) =>
|
|
|
113
112
|
);
|
|
114
113
|
|
|
115
114
|
const renderItem = useCallback(
|
|
116
|
-
({ item }: { item:
|
|
117
|
-
const isRevoking =
|
|
115
|
+
({ item }: { item: ConnectedApp }) => {
|
|
116
|
+
const isRevoking = revokingAppId === item.applicationId;
|
|
118
117
|
return (
|
|
119
118
|
<SettingsListGroup>
|
|
120
119
|
<SettingsListItem
|
|
121
120
|
icon={<Avatar name={item.name} size={APP_ICON_SIZE} />}
|
|
122
121
|
title={item.name}
|
|
123
122
|
description={
|
|
124
|
-
t('connectedApps.item.
|
|
125
|
-
relative: formatRelative(item.
|
|
123
|
+
t('connectedApps.item.granted', {
|
|
124
|
+
relative: formatRelative(item.firstGrantedAt),
|
|
126
125
|
})
|
|
127
|
-
|| `
|
|
126
|
+
|| `Granted ${formatRelative(item.firstGrantedAt)}`
|
|
128
127
|
}
|
|
129
128
|
onPress={isRevoking ? undefined : () => confirmRevoke(item)}
|
|
130
129
|
disabled={isRevoking}
|
|
@@ -142,7 +141,7 @@ const ConnectedAppsScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) =>
|
|
|
142
141
|
</SettingsListGroup>
|
|
143
142
|
);
|
|
144
143
|
},
|
|
145
|
-
[bloomTheme.colors.error, confirmRevoke,
|
|
144
|
+
[bloomTheme.colors.error, confirmRevoke, revokingAppId, t],
|
|
146
145
|
);
|
|
147
146
|
|
|
148
147
|
return (
|
|
@@ -158,7 +157,7 @@ const ConnectedAppsScreen: React.FC<BaseScreenProps> = ({ onClose, goBack }) =>
|
|
|
158
157
|
) : (
|
|
159
158
|
<FlatList
|
|
160
159
|
data={apps ?? []}
|
|
161
|
-
keyExtractor={(item) => item.
|
|
160
|
+
keyExtractor={(item) => item.applicationId}
|
|
162
161
|
renderItem={renderItem}
|
|
163
162
|
contentContainerClassName="px-screen-margin py-space-16"
|
|
164
163
|
contentContainerStyle={styles.listContent}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState,
|
|
1
|
+
import React, { useState, useCallback, useMemo } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Platform,
|
|
@@ -16,12 +16,14 @@ import { H1, Text } from '@oxyhq/bloom/typography';
|
|
|
16
16
|
import { Button } from '@oxyhq/bloom/button';
|
|
17
17
|
import { TextField, TextFieldInput } from '@oxyhq/bloom/text-field';
|
|
18
18
|
import { normalizeTheme } from '@oxyhq/core';
|
|
19
|
+
import type { User } from '@oxyhq/core';
|
|
19
20
|
import Header from '../components/Header';
|
|
20
21
|
import { useI18n } from '../hooks/useI18n';
|
|
21
22
|
import { useOxy } from '../context/OxyContext';
|
|
22
23
|
import { useProfileEditing } from '../hooks/useProfileEditing';
|
|
23
24
|
import { toast } from '@oxyhq/bloom';
|
|
24
|
-
import { EMAIL_REGEX } from '@oxyhq/core';
|
|
25
|
+
import { EMAIL_REGEX, isValidDisplayName } from '@oxyhq/core';
|
|
26
|
+
import { getLinkTitle, getLinkDescription, linksToListItems } from './linkFormat';
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Field types supported by EditProfileFieldScreen
|
|
@@ -71,8 +73,82 @@ type EditableListItem = {
|
|
|
71
73
|
coordinates?: { lat: number; lon: number };
|
|
72
74
|
};
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Pure seeding function: derives the initial `fieldValues` / `listItems` for a
|
|
79
|
+
* given field type from the active account snapshot. Called ONCE per state via
|
|
80
|
+
* lazy `useState` initializers — never from an effect — so a background
|
|
81
|
+
* `refreshSessions()` / `useCurrentUser()` swap of the `user` reference can't
|
|
82
|
+
* wipe in-progress typing. Each editor mounts with a fixed `fieldType`, so the
|
|
83
|
+
* seed is stable for the lifetime of the mount.
|
|
84
|
+
*/
|
|
85
|
+
function buildInitialProfileState(
|
|
86
|
+
user: User | null,
|
|
87
|
+
fieldType: ProfileFieldType,
|
|
88
|
+
): { fieldValues: Record<string, string>; listItems: EditableListItem[] } {
|
|
89
|
+
if (!user) {
|
|
90
|
+
return { fieldValues: {}, listItems: [] };
|
|
91
|
+
}
|
|
92
|
+
const userData = user;
|
|
93
|
+
|
|
94
|
+
if (fieldType === 'locations') {
|
|
95
|
+
const locations = Array.isArray(userData.locations) ? userData.locations : [];
|
|
96
|
+
return {
|
|
97
|
+
fieldValues: {},
|
|
98
|
+
listItems: locations.map((loc, i) => ({
|
|
99
|
+
id: String(loc.id || `location-${i}`),
|
|
100
|
+
name: String(loc.name || ''),
|
|
101
|
+
...loc,
|
|
102
|
+
})),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (fieldType === 'links') {
|
|
107
|
+
const linksMetadata = Array.isArray(userData.linksMetadata) ? userData.linksMetadata : [];
|
|
108
|
+
const links = Array.isArray(userData.links) ? userData.links : [];
|
|
109
|
+
// Prefer rich link metadata; fall back to the plain links array.
|
|
110
|
+
if (linksMetadata.length > 0) {
|
|
111
|
+
return {
|
|
112
|
+
fieldValues: {},
|
|
113
|
+
listItems: linksMetadata.map((link, i) => ({
|
|
114
|
+
...link,
|
|
115
|
+
id: String(link.id || `link-${i}`),
|
|
116
|
+
url: String(link.url || ''),
|
|
117
|
+
title: String(link.title || getLinkTitle(String(link.url || ''))),
|
|
118
|
+
description: String(link.description || getLinkDescription(String(link.url || ''))),
|
|
119
|
+
})),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
fieldValues: {},
|
|
124
|
+
listItems: linksToListItems(links),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Scalar fields: seed only the keys this field type edits.
|
|
129
|
+
const fieldValues: Record<string, string> = {};
|
|
130
|
+
switch (fieldType) {
|
|
131
|
+
case 'displayName':
|
|
132
|
+
fieldValues.firstName = String(userData.name?.first || '');
|
|
133
|
+
fieldValues.lastName = String(userData.lastName || userData.name?.last || '');
|
|
134
|
+
break;
|
|
135
|
+
case 'birthday':
|
|
136
|
+
fieldValues.birthday = String(userData.birthday || userData.dateOfBirth || '');
|
|
137
|
+
break;
|
|
138
|
+
case 'address':
|
|
139
|
+
fieldValues.address = String(userData.address || '');
|
|
140
|
+
break;
|
|
141
|
+
case 'username':
|
|
142
|
+
case 'email':
|
|
143
|
+
case 'bio':
|
|
144
|
+
case 'phone':
|
|
145
|
+
fieldValues[fieldType] = String(userData[fieldType] || '');
|
|
146
|
+
break;
|
|
147
|
+
default:
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
return { fieldValues, listItems: [] };
|
|
151
|
+
}
|
|
76
152
|
|
|
77
153
|
/**
|
|
78
154
|
* EditProfileFieldScreen - A dedicated screen for editing profile fields
|
|
@@ -98,12 +174,18 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
98
174
|
const bloomTheme = useTheme();
|
|
99
175
|
const normalizedTheme = normalizeTheme(theme);
|
|
100
176
|
|
|
101
|
-
// State for field values
|
|
102
|
-
|
|
177
|
+
// State for field values — seeded ONCE from the active account snapshot at
|
|
178
|
+
// mount via lazy initializers. See buildInitialProfileState: no effect
|
|
179
|
+
// reseeds these, so a background user-ref swap never wipes typing.
|
|
180
|
+
const [fieldValues, setFieldValues] = useState<Record<string, string>>(
|
|
181
|
+
() => buildInitialProfileState(user, fieldType).fieldValues,
|
|
182
|
+
);
|
|
103
183
|
const [fieldErrors, setFieldErrors] = useState<Record<string, string | undefined>>({});
|
|
104
184
|
|
|
105
|
-
// State for list fields (locations, links)
|
|
106
|
-
const [listItems, setListItems] = useState<EditableListItem[]>(
|
|
185
|
+
// State for list fields (locations, links) — same one-time mount seeding.
|
|
186
|
+
const [listItems, setListItems] = useState<EditableListItem[]>(
|
|
187
|
+
() => buildInitialProfileState(user, fieldType).listItems,
|
|
188
|
+
);
|
|
107
189
|
const [newItemValue, setNewItemValue] = useState('');
|
|
108
190
|
|
|
109
191
|
// Get field configuration based on fieldType
|
|
@@ -118,11 +200,21 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
118
200
|
key: 'firstName',
|
|
119
201
|
label: t('editProfile.items.displayName.firstName') || 'First Name',
|
|
120
202
|
placeholder: t('editProfile.items.displayName.firstNamePlaceholder') || 'Enter first name',
|
|
203
|
+
validation: (value) =>
|
|
204
|
+
isValidDisplayName(value)
|
|
205
|
+
? undefined
|
|
206
|
+
: (t('editProfile.items.displayName.invalidChars')
|
|
207
|
+
|| 'Use letters and spaces only'),
|
|
121
208
|
},
|
|
122
209
|
{
|
|
123
210
|
key: 'lastName',
|
|
124
211
|
label: t('editProfile.items.displayName.lastName') || 'Last Name',
|
|
125
212
|
placeholder: t('editProfile.items.displayName.lastNamePlaceholder') || 'Enter last name (optional)',
|
|
213
|
+
validation: (value) =>
|
|
214
|
+
isValidDisplayName(value)
|
|
215
|
+
? undefined
|
|
216
|
+
: (t('editProfile.items.displayName.invalidChars')
|
|
217
|
+
|| 'Use letters and spaces only'),
|
|
126
218
|
},
|
|
127
219
|
],
|
|
128
220
|
};
|
|
@@ -266,62 +358,6 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
266
358
|
}
|
|
267
359
|
}, [fieldType, t]);
|
|
268
360
|
|
|
269
|
-
// Initialize field values from the active account's data
|
|
270
|
-
useEffect(() => {
|
|
271
|
-
if (!user) return;
|
|
272
|
-
|
|
273
|
-
const userData = user;
|
|
274
|
-
|
|
275
|
-
if (fieldConfig.isList) {
|
|
276
|
-
if (fieldType === 'locations') {
|
|
277
|
-
const locations = Array.isArray(userData.locations) ? userData.locations : [];
|
|
278
|
-
setListItems(locations.map((loc, i) => ({
|
|
279
|
-
id: String(loc.id || `location-${i}`),
|
|
280
|
-
name: String(loc.name || ''),
|
|
281
|
-
...loc,
|
|
282
|
-
})));
|
|
283
|
-
} else if (fieldType === 'links') {
|
|
284
|
-
const linksMetadata = Array.isArray(userData.linksMetadata) ? userData.linksMetadata : [];
|
|
285
|
-
const links = Array.isArray(userData.links) ? userData.links : [];
|
|
286
|
-
// Use linksMetadata if available, otherwise convert links array
|
|
287
|
-
if (linksMetadata.length > 0) {
|
|
288
|
-
setListItems(linksMetadata.map((link, i) => ({
|
|
289
|
-
...link,
|
|
290
|
-
id: String(link.id || `link-${i}`),
|
|
291
|
-
url: String(link.url || ''),
|
|
292
|
-
title: String(link.title || getLinkTitle(String(link.url || ''))),
|
|
293
|
-
description: String(link.description || getLinkDescription(String(link.url || ''))),
|
|
294
|
-
})));
|
|
295
|
-
} else {
|
|
296
|
-
setListItems(links.map((item, i) => {
|
|
297
|
-
return {
|
|
298
|
-
id: `link-${i}`,
|
|
299
|
-
url: item,
|
|
300
|
-
title: getLinkTitle(item),
|
|
301
|
-
description: getLinkDescription(item),
|
|
302
|
-
};
|
|
303
|
-
}));
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
} else {
|
|
307
|
-
const initialValues: Record<string, string> = {};
|
|
308
|
-
fieldConfig.fields.forEach(field => {
|
|
309
|
-
if (field.key === 'firstName') {
|
|
310
|
-
initialValues[field.key] = String(userData.name?.first || '');
|
|
311
|
-
} else if (field.key === 'lastName') {
|
|
312
|
-
initialValues[field.key] = String(userData.lastName || userData.name?.last || '');
|
|
313
|
-
} else if (field.key === 'birthday') {
|
|
314
|
-
initialValues[field.key] = String(userData.birthday || userData.dateOfBirth || '');
|
|
315
|
-
} else if (field.key === 'address') {
|
|
316
|
-
initialValues[field.key] = String(userData.address || userData.location || '');
|
|
317
|
-
} else {
|
|
318
|
-
initialValues[field.key] = String(userData[field.key] || '');
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
setFieldValues(initialValues);
|
|
322
|
-
}
|
|
323
|
-
}, [user, fieldConfig, fieldType]);
|
|
324
|
-
|
|
325
361
|
// Field change handler
|
|
326
362
|
const handleFieldChange = useCallback((key: string, value: string) => {
|
|
327
363
|
setFieldValues(prev => ({ ...prev, [key]: value }));
|
|
@@ -335,7 +371,7 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
335
371
|
const errors: Record<string, string | undefined> = {};
|
|
336
372
|
let isValid = true;
|
|
337
373
|
|
|
338
|
-
fieldConfig.fields
|
|
374
|
+
for (const field of fieldConfig.fields) {
|
|
339
375
|
if (field.validation) {
|
|
340
376
|
const error = field.validation(fieldValues[field.key] || '');
|
|
341
377
|
if (error) {
|
|
@@ -343,7 +379,7 @@ const EditProfileFieldScreen: React.FC<EditProfileFieldScreenProps> = ({
|
|
|
343
379
|
isValid = false;
|
|
344
380
|
}
|
|
345
381
|
}
|
|
346
|
-
}
|
|
382
|
+
}
|
|
347
383
|
|
|
348
384
|
setFieldErrors(errors);
|
|
349
385
|
return isValid;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { View, ScrollView } from 'react-native';
|
|
4
|
+
import { useTheme } from '@oxyhq/bloom/theme';
|
|
5
|
+
import { H4, Text } from '@oxyhq/bloom/typography';
|
|
6
|
+
import { SettingsListGroup, SettingsListItem } from '@oxyhq/bloom/settings-list';
|
|
7
|
+
import { getAccountDisplayName } from '@oxyhq/core';
|
|
8
|
+
import type { BaseScreenProps } from '../types/navigation';
|
|
9
|
+
import Header from '../components/Header';
|
|
10
|
+
import Avatar from '../components/Avatar';
|
|
11
|
+
import { SettingsIcon } from '../components/SettingsIcon';
|
|
12
|
+
import { useOxy } from '../context/OxyContext';
|
|
13
|
+
import { useI18n } from '../hooks/useI18n';
|
|
14
|
+
import type { ProfileFieldType } from './EditProfileFieldScreen';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* EditProfileScreen — the profile-editing HUB.
|
|
18
|
+
*
|
|
19
|
+
* Lists every editable profile field as its own row; each row deep-links into
|
|
20
|
+
* {@link EditProfileFieldScreen} with the matching `fieldType`. The avatar row
|
|
21
|
+
* reuses the exact same `openAvatarPicker` entry point that
|
|
22
|
+
* {@link ManageAccountScreen} uses. This is the single entry into per-field
|
|
23
|
+
* editing — reached from ManageAccount's "Edit profile" row.
|
|
24
|
+
*/
|
|
25
|
+
const EditProfileScreen: React.FC<BaseScreenProps> = ({ onClose, goBack, navigate }) => {
|
|
26
|
+
const bloomTheme = useTheme();
|
|
27
|
+
const { t, locale } = useI18n();
|
|
28
|
+
const { user, oxyServices, openAvatarPicker } = useOxy();
|
|
29
|
+
|
|
30
|
+
const displayName = useMemo(() => getAccountDisplayName(user, locale), [user, locale]);
|
|
31
|
+
const avatarUri = useMemo(
|
|
32
|
+
() => (user?.avatar ? oxyServices.getFileDownloadUrl(user.avatar, 'thumb') : undefined),
|
|
33
|
+
[user?.avatar, oxyServices],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const notSet = t('editProfile.notSet') || 'Not set';
|
|
37
|
+
|
|
38
|
+
// Current value previews for each row. Typed User fields resolve directly;
|
|
39
|
+
// `locations` is only reachable via the User index signature (typed
|
|
40
|
+
// `unknown`), so it is narrowed defensively.
|
|
41
|
+
const linkPreview = user?.linksMetadata?.[0]?.url ?? user?.links?.[0] ?? notSet;
|
|
42
|
+
const locationPreview = ((): string => {
|
|
43
|
+
const locations = user?.locations;
|
|
44
|
+
const first: unknown = Array.isArray(locations) ? locations[0] : undefined;
|
|
45
|
+
if (first && typeof first === 'object' && 'name' in first && typeof first.name === 'string' && first.name) {
|
|
46
|
+
return first.name;
|
|
47
|
+
}
|
|
48
|
+
return notSet;
|
|
49
|
+
})();
|
|
50
|
+
|
|
51
|
+
const goToField = (fieldType: ProfileFieldType) => navigate?.('EditProfileField', { fieldType });
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<View className="flex-1 bg-bg">
|
|
55
|
+
<Header
|
|
56
|
+
title={t('editProfile.title') || 'Edit Profile'}
|
|
57
|
+
onBack={goBack || onClose}
|
|
58
|
+
elevation="subtle"
|
|
59
|
+
/>
|
|
60
|
+
<ScrollView
|
|
61
|
+
className="flex-1"
|
|
62
|
+
contentContainerClassName="px-screen-margin pb-space-24"
|
|
63
|
+
showsVerticalScrollIndicator={false}
|
|
64
|
+
>
|
|
65
|
+
{/* Profile card */}
|
|
66
|
+
<View className="items-center bg-fill-secondary rounded-radius-20 px-space-20 py-space-24 mb-space-16">
|
|
67
|
+
<Avatar uri={avatarUri} name={displayName} size={72} />
|
|
68
|
+
<H4 className="text-text mt-space-12" numberOfLines={1}>
|
|
69
|
+
{displayName}
|
|
70
|
+
</H4>
|
|
71
|
+
{user?.username ? (
|
|
72
|
+
<Text className="text-text-secondary text-sm mt-space-2" numberOfLines={1}>
|
|
73
|
+
@{user.username}
|
|
74
|
+
</Text>
|
|
75
|
+
) : null}
|
|
76
|
+
</View>
|
|
77
|
+
|
|
78
|
+
{/* Profile picture */}
|
|
79
|
+
<SettingsListGroup title={t('editProfile.sections.profilePicture') || 'Profile Picture'}>
|
|
80
|
+
<SettingsListItem
|
|
81
|
+
icon={<SettingsIcon name="camera" color={bloomTheme.colors.primary} />}
|
|
82
|
+
title={t('editProfile.changeAvatar') || 'Change avatar'}
|
|
83
|
+
description={t('editProfile.items.avatar.subtitle') || 'Update your profile photo'}
|
|
84
|
+
onPress={openAvatarPicker}
|
|
85
|
+
rightElement={<Avatar uri={avatarUri} name={displayName} size={32} />}
|
|
86
|
+
/>
|
|
87
|
+
</SettingsListGroup>
|
|
88
|
+
|
|
89
|
+
{/* Basic information */}
|
|
90
|
+
<SettingsListGroup title={t('editProfile.sections.basicInfo') || 'Basic Information'}>
|
|
91
|
+
<SettingsListItem
|
|
92
|
+
icon={<SettingsIcon name="account" color={bloomTheme.colors.primary} />}
|
|
93
|
+
title={t('editProfile.items.displayName.title') || 'Display Name'}
|
|
94
|
+
description={displayName || notSet}
|
|
95
|
+
onPress={() => goToField('displayName')}
|
|
96
|
+
/>
|
|
97
|
+
<SettingsListItem
|
|
98
|
+
icon={<SettingsIcon name="at" color={bloomTheme.colors.info} />}
|
|
99
|
+
title={t('editProfile.items.username.title') || 'Username'}
|
|
100
|
+
description={user?.username ? `@${user.username}` : notSet}
|
|
101
|
+
onPress={() => goToField('username')}
|
|
102
|
+
/>
|
|
103
|
+
<SettingsListItem
|
|
104
|
+
icon={<SettingsIcon name="email" color={bloomTheme.colors.success} />}
|
|
105
|
+
title={t('editProfile.items.email.title') || 'Email'}
|
|
106
|
+
description={user?.email || notSet}
|
|
107
|
+
onPress={() => goToField('email')}
|
|
108
|
+
/>
|
|
109
|
+
<SettingsListItem
|
|
110
|
+
icon={<SettingsIcon name="phone" color={bloomTheme.colors.warning} />}
|
|
111
|
+
title={t('editProfile.items.phone.title') || 'Phone Number'}
|
|
112
|
+
description={user?.phone || notSet}
|
|
113
|
+
onPress={() => goToField('phone')}
|
|
114
|
+
/>
|
|
115
|
+
</SettingsListGroup>
|
|
116
|
+
|
|
117
|
+
{/* About you */}
|
|
118
|
+
<SettingsListGroup title={t('editProfile.sections.about') || 'About You'}>
|
|
119
|
+
<SettingsListItem
|
|
120
|
+
icon={<SettingsIcon name="text" color={bloomTheme.colors.primary} />}
|
|
121
|
+
title={t('editProfile.items.bio.title') || 'Bio'}
|
|
122
|
+
description={user?.bio || notSet}
|
|
123
|
+
onPress={() => goToField('bio')}
|
|
124
|
+
/>
|
|
125
|
+
<SettingsListItem
|
|
126
|
+
icon={<SettingsIcon name="map-marker" color={bloomTheme.colors.info} />}
|
|
127
|
+
title={t('editProfile.items.address.title') || 'Address'}
|
|
128
|
+
description={user?.address || notSet}
|
|
129
|
+
onPress={() => goToField('address')}
|
|
130
|
+
/>
|
|
131
|
+
<SettingsListItem
|
|
132
|
+
icon={<SettingsIcon name="cake-variant" color={bloomTheme.colors.warning} />}
|
|
133
|
+
title={t('editProfile.items.birthday.title') || 'Birthday'}
|
|
134
|
+
description={user?.birthday || notSet}
|
|
135
|
+
onPress={() => goToField('birthday')}
|
|
136
|
+
/>
|
|
137
|
+
<SettingsListItem
|
|
138
|
+
icon={<SettingsIcon name="link-variant" color={bloomTheme.colors.success} />}
|
|
139
|
+
title={t('editProfile.items.links.title') || 'Links'}
|
|
140
|
+
description={linkPreview}
|
|
141
|
+
onPress={() => goToField('links')}
|
|
142
|
+
/>
|
|
143
|
+
<SettingsListItem
|
|
144
|
+
icon={<SettingsIcon name="map-marker-multiple" color={bloomTheme.colors.primary} />}
|
|
145
|
+
title={t('editProfile.items.locations.title') || 'Locations'}
|
|
146
|
+
description={locationPreview}
|
|
147
|
+
onPress={() => goToField('locations')}
|
|
148
|
+
/>
|
|
149
|
+
</SettingsListGroup>
|
|
150
|
+
</ScrollView>
|
|
151
|
+
</View>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export default EditProfileScreen;
|
|
@@ -78,7 +78,7 @@ const formatRelative = (dateString?: string): string => {
|
|
|
78
78
|
* Replaces AccountOverview + AccountSettings + the per-account half of
|
|
79
79
|
* SessionManagement. Lists ONLY the active user's profile, sessions on this
|
|
80
80
|
* device, and security/destructive actions for THIS account. Multi-account
|
|
81
|
-
* surface lives in {@link
|
|
81
|
+
* surface lives in {@link AccountSwitcher} — keep these concerns separate.
|
|
82
82
|
*/
|
|
83
83
|
const ManageAccountScreen: React.FC<BaseScreenProps> = ({
|
|
84
84
|
onClose,
|
|
@@ -384,9 +384,7 @@ const ManageAccountScreen: React.FC<BaseScreenProps> = ({
|
|
|
384
384
|
t('manageAccount.items.editProfile.subtitle')
|
|
385
385
|
|| 'Name, username, bio, links'
|
|
386
386
|
}
|
|
387
|
-
onPress={() =>
|
|
388
|
-
navigate?.('EditProfileField', { field: 'username' })
|
|
389
|
-
}
|
|
387
|
+
onPress={() => navigate?.('EditProfile')}
|
|
390
388
|
/>
|
|
391
389
|
<SettingsListItem
|
|
392
390
|
icon={
|
|
@@ -400,9 +398,7 @@ const ManageAccountScreen: React.FC<BaseScreenProps> = ({
|
|
|
400
398
|
t('manageAccount.items.theme.subtitle')
|
|
401
399
|
|| 'Personalize your Bloom color'
|
|
402
400
|
}
|
|
403
|
-
onPress={() =>
|
|
404
|
-
navigate?.('EditProfileField', { field: 'color' })
|
|
405
|
-
}
|
|
401
|
+
onPress={() => navigate?.('Preferences')}
|
|
406
402
|
/>
|
|
407
403
|
<SettingsListItem
|
|
408
404
|
icon={
|