@oxyhq/services 14.1.0 → 15.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 +0 -17
- 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/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/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 +0 -5
- 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/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/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 +0 -1
- 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 +0 -2
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.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/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/utils/deviceFlowSignIn.d.ts +15 -13
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +0 -1
- 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 +0 -2
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.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/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/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 +0 -4
- 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/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 +2 -0
- 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/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
|
@@ -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={
|