@oxyhq/services 5.7.5 → 5.8.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.
Files changed (239) hide show
  1. package/README.md +76 -76
  2. package/lib/commonjs/core/index.js +177 -102
  3. package/lib/commonjs/core/index.js.map +1 -1
  4. package/lib/commonjs/index.js +88 -29
  5. package/lib/commonjs/index.js.map +1 -1
  6. package/lib/commonjs/node/createAuth.js +585 -7
  7. package/lib/commonjs/node/createAuth.js.map +1 -1
  8. package/lib/commonjs/node/index.js +38 -1
  9. package/lib/commonjs/node/index.js.map +1 -1
  10. package/lib/commonjs/ui/components/Avatar.js +15 -6
  11. package/lib/commonjs/ui/components/Avatar.js.map +1 -1
  12. package/lib/commonjs/ui/components/GroupedItem.js +58 -13
  13. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  14. package/lib/commonjs/ui/components/GroupedSection.js +7 -1
  15. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  16. package/lib/commonjs/ui/components/Header.js +322 -0
  17. package/lib/commonjs/ui/components/Header.js.map +1 -0
  18. package/lib/commonjs/ui/components/OxyProvider.js +23 -7
  19. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  20. package/lib/commonjs/ui/components/index.js +7 -0
  21. package/lib/commonjs/ui/components/index.js.map +1 -1
  22. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +1 -1
  23. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -1
  24. package/lib/commonjs/ui/components/internal/TextField.js +606 -546
  25. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  26. package/lib/commonjs/ui/components/internal/TextField.md +436 -0
  27. package/lib/commonjs/ui/context/OxyContext.js +122 -78
  28. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  29. package/lib/commonjs/ui/hooks/useSessionSocket.js +5 -2
  30. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
  31. package/lib/commonjs/ui/navigation/OxyRouter.js +1 -1
  32. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  33. package/lib/commonjs/ui/screens/AccountCenterScreen.js +6 -6
  34. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  35. package/lib/commonjs/ui/screens/AccountManagementDemo.js +3 -3
  36. package/lib/commonjs/ui/screens/AccountManagementDemo.js.map +1 -1
  37. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +241 -598
  38. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  39. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +1151 -406
  40. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  41. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +135 -237
  42. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  43. package/lib/commonjs/ui/screens/AppInfoScreen.js +246 -463
  44. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  45. package/lib/commonjs/ui/screens/FeedbackScreen.js +3 -3
  46. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
  47. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js +808 -650
  48. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
  49. package/lib/commonjs/ui/screens/RecoverAccountScreen.js +51 -72
  50. package/lib/commonjs/ui/screens/RecoverAccountScreen.js.map +1 -1
  51. package/lib/commonjs/ui/screens/SessionManagementScreen.js +11 -29
  52. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  53. package/lib/commonjs/ui/screens/SignInScreen.js +30 -303
  54. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  55. package/lib/commonjs/ui/screens/SignUpScreen.js +4 -4
  56. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  57. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +19 -31
  58. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  59. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +7 -10
  60. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  61. package/lib/commonjs/ui/screens/internal/SignUpIdentityStep.js +11 -5
  62. package/lib/commonjs/ui/screens/internal/SignUpIdentityStep.js.map +1 -1
  63. package/lib/commonjs/ui/screens/internal/SignUpSecurityStep.js +11 -4
  64. package/lib/commonjs/ui/screens/internal/SignUpSecurityStep.js.map +1 -1
  65. package/lib/commonjs/ui/stores/authStore.js +12 -0
  66. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  67. package/lib/commonjs/ui/styles/authStyles.js +337 -0
  68. package/lib/commonjs/ui/styles/authStyles.js.map +1 -0
  69. package/lib/commonjs/ui/styles/index.js +11 -0
  70. package/lib/commonjs/ui/styles/index.js.map +1 -1
  71. package/lib/module/core/index.js +177 -41
  72. package/lib/module/core/index.js.map +1 -1
  73. package/lib/module/index.js +26 -4
  74. package/lib/module/index.js.map +1 -1
  75. package/lib/module/node/createAuth.js +584 -7
  76. package/lib/module/node/createAuth.js.map +1 -1
  77. package/lib/module/node/index.js +7 -1
  78. package/lib/module/node/index.js.map +1 -1
  79. package/lib/module/ui/components/Avatar.js +15 -6
  80. package/lib/module/ui/components/Avatar.js.map +1 -1
  81. package/lib/module/ui/components/GroupedItem.js +59 -14
  82. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  83. package/lib/module/ui/components/GroupedSection.js +7 -1
  84. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  85. package/lib/module/ui/components/Header.js +317 -0
  86. package/lib/module/ui/components/Header.js.map +1 -0
  87. package/lib/module/ui/components/OxyProvider.js +25 -9
  88. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  89. package/lib/module/ui/components/index.js +1 -0
  90. package/lib/module/ui/components/index.js.map +1 -1
  91. package/lib/module/ui/components/internal/GroupedPillButtons.js +1 -1
  92. package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -1
  93. package/lib/module/ui/components/internal/TextField.js +607 -547
  94. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  95. package/lib/module/ui/components/internal/TextField.md +436 -0
  96. package/lib/module/ui/context/OxyContext.js +121 -77
  97. package/lib/module/ui/context/OxyContext.js.map +1 -1
  98. package/lib/module/ui/hooks/useSessionSocket.js +5 -2
  99. package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
  100. package/lib/module/ui/navigation/OxyRouter.js +1 -1
  101. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  102. package/lib/module/ui/screens/AccountCenterScreen.js +6 -6
  103. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  104. package/lib/module/ui/screens/AccountManagementDemo.js +3 -3
  105. package/lib/module/ui/screens/AccountManagementDemo.js.map +1 -1
  106. package/lib/module/ui/screens/AccountOverviewScreen.js +242 -597
  107. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  108. package/lib/module/ui/screens/AccountSettingsScreen.js +1152 -407
  109. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  110. package/lib/module/ui/screens/AccountSwitcherScreen.js +135 -237
  111. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  112. package/lib/module/ui/screens/AppInfoScreen.js +248 -465
  113. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  114. package/lib/module/ui/screens/FeedbackScreen.js +3 -3
  115. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
  116. package/lib/module/ui/screens/PaymentGatewayScreen.js +809 -651
  117. package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
  118. package/lib/module/ui/screens/RecoverAccountScreen.js +53 -74
  119. package/lib/module/ui/screens/RecoverAccountScreen.js.map +1 -1
  120. package/lib/module/ui/screens/SessionManagementScreen.js +11 -29
  121. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  122. package/lib/module/ui/screens/SignInScreen.js +32 -305
  123. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  124. package/lib/module/ui/screens/SignUpScreen.js +5 -5
  125. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  126. package/lib/module/ui/screens/internal/SignInPasswordStep.js +19 -31
  127. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  128. package/lib/module/ui/screens/internal/SignInUsernameStep.js +7 -10
  129. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  130. package/lib/module/ui/screens/internal/SignUpIdentityStep.js +11 -5
  131. package/lib/module/ui/screens/internal/SignUpIdentityStep.js.map +1 -1
  132. package/lib/module/ui/screens/internal/SignUpSecurityStep.js +11 -4
  133. package/lib/module/ui/screens/internal/SignUpSecurityStep.js.map +1 -1
  134. package/lib/module/ui/stores/authStore.js +12 -0
  135. package/lib/module/ui/stores/authStore.js.map +1 -1
  136. package/lib/module/ui/styles/authStyles.js +332 -0
  137. package/lib/module/ui/styles/authStyles.js.map +1 -0
  138. package/lib/module/ui/styles/index.js +1 -0
  139. package/lib/module/ui/styles/index.js.map +1 -1
  140. package/lib/typescript/core/index.d.ts +68 -24
  141. package/lib/typescript/core/index.d.ts.map +1 -1
  142. package/lib/typescript/index.d.ts +13 -3
  143. package/lib/typescript/index.d.ts.map +1 -1
  144. package/lib/typescript/node/createAuth.d.ts +112 -0
  145. package/lib/typescript/node/createAuth.d.ts.map +1 -1
  146. package/lib/typescript/node/index.d.ts +2 -0
  147. package/lib/typescript/node/index.d.ts.map +1 -1
  148. package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
  149. package/lib/typescript/ui/components/GroupedItem.d.ts +6 -0
  150. package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
  151. package/lib/typescript/ui/components/GroupedSection.d.ts +6 -0
  152. package/lib/typescript/ui/components/GroupedSection.d.ts.map +1 -1
  153. package/lib/typescript/ui/components/Header.d.ts +22 -0
  154. package/lib/typescript/ui/components/Header.d.ts.map +1 -0
  155. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  156. package/lib/typescript/ui/components/index.d.ts +1 -0
  157. package/lib/typescript/ui/components/index.d.ts.map +1 -1
  158. package/lib/typescript/ui/components/internal/TextField.d.ts +31 -16
  159. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  160. package/lib/typescript/ui/context/OxyContext.d.ts +5 -2
  161. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  162. package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
  163. package/lib/typescript/ui/navigation/types.d.ts +9 -2
  164. package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
  165. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  166. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  167. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  168. package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
  169. package/lib/typescript/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
  170. package/lib/typescript/ui/screens/RecoverAccountScreen.d.ts +5 -1
  171. package/lib/typescript/ui/screens/RecoverAccountScreen.d.ts.map +1 -1
  172. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  173. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  174. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +1 -1
  175. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  176. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +0 -1
  177. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
  178. package/lib/typescript/ui/screens/internal/SignUpIdentityStep.d.ts.map +1 -1
  179. package/lib/typescript/ui/screens/internal/SignUpSecurityStep.d.ts.map +1 -1
  180. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  181. package/lib/typescript/ui/styles/authStyles.d.ts +326 -0
  182. package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -0
  183. package/lib/typescript/ui/styles/index.d.ts +1 -0
  184. package/lib/typescript/ui/styles/index.d.ts.map +1 -1
  185. package/package.json +1 -4
  186. package/src/core/index.ts +195 -41
  187. package/src/index.ts +72 -4
  188. package/src/node/createAuth.ts +623 -7
  189. package/src/node/index.ts +19 -1
  190. package/src/ui/components/Avatar.tsx +11 -5
  191. package/src/ui/components/GroupedItem.tsx +57 -9
  192. package/src/ui/components/GroupedSection.tsx +12 -0
  193. package/src/ui/components/Header.tsx +364 -0
  194. package/src/ui/components/OxyProvider.tsx +31 -15
  195. package/src/ui/components/index.ts +1 -0
  196. package/src/ui/components/internal/GroupedPillButtons.tsx +1 -1
  197. package/src/ui/components/internal/TextField.md +436 -0
  198. package/src/ui/components/internal/TextField.tsx +720 -620
  199. package/src/ui/context/OxyContext.tsx +150 -63
  200. package/src/ui/hooks/useSessionSocket.ts +5 -2
  201. package/src/ui/navigation/OxyRouter.tsx +1 -1
  202. package/src/ui/navigation/types.ts +10 -2
  203. package/src/ui/screens/AccountCenterScreen.tsx +5 -5
  204. package/src/ui/screens/AccountManagementDemo.tsx +9 -9
  205. package/src/ui/screens/AccountOverviewScreen.tsx +265 -414
  206. package/src/ui/screens/AccountSettingsScreen.tsx +1165 -403
  207. package/src/ui/screens/AccountSwitcherScreen.tsx +158 -202
  208. package/src/ui/screens/AppInfoScreen.tsx +270 -497
  209. package/src/ui/screens/FeedbackScreen.tsx +3 -3
  210. package/src/ui/screens/PaymentGatewayScreen.tsx +668 -365
  211. package/src/ui/screens/ProfileScreen.tsx +5 -5
  212. package/src/ui/screens/RecoverAccountScreen.tsx +46 -74
  213. package/src/ui/screens/SessionManagementScreen.tsx +14 -22
  214. package/src/ui/screens/SignInScreen.tsx +27 -294
  215. package/src/ui/screens/SignUpScreen.tsx +5 -5
  216. package/src/ui/screens/internal/SignInPasswordStep.tsx +11 -22
  217. package/src/ui/screens/internal/SignInUsernameStep.tsx +3 -10
  218. package/src/ui/screens/internal/SignUpIdentityStep.tsx +2 -5
  219. package/src/ui/screens/internal/SignUpSecurityStep.tsx +3 -4
  220. package/src/ui/stores/authStore.ts +12 -0
  221. package/src/ui/styles/authStyles.ts +352 -0
  222. package/src/ui/styles/index.ts +1 -0
  223. package/lib/commonjs/core/auth-manager.js +0 -440
  224. package/lib/commonjs/core/auth-manager.js.map +0 -1
  225. package/lib/commonjs/core/use-auth.js +0 -244
  226. package/lib/commonjs/core/use-auth.js.map +0 -1
  227. package/lib/module/core/auth-manager.js +0 -432
  228. package/lib/module/core/auth-manager.js.map +0 -1
  229. package/lib/module/core/use-auth.js +0 -235
  230. package/lib/module/core/use-auth.js.map +0 -1
  231. package/lib/typescript/core/auth-manager.d.ts +0 -136
  232. package/lib/typescript/core/auth-manager.d.ts.map +0 -1
  233. package/lib/typescript/core/use-auth.d.ts +0 -79
  234. package/lib/typescript/core/use-auth.d.ts.map +0 -1
  235. package/src/__tests__/middleware.test.ts +0 -105
  236. package/src/__tests__/setup.ts +0 -10
  237. package/src/__tests__/zero-config-auth.test.ts +0 -607
  238. package/src/core/auth-manager.ts +0 -500
  239. package/src/core/use-auth.tsx +0 -245
@@ -10,6 +10,7 @@ import {
10
10
  TextInput,
11
11
  Animated,
12
12
  Platform,
13
+ Image,
13
14
  } from 'react-native';
14
15
  import { BaseScreenProps } from '../navigation/types';
15
16
  import { useOxy } from '../context/OxyContext';
@@ -20,6 +21,7 @@ import { toast } from '../../lib/sonner';
20
21
  import { fontFamilies } from '../styles/fonts';
21
22
  import { confirmAction } from '../utils/confirmAction';
22
23
  import { useAuthStore } from '../stores/authStore';
24
+ import { Header, GroupedSection } from '../components';
23
25
 
24
26
  const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
25
27
  onClose,
@@ -56,6 +58,34 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
56
58
  const [tempBio, setTempBio] = useState('');
57
59
  const [tempLocation, setTempLocation] = useState('');
58
60
  const [tempLinks, setTempLinks] = useState<string[]>([]);
61
+ const [tempLinksWithMetadata, setTempLinksWithMetadata] = useState<Array<{
62
+ url: string;
63
+ title?: string;
64
+ description?: string;
65
+ image?: string;
66
+ id: string;
67
+ }>>([]);
68
+ const [isAddingLink, setIsAddingLink] = useState(false);
69
+ const [newLinkUrl, setNewLinkUrl] = useState('');
70
+ const [isFetchingMetadata, setIsFetchingMetadata] = useState(false);
71
+
72
+ // Location management state
73
+ const [tempLocations, setTempLocations] = useState<Array<{
74
+ id: string;
75
+ name: string;
76
+ label?: string;
77
+ coordinates?: { lat: number; lon: number };
78
+ }>>([]);
79
+ const [isAddingLocation, setIsAddingLocation] = useState(false);
80
+ const [newLocationQuery, setNewLocationQuery] = useState('');
81
+ const [locationSearchResults, setLocationSearchResults] = useState<Array<{
82
+ place_id: number;
83
+ display_name: string;
84
+ lat: string;
85
+ lon: string;
86
+ type: string;
87
+ }>>([]);
88
+ const [isSearchingLocations, setIsSearchingLocations] = useState(false);
59
89
 
60
90
  // Memoize theme-related calculations to prevent unnecessary recalculations
61
91
  const themeStyles = useMemo(() => {
@@ -71,7 +101,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
71
101
  const animateSaveButton = useCallback((toValue: number) => {
72
102
  Animated.spring(saveButtonScale, {
73
103
  toValue,
74
- useNativeDriver: true,
104
+ useNativeDriver: Platform.OS !== 'web',
75
105
  tension: 150,
76
106
  friction: 8,
77
107
  }).start();
@@ -90,11 +120,59 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
90
120
  setEmail(user.email || '');
91
121
  setBio(user.bio || '');
92
122
  setLocation(user.location || '');
93
- setLinks(
94
- Array.isArray(user.links)
95
- ? user.links.map(l => typeof l === 'string' ? l : l.link).filter(Boolean)
96
- : user.website ? [user.website] : []
97
- );
123
+
124
+ // Handle locations - convert single location to array format
125
+ if (user.locations && Array.isArray(user.locations)) {
126
+ setTempLocations(user.locations.map((loc, index) => ({
127
+ id: loc.id || `existing-${index}`,
128
+ name: loc.name,
129
+ label: loc.label,
130
+ coordinates: loc.coordinates
131
+ })));
132
+ } else if (user.location) {
133
+ // Convert single location string to array format
134
+ setTempLocations([{
135
+ id: 'existing-0',
136
+ name: user.location,
137
+ label: 'Location'
138
+ }]);
139
+ } else {
140
+ setTempLocations([]);
141
+ }
142
+
143
+ // Handle links - simple and direct like other fields
144
+ if (user.linksMetadata && Array.isArray(user.linksMetadata)) {
145
+ const urls = user.linksMetadata.map(l => l.url);
146
+ setLinks(urls);
147
+ const metadataWithIds = user.linksMetadata.map((link, index) => ({
148
+ ...link,
149
+ id: link.id || `existing-${index}`
150
+ }));
151
+ setTempLinksWithMetadata(metadataWithIds);
152
+ } else if (Array.isArray(user.links)) {
153
+ const simpleLinks = user.links.map(l => typeof l === 'string' ? l : l.link).filter(Boolean);
154
+ setLinks(simpleLinks);
155
+ const linksWithMetadata = simpleLinks.map((url, index) => ({
156
+ url,
157
+ title: url.replace(/^https?:\/\//, '').replace(/\/$/, ''),
158
+ description: `Link to ${url}`,
159
+ image: undefined,
160
+ id: `existing-${index}`
161
+ }));
162
+ setTempLinksWithMetadata(linksWithMetadata);
163
+ } else if (user.website) {
164
+ setLinks([user.website]);
165
+ setTempLinksWithMetadata([{
166
+ url: user.website,
167
+ title: user.website.replace(/^https?:\/\//, '').replace(/\/$/, ''),
168
+ description: `Link to ${user.website}`,
169
+ image: undefined,
170
+ id: 'existing-0'
171
+ }]);
172
+ } else {
173
+ setLinks([]);
174
+ setTempLinksWithMetadata([]);
175
+ }
98
176
  setAvatarUrl(user.avatar?.url || '');
99
177
  }
100
178
  }, [user]);
@@ -110,10 +188,15 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
110
188
  username,
111
189
  email,
112
190
  bio,
113
- location,
191
+ location: tempLocations.length > 0 ? tempLocations[0].name : '', // Keep backward compatibility
192
+ locations: tempLocations.length > 0 ? tempLocations : undefined,
114
193
  links,
194
+ linksMetadata: tempLinksWithMetadata.length > 0 ? tempLinksWithMetadata : undefined,
115
195
  };
116
196
 
197
+ console.log('Saving updates:', updates);
198
+ console.log('Links metadata being saved:', tempLinksWithMetadata);
199
+
117
200
  // Handle name field
118
201
  if (displayName || lastName) {
119
202
  updates.name = { first: displayName, last: lastName };
@@ -166,10 +249,11 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
166
249
  setTempBio(currentValue);
167
250
  break;
168
251
  case 'location':
169
- setTempLocation(currentValue);
252
+ // Don't reset the locations - keep the existing data
170
253
  break;
171
254
  case 'links':
172
- setTempLinks([...links]);
255
+ // Don't reset the metadata - keep the existing rich metadata
256
+ // The tempLinksWithMetadata should already contain the rich data from the database
173
257
  break;
174
258
  }
175
259
  setEditingField(type);
@@ -193,10 +277,13 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
193
277
  setBio(tempBio);
194
278
  break;
195
279
  case 'location':
196
- setLocation(tempLocation);
280
+ // Locations are handled in the main save function
197
281
  break;
198
282
  case 'links':
199
- setLinks(tempLinks);
283
+ // Save both URLs and metadata
284
+ setLinks(tempLinksWithMetadata.map(link => link.url));
285
+ // Store full metadata for database
286
+ setTempLinksWithMetadata(tempLinksWithMetadata);
200
287
  break;
201
288
  }
202
289
 
@@ -211,64 +298,419 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
211
298
  setEditingField(null);
212
299
  };
213
300
 
214
- const getFieldLabel = (type: string) => {
215
- const labels = {
216
- displayName: 'Display Name',
217
- username: 'Username',
218
- email: 'Email',
219
- bio: 'Bio',
220
- location: 'Location',
221
- links: 'Links'
222
- };
223
- return labels[type as keyof typeof labels] || 'Field';
301
+
302
+
303
+ const fetchLinkMetadata = async (url: string) => {
304
+ try {
305
+ setIsFetchingMetadata(true);
306
+ console.log('Fetching metadata for URL:', url);
307
+
308
+ // Use the backend API to fetch metadata
309
+ const metadata = await oxyServices.fetchLinkMetadata(url);
310
+ console.log('Received metadata:', metadata);
311
+
312
+ return {
313
+ ...metadata,
314
+ id: Date.now().toString()
315
+ };
316
+ } catch (error) {
317
+ console.error('Error fetching metadata:', error);
318
+ // Fallback to basic metadata
319
+ return {
320
+ url: url.startsWith('http') ? url : 'https://' + url,
321
+ title: url.replace(/^https?:\/\//, '').replace(/\/$/, ''),
322
+ description: 'Link',
323
+ image: undefined,
324
+ id: Date.now().toString()
325
+ };
326
+ } finally {
327
+ setIsFetchingMetadata(false);
328
+ }
329
+ };
330
+
331
+ const searchLocations = async (query: string) => {
332
+ if (!query.trim() || query.length < 3) {
333
+ setLocationSearchResults([]);
334
+ return;
335
+ }
336
+
337
+ try {
338
+ setIsSearchingLocations(true);
339
+ const response = await fetch(
340
+ `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=5&addressdetails=1`
341
+ );
342
+ const data = await response.json();
343
+ setLocationSearchResults(data);
344
+ } catch (error) {
345
+ console.error('Error searching locations:', error);
346
+ setLocationSearchResults([]);
347
+ } finally {
348
+ setIsSearchingLocations(false);
349
+ }
224
350
  };
225
351
 
226
- const getFieldIcon = (type: string) => {
227
- const icons = {
228
- displayName: { name: 'person', color: '#007AFF' },
229
- username: { name: 'at', color: '#5856D6' },
230
- email: { name: 'mail', color: '#FF9500' },
231
- bio: { name: 'document-text', color: '#34C759' },
232
- location: { name: 'location', color: '#FF3B30' },
233
- links: { name: 'link', color: '#32D74B' }
352
+ const addLocation = (locationData: {
353
+ place_id: number;
354
+ display_name: string;
355
+ lat: string;
356
+ lon: string;
357
+ type: string;
358
+ }) => {
359
+ const newLocation = {
360
+ id: Date.now().toString(),
361
+ name: locationData.display_name,
362
+ label: locationData.type === 'city' ? 'City' :
363
+ locationData.type === 'country' ? 'Country' :
364
+ locationData.type === 'state' ? 'State' : 'Location',
365
+ coordinates: {
366
+ lat: parseFloat(locationData.lat),
367
+ lon: parseFloat(locationData.lon)
368
+ }
234
369
  };
235
- return icons[type as keyof typeof icons] || { name: 'person', color: '#007AFF' };
370
+
371
+ setTempLocations(prev => [...prev, newLocation]);
372
+ setNewLocationQuery('');
373
+ setLocationSearchResults([]);
374
+ setIsAddingLocation(false);
375
+ };
376
+
377
+ const removeLocation = (id: string) => {
378
+ setTempLocations(prev => prev.filter(loc => loc.id !== id));
379
+ };
380
+
381
+ const moveLocation = (fromIndex: number, toIndex: number) => {
382
+ setTempLocations(prev => {
383
+ const newLocations = [...prev];
384
+ const [movedLocation] = newLocations.splice(fromIndex, 1);
385
+ newLocations.splice(toIndex, 0, movedLocation);
386
+ return newLocations;
387
+ });
388
+ };
389
+
390
+ const addLink = async () => {
391
+ if (!newLinkUrl.trim()) return;
392
+
393
+ const url = newLinkUrl.trim();
394
+ console.log('Adding link:', url);
395
+
396
+ const metadata = await fetchLinkMetadata(url);
397
+ console.log('Final metadata for adding:', metadata);
398
+
399
+ setTempLinksWithMetadata(prev => [...prev, metadata]);
400
+ setNewLinkUrl('');
401
+ setIsAddingLink(false);
402
+ };
403
+
404
+ const removeLink = (id: string) => {
405
+ setTempLinksWithMetadata(prev => prev.filter(link => link.id !== id));
406
+ };
407
+
408
+ const moveLink = (fromIndex: number, toIndex: number) => {
409
+ setTempLinksWithMetadata(prev => {
410
+ const newLinks = [...prev];
411
+ const [movedLink] = newLinks.splice(fromIndex, 1);
412
+ newLinks.splice(toIndex, 0, movedLink);
413
+ return newLinks;
414
+ });
236
415
  };
237
416
 
238
417
  const renderEditingField = (type: string) => {
239
418
  if (type === 'displayName') {
240
419
  return (
241
- <View style={styles.editingFieldContainer}>
420
+ <View style={[styles.editingFieldContainer, { backgroundColor: themeStyles.backgroundColor }]}>
242
421
  <View style={styles.editingFieldContent}>
243
- <View style={[styles.newValueSection, { flexDirection: 'row', gap: 12 }]}>
244
- <View style={{ flex: 1 }}>
245
- <Text style={styles.editingFieldLabel}>First Name</Text>
246
- <TextInput
247
- style={styles.editingFieldInput}
248
- value={tempDisplayName}
249
- onChangeText={setTempDisplayName}
250
- placeholder="Enter your first name"
251
- placeholderTextColor={themeStyles.isDarkTheme ? '#aaa' : '#999'}
252
- autoFocus
253
- selectionColor={themeStyles.primaryColor}
254
- />
422
+ <View style={styles.newValueSection}>
423
+ <View style={styles.editingFieldHeader}>
424
+ <Text style={[styles.editingFieldLabel, { color: themeStyles.isDarkTheme ? '#FFFFFF' : '#1A1A1A' }]}>Edit Display Name</Text>
255
425
  </View>
256
- <View style={{ flex: 1 }}>
257
- <Text style={styles.editingFieldLabel}>Last Name</Text>
258
- <TextInput
259
- style={styles.editingFieldInput}
260
- value={tempLastName}
261
- onChangeText={setTempLastName}
262
- placeholder="Enter your last name"
263
- placeholderTextColor={themeStyles.isDarkTheme ? '#aaa' : '#999'}
264
- selectionColor={themeStyles.primaryColor}
265
- />
426
+ <View style={{ flexDirection: 'row', gap: 12 }}>
427
+ <View style={{ flex: 1 }}>
428
+ <Text style={styles.editingFieldLabel}>First Name</Text>
429
+ <TextInput
430
+ style={styles.editingFieldInput}
431
+ value={tempDisplayName}
432
+ onChangeText={setTempDisplayName}
433
+ placeholder="Enter your first name"
434
+ placeholderTextColor={themeStyles.isDarkTheme ? '#aaa' : '#999'}
435
+ autoFocus
436
+ selectionColor={themeStyles.primaryColor}
437
+ />
438
+ </View>
439
+ <View style={{ flex: 1 }}>
440
+ <Text style={styles.editingFieldLabel}>Last Name</Text>
441
+ <TextInput
442
+ style={styles.editingFieldInput}
443
+ value={tempLastName}
444
+ onChangeText={setTempLastName}
445
+ placeholder="Enter your last name"
446
+ placeholderTextColor={themeStyles.isDarkTheme ? '#aaa' : '#999'}
447
+ selectionColor={themeStyles.primaryColor}
448
+ />
449
+ </View>
266
450
  </View>
267
451
  </View>
268
452
  </View>
269
453
  </View>
270
454
  );
271
455
  }
456
+
457
+ if (type === 'location') {
458
+ return (
459
+ <View style={[styles.editingFieldContainer, { backgroundColor: themeStyles.backgroundColor }]}>
460
+ <View style={styles.editingFieldContent}>
461
+ <View style={styles.newValueSection}>
462
+ <View style={styles.editingFieldHeader}>
463
+ <Text style={[styles.editingFieldLabel, { color: themeStyles.isDarkTheme ? '#FFFFFF' : '#1A1A1A' }]}>Manage Your Locations</Text>
464
+ </View>
465
+
466
+ {/* Add new location section */}
467
+ {isAddingLocation ? (
468
+ <View style={styles.addLocationSection}>
469
+ <Text style={styles.addLocationLabel}>
470
+ Add New Location
471
+ {isSearchingLocations && (
472
+ <Text style={styles.searchingText}> • Searching...</Text>
473
+ )}
474
+ </Text>
475
+ <View style={styles.addLocationInputContainer}>
476
+ <TextInput
477
+ style={styles.addLocationInput}
478
+ value={newLocationQuery}
479
+ onChangeText={(text) => {
480
+ setNewLocationQuery(text);
481
+ searchLocations(text);
482
+ }}
483
+ placeholder="Search for a location..."
484
+ placeholderTextColor={themeStyles.isDarkTheme ? '#aaa' : '#999'}
485
+ autoFocus
486
+ selectionColor={themeStyles.primaryColor}
487
+ />
488
+ <View style={styles.addLocationButtons}>
489
+ <TouchableOpacity
490
+ style={[styles.addLocationButton, styles.cancelButton]}
491
+ onPress={() => {
492
+ setIsAddingLocation(false);
493
+ setNewLocationQuery('');
494
+ setLocationSearchResults([]);
495
+ }}
496
+ >
497
+ <Text style={styles.cancelButtonText}>Cancel</Text>
498
+ </TouchableOpacity>
499
+ </View>
500
+ </View>
501
+
502
+ {/* Search results */}
503
+ {locationSearchResults.length > 0 && (
504
+ <View style={styles.searchResults}>
505
+ {locationSearchResults.map((result) => (
506
+ <TouchableOpacity
507
+ key={result.place_id}
508
+ style={styles.searchResultItem}
509
+ onPress={() => addLocation(result)}
510
+ >
511
+ <Text style={styles.searchResultName} numberOfLines={2}>
512
+ {result.display_name}
513
+ </Text>
514
+ <Text style={styles.searchResultType}>
515
+ {result.type}
516
+ </Text>
517
+ </TouchableOpacity>
518
+ ))}
519
+ </View>
520
+ )}
521
+ </View>
522
+ ) : (
523
+ <TouchableOpacity
524
+ style={styles.addLocationTrigger}
525
+ onPress={() => setIsAddingLocation(true)}
526
+ >
527
+ <OxyIcon name="add" size={20} color={themeStyles.primaryColor} />
528
+ <Text style={styles.addLocationTriggerText}>Add a new location</Text>
529
+ </TouchableOpacity>
530
+ )}
531
+
532
+ {/* Existing locations list */}
533
+ {tempLocations.length > 0 && (
534
+ <View style={styles.locationsList}>
535
+ <Text style={styles.locationsListTitle}>Your Locations ({tempLocations.length})</Text>
536
+ {tempLocations.map((location, index) => (
537
+ <View key={location.id} style={styles.locationItem}>
538
+ <View style={styles.locationItemContent}>
539
+ <View style={styles.locationItemDragHandle}>
540
+ <View style={styles.reorderButtons}>
541
+ <TouchableOpacity
542
+ style={[styles.reorderButton, index === 0 && styles.reorderButtonDisabled]}
543
+ onPress={() => index > 0 && moveLocation(index, index - 1)}
544
+ disabled={index === 0}
545
+ >
546
+ <OxyIcon name="chevron-up" size={12} color={index === 0 ? "#ccc" : "#666"} />
547
+ </TouchableOpacity>
548
+ <TouchableOpacity
549
+ style={[styles.reorderButton, index === tempLocations.length - 1 && styles.reorderButtonDisabled]}
550
+ onPress={() => index < tempLocations.length - 1 && moveLocation(index, index + 1)}
551
+ disabled={index === tempLocations.length - 1}
552
+ >
553
+ <OxyIcon name="chevron-down" size={12} color={index === tempLocations.length - 1 ? "#ccc" : "#666"} />
554
+ </TouchableOpacity>
555
+ </View>
556
+ </View>
557
+ <View style={styles.locationItemInfo}>
558
+ <View style={styles.locationItemHeader}>
559
+ <Text style={styles.locationItemName} numberOfLines={1}>
560
+ {location.name}
561
+ </Text>
562
+ {location.label && (
563
+ <View style={styles.locationLabel}>
564
+ <Text style={styles.locationLabelText}>
565
+ {location.label}
566
+ </Text>
567
+ </View>
568
+ )}
569
+ </View>
570
+ {location.coordinates && (
571
+ <Text style={styles.locationCoordinates}>
572
+ {location.coordinates.lat.toFixed(4)}, {location.coordinates.lon.toFixed(4)}
573
+ </Text>
574
+ )}
575
+ </View>
576
+ <View style={styles.locationItemActions}>
577
+ <TouchableOpacity
578
+ style={styles.locationItemButton}
579
+ onPress={() => removeLocation(location.id)}
580
+ >
581
+ <OxyIcon name="trash" size={14} color="#FF3B30" />
582
+ </TouchableOpacity>
583
+ </View>
584
+ </View>
585
+ {index < tempLocations.length - 1 && (
586
+ <View style={styles.locationItemDivider} />
587
+ )}
588
+ </View>
589
+ ))}
590
+ <View style={styles.reorderHint}>
591
+ <Text style={styles.reorderHintText}>Use ↑↓ buttons to reorder your locations</Text>
592
+ </View>
593
+ </View>
594
+ )}
595
+ </View>
596
+ </View>
597
+ </View>
598
+ );
599
+ }
600
+
601
+ if (type === 'links') {
602
+ return (
603
+ <View style={[styles.editingFieldContainer, { backgroundColor: themeStyles.backgroundColor }]}>
604
+ <View style={styles.editingFieldContent}>
605
+ <View style={styles.newValueSection}>
606
+ <View style={styles.editingFieldHeader}>
607
+ <Text style={[styles.editingFieldLabel, { color: themeStyles.isDarkTheme ? '#FFFFFF' : '#1A1A1A' }]}>Manage Your Links</Text>
608
+ </View>
609
+
610
+ <GroupedSection
611
+ items={[
612
+ // Add new link item
613
+ ...(isAddingLink ? [{
614
+ id: 'add-link-input',
615
+ icon: 'add',
616
+ iconColor: '#32D74B',
617
+ title: 'Add New Link',
618
+ subtitle: isFetchingMetadata ? 'Fetching metadata...' : 'Enter URL to add a new link',
619
+ multiRow: true,
620
+ customContent: (
621
+ <View style={styles.addLinkInputContainer}>
622
+ <TextInput
623
+ style={styles.addLinkInput}
624
+ value={newLinkUrl}
625
+ onChangeText={setNewLinkUrl}
626
+ placeholder="Enter URL (e.g., https://example.com)"
627
+ placeholderTextColor={themeStyles.isDarkTheme ? '#aaa' : '#999'}
628
+ keyboardType="url"
629
+ autoFocus
630
+ selectionColor={themeStyles.primaryColor}
631
+ />
632
+ <View style={styles.addLinkButtons}>
633
+ <TouchableOpacity
634
+ style={[styles.addLinkButton, styles.cancelButton]}
635
+ onPress={() => {
636
+ setIsAddingLink(false);
637
+ setNewLinkUrl('');
638
+ }}
639
+ >
640
+ <Text style={styles.cancelButtonText}>Cancel</Text>
641
+ </TouchableOpacity>
642
+ <TouchableOpacity
643
+ style={[styles.addLinkButton, styles.addButton, { opacity: isFetchingMetadata ? 0.5 : 1 }]}
644
+ onPress={addLink}
645
+ disabled={isFetchingMetadata}
646
+ >
647
+ {isFetchingMetadata ? (
648
+ <ActivityIndicator size="small" color="#fff" />
649
+ ) : (
650
+ <Text style={styles.addButtonText}>Add</Text>
651
+ )}
652
+ </TouchableOpacity>
653
+ </View>
654
+ </View>
655
+ ),
656
+ }] : [{
657
+ id: 'add-link-trigger',
658
+ icon: 'add',
659
+ iconColor: '#32D74B',
660
+ title: 'Add a new link',
661
+ subtitle: 'Tap to add a new link to your profile',
662
+ onPress: () => setIsAddingLink(true),
663
+ }]),
664
+ // Existing links
665
+ ...tempLinksWithMetadata.map((link, index) => ({
666
+ id: link.id,
667
+ image: link.image || undefined,
668
+ imageSize: 32,
669
+ icon: link.image ? undefined : 'link',
670
+ iconColor: '#32D74B',
671
+ title: link.title || link.url,
672
+ subtitle: link.description && link.description !== link.title ? link.description : link.url,
673
+ multiRow: true,
674
+ customContent: (
675
+ <View style={styles.linkItemActions}>
676
+ <View style={styles.reorderButtons}>
677
+ <TouchableOpacity
678
+ style={[styles.reorderButton, index === 0 && styles.reorderButtonDisabled]}
679
+ onPress={() => index > 0 && moveLink(index, index - 1)}
680
+ disabled={index === 0}
681
+ >
682
+ <OxyIcon name="chevron-up" size={12} color={index === 0 ? "#ccc" : "#666"} />
683
+ </TouchableOpacity>
684
+ <TouchableOpacity
685
+ style={[styles.reorderButton, index === tempLinksWithMetadata.length - 1 && styles.reorderButtonDisabled]}
686
+ onPress={() => index < tempLinksWithMetadata.length - 1 && moveLink(index, index + 1)}
687
+ disabled={index === tempLinksWithMetadata.length - 1}
688
+ >
689
+ <OxyIcon name="chevron-down" size={12} color={index === tempLinksWithMetadata.length - 1 ? "#ccc" : "#666"} />
690
+ </TouchableOpacity>
691
+ </View>
692
+ <TouchableOpacity
693
+ style={styles.linkItemButton}
694
+ onPress={() => removeLink(link.id)}
695
+ >
696
+ <OxyIcon name="trash" size={14} color="#FF3B30" />
697
+ </TouchableOpacity>
698
+ </View>
699
+ ),
700
+ })),
701
+ ]}
702
+ theme={theme}
703
+ />
704
+ {tempLinksWithMetadata.length > 0 && (
705
+ <View style={styles.reorderHint}>
706
+ <Text style={styles.reorderHintText}>Use ↑↓ buttons to reorder your links</Text>
707
+ </View>
708
+ )}
709
+ </View>
710
+ </View>
711
+ </View>
712
+ );
713
+ }
272
714
  const fieldConfig = {
273
715
  displayName: { label: 'Display Name', value: displayName, placeholder: 'Enter your display name', icon: 'person', color: '#007AFF', multiline: false, keyboardType: 'default' as const },
274
716
  username: { label: 'Username', value: username, placeholder: 'Choose a username', icon: 'at', color: '#5856D6', multiline: false, keyboardType: 'default' as const },
@@ -305,12 +747,14 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
305
747
  };
306
748
 
307
749
  return (
308
- <View style={styles.editingFieldContainer}>
750
+ <View style={[styles.editingFieldContainer, { backgroundColor: themeStyles.backgroundColor }]}>
309
751
  <View style={styles.editingFieldContent}>
310
752
  <View style={styles.newValueSection}>
311
- <Text style={styles.editingFieldLabel}>
312
- {`Enter ${config.label.toLowerCase()}:`}
313
- </Text>
753
+ <View style={styles.editingFieldHeader}>
754
+ <Text style={[styles.editingFieldLabel, { color: themeStyles.isDarkTheme ? '#FFFFFF' : '#1A1A1A' }]}>
755
+ {`Enter ${config.label.toLowerCase()}:`}
756
+ </Text>
757
+ </View>
314
758
  <TextInput
315
759
  style={[
316
760
  config.multiline ? styles.editingFieldTextArea : styles.editingFieldInput,
@@ -336,42 +780,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
336
780
  );
337
781
  };
338
782
 
339
- const renderField = (
340
- type: string,
341
- label: string,
342
- value: string,
343
- placeholder: string,
344
- icon: string,
345
- iconColor: string,
346
- multiline = false,
347
- keyboardType: 'default' | 'email-address' | 'url' = 'default',
348
- isFirst = false,
349
- isLast = false
350
- ) => {
351
- const itemStyles = [
352
- styles.settingItem,
353
- isFirst && styles.firstSettingItem,
354
- isLast && styles.lastSettingItem
355
- ];
356
783
 
357
- return (
358
- <TouchableOpacity
359
- style={itemStyles}
360
- onPress={() => startEditing(type, value)}
361
- >
362
- <View style={styles.settingInfo}>
363
- <OxyIcon name={icon} size={20} color={iconColor} style={styles.settingIcon} />
364
- <View>
365
- <Text style={styles.settingLabel}>{label}</Text>
366
- <Text style={styles.settingDescription}>
367
- {value || placeholder}
368
- </Text>
369
- </View>
370
- </View>
371
- <OxyIcon name="chevron-forward" size={16} color="#ccc" />
372
- </TouchableOpacity>
373
- );
374
- };
375
784
 
376
785
  if (authLoading || !isAuthenticated) {
377
786
  return (
@@ -384,67 +793,71 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
384
793
  return (
385
794
  <View style={[styles.container, { backgroundColor: themeStyles.backgroundColor }]}>
386
795
  {/* Header */}
387
- <View style={styles.header}>
388
- {editingField ? (
389
- <View style={styles.editingHeader}>
390
- <View style={styles.editingHeaderTop}>
391
- <TouchableOpacity style={styles.cancelButton} onPress={cancelEditing}>
392
- <Ionicons name="close" size={24} color="#666" />
393
- </TouchableOpacity>
394
- <Animated.View style={{ transform: [{ scale: saveButtonScale }] }}>
395
- <TouchableOpacity
396
- style={[
397
- styles.saveHeaderButton,
398
- {
399
- opacity: isSaving ? 0.7 : 1,
400
- backgroundColor: editingField ? getFieldIcon(editingField).color : '#007AFF'
401
- }
402
- ]}
403
- onPress={() => saveField(editingField)}
404
- disabled={isSaving}
405
- >
406
- {isSaving ? (
407
- <ActivityIndicator size="small" color="#fff" />
408
- ) : (
409
- <Text style={styles.saveButtonText}>Save</Text>
410
- )}
411
- </TouchableOpacity>
412
- </Animated.View>
413
- </View>
414
- <View style={styles.editingHeaderBottom}>
415
- <View style={styles.headerTitleWithIcon}>
416
- <OxyIcon
417
- name={getFieldIcon(editingField).name}
418
- size={50}
419
- color={getFieldIcon(editingField).color}
420
- style={styles.headerIcon}
421
- />
422
- <Text style={styles.headerTitleLarge}>{getFieldLabel(editingField)}</Text>
423
- </View>
796
+ {editingField ? (
797
+ <View style={[styles.editingHeader, { backgroundColor: '#FFFFFF', borderBottomColor: themeStyles.isDarkTheme ? '#38383A' : '#E9ECEF' }]}>
798
+ <View style={styles.editingHeaderContent}>
799
+ <TouchableOpacity style={styles.editingBackButton} onPress={cancelEditing}>
800
+ <OxyIcon name="chevron-back" size={20} color={themeStyles.primaryColor} />
801
+ </TouchableOpacity>
802
+ <View style={styles.editingTitleContainer}>
424
803
  </View>
425
- </View>
426
- ) : (
427
- <View style={styles.normalHeader}>
428
- <TouchableOpacity style={styles.cancelButton} onPress={onClose || goBack}>
429
- <Ionicons name="close" size={24} color="#666" />
804
+ <TouchableOpacity
805
+ style={[styles.editingSaveButton, { opacity: isSaving ? 0.5 : 1 }]}
806
+ onPress={() => saveField(editingField)}
807
+ disabled={isSaving}
808
+ >
809
+ {isSaving ? (
810
+ <ActivityIndicator size="small" color={themeStyles.primaryColor} />
811
+ ) : (
812
+ <Text style={[styles.editingSaveButtonText, { color: themeStyles.primaryColor }]}>Save</Text>
813
+ )}
430
814
  </TouchableOpacity>
431
- <Text style={styles.headerTitle}>Account Settings</Text>
432
- <Animated.View style={{ transform: [{ scale: saveButtonScale }] }}>
433
- <TouchableOpacity
434
- style={[styles.saveIconButton, { opacity: isSaving ? 0.7 : 1 }]}
435
- onPress={handleSave}
436
- disabled={isSaving}
437
- >
438
- {isSaving ? (
439
- <ActivityIndicator size="small" color={themeStyles.primaryColor} />
440
- ) : (
441
- <Ionicons name="checkmark" size={24} color={themeStyles.primaryColor} />
442
- )}
443
- </TouchableOpacity>
444
- </Animated.View>
445
815
  </View>
446
- )}
447
- </View>
816
+ <View style={styles.editingHeaderBottom}>
817
+ <OxyIcon
818
+ name={
819
+ editingField === 'displayName' ? 'person' :
820
+ editingField === 'username' ? 'at' :
821
+ editingField === 'email' ? 'mail' :
822
+ editingField === 'bio' ? 'document-text' :
823
+ editingField === 'location' ? 'location' :
824
+ editingField === 'links' ? 'link' : 'person'
825
+ }
826
+ size={56}
827
+ color={
828
+ editingField === 'displayName' ? '#007AFF' :
829
+ editingField === 'username' ? '#5856D6' :
830
+ editingField === 'email' ? '#FF9500' :
831
+ editingField === 'bio' ? '#34C759' :
832
+ editingField === 'location' ? '#FF3B30' :
833
+ editingField === 'links' ? '#32D74B' : '#007AFF'
834
+ }
835
+ style={styles.editingBottomIcon}
836
+ />
837
+ <Text style={[styles.editingBottomTitle, { color: themeStyles.isDarkTheme ? '#FFFFFF' : '#1A1A1A' }]}>
838
+ {editingField === 'displayName' ? 'Display Name' :
839
+ editingField === 'username' ? 'Username' :
840
+ editingField === 'email' ? 'Email' :
841
+ editingField === 'bio' ? 'Bio' :
842
+ editingField === 'location' ? 'Location' :
843
+ editingField === 'links' ? 'Links' : 'Field'}
844
+ </Text>
845
+ </View>
846
+ </View>
847
+ ) : (
848
+ <Header
849
+ title="Edit Profile"
850
+ theme={theme}
851
+ onBack={goBack || onClose}
852
+ rightAction={{
853
+ icon: 'checkmark',
854
+ onPress: handleSave,
855
+ loading: isSaving,
856
+ disabled: isSaving,
857
+ }}
858
+ elevation="subtle"
859
+ />
860
+ )}
448
861
 
449
862
  <ScrollView style={editingField ? styles.contentEditing : styles.content}>
450
863
  {editingField ? (
@@ -459,149 +872,172 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
459
872
  <View style={styles.section}>
460
873
  <Text style={styles.sectionTitle}>Profile Picture</Text>
461
874
 
462
- <TouchableOpacity
463
- style={[styles.settingItem, styles.firstSettingItem, styles.lastSettingItem]}
464
- onPress={handleAvatarUpdate}
465
- >
466
- <View style={styles.userIcon}>
467
- <Avatar
468
- uri={avatarUrl}
469
- name={displayName || username}
470
- size={50}
471
- theme={theme}
472
- />
473
- </View>
474
- <View style={styles.settingInfo}>
475
- <View>
476
- <Text style={styles.settingLabel}>Profile Photo</Text>
477
- <Text style={styles.settingDescription}>
478
- {avatarUrl ? 'Tap to change your profile picture' : 'Tap to add a profile picture'}
479
- </Text>
480
- </View>
481
- </View>
482
- <OxyIcon name="chevron-forward" size={16} color="#ccc" />
483
- </TouchableOpacity>
875
+ <GroupedSection
876
+ items={[
877
+ {
878
+ id: 'profile-photo',
879
+ icon: avatarUrl ? undefined : 'person',
880
+ iconColor: '#007AFF',
881
+ image: avatarUrl || undefined,
882
+ imageSize: 40,
883
+ title: 'Profile Photo',
884
+ subtitle: avatarUrl ? 'Tap to change your profile picture' : 'Tap to add a profile picture',
885
+ onPress: handleAvatarUpdate,
886
+ },
887
+ ]}
888
+ theme={theme}
889
+ />
484
890
  </View>
485
891
 
486
892
  {/* Basic Information */}
487
893
  <View style={styles.section}>
488
894
  <Text style={styles.sectionTitle}>Basic Information</Text>
489
895
 
490
- {renderField(
491
- 'displayName',
492
- 'Display Name',
493
- [displayName, lastName].filter(Boolean).join(' '), // Show full name
494
- 'Add your display name',
495
- 'person',
496
- '#007AFF',
497
- false,
498
- 'default',
499
- true,
500
- false
501
- )}
502
-
503
- {renderField(
504
- 'username',
505
- 'Username',
506
- username,
507
- 'Choose a username',
508
- 'at',
509
- '#5856D6',
510
- false,
511
- 'default',
512
- false,
513
- false
514
- )}
515
-
516
- {renderField(
517
- 'email',
518
- 'Email',
519
- email,
520
- 'Add your email address',
521
- 'mail',
522
- '#FF9500',
523
- false,
524
- 'email-address',
525
- false,
526
- true
527
- )}
896
+ <GroupedSection
897
+ items={[
898
+ {
899
+ id: 'display-name',
900
+ icon: 'person',
901
+ iconColor: '#007AFF',
902
+ title: 'Display Name',
903
+ subtitle: [displayName, lastName].filter(Boolean).join(' ') || 'Add your display name',
904
+ onPress: () => startEditing('displayName', ''),
905
+ },
906
+ {
907
+ id: 'username',
908
+ icon: 'at',
909
+ iconColor: '#5856D6',
910
+ title: 'Username',
911
+ subtitle: username || 'Choose a username',
912
+ onPress: () => startEditing('username', username),
913
+ },
914
+ {
915
+ id: 'email',
916
+ icon: 'mail',
917
+ iconColor: '#FF9500',
918
+ title: 'Email',
919
+ subtitle: email || 'Add your email address',
920
+ onPress: () => startEditing('email', email),
921
+ },
922
+ ]}
923
+ theme={theme}
924
+ />
528
925
  </View>
529
926
 
530
927
  {/* About You */}
531
928
  <View style={styles.section}>
532
929
  <Text style={styles.sectionTitle}>About You</Text>
533
930
 
534
- {renderField(
535
- 'bio',
536
- 'Bio',
537
- bio,
538
- 'Tell people about yourself',
539
- 'document-text',
540
- '#34C759',
541
- true,
542
- 'default',
543
- true,
544
- false
545
- )}
546
-
547
- {renderField(
548
- 'location',
549
- 'Location',
550
- location,
551
- 'Add your location',
552
- 'location',
553
- '#FF3B30',
554
- false,
555
- 'default',
556
- false,
557
- false
558
- )}
559
-
560
- {renderField(
561
- 'links',
562
- 'Links',
563
- links.join(', '),
564
- 'Add your links',
565
- 'link',
566
- '#32D74B',
567
- false,
568
- 'url',
569
- false,
570
- true
571
- )}
931
+ <GroupedSection
932
+ items={[
933
+ {
934
+ id: 'bio',
935
+ icon: 'document-text',
936
+ iconColor: '#34C759',
937
+ title: 'Bio',
938
+ subtitle: bio || 'Tell people about yourself',
939
+ onPress: () => startEditing('bio', bio),
940
+ },
941
+ {
942
+ id: 'locations',
943
+ icon: 'location',
944
+ iconColor: '#FF3B30',
945
+ title: 'Locations',
946
+ subtitle: tempLocations.length > 0 ? `${tempLocations.length} location${tempLocations.length !== 1 ? 's' : ''} added` : 'Add your locations',
947
+ onPress: () => startEditing('location', ''),
948
+ customContentBelow: tempLocations.length > 0 && (
949
+ <View style={styles.linksPreviewContainer}>
950
+ {tempLocations.slice(0, 2).map((location, index) => (
951
+ <View key={location.id || index} style={styles.linkPreviewItem}>
952
+ <View style={styles.linkPreviewImage}>
953
+ <Text style={styles.linkPreviewImageText}>
954
+ {location.name.charAt(0).toUpperCase()}
955
+ </Text>
956
+ </View>
957
+ <View style={styles.linkPreviewContent}>
958
+ <Text style={styles.linkPreviewTitle} numberOfLines={1}>
959
+ {location.name}
960
+ </Text>
961
+ {location.label && (
962
+ <Text style={styles.linkPreviewSubtitle}>
963
+ {location.label}
964
+ </Text>
965
+ )}
966
+ </View>
967
+ </View>
968
+ ))}
969
+ {tempLocations.length > 2 && (
970
+ <Text style={styles.linkPreviewMore}>
971
+ +{tempLocations.length - 2} more
972
+ </Text>
973
+ )}
974
+ </View>
975
+ ),
976
+ },
977
+ {
978
+ id: 'links',
979
+ icon: 'link',
980
+ iconColor: '#32D74B',
981
+ title: 'Links',
982
+ subtitle: tempLinksWithMetadata.length > 0 ? `${tempLinksWithMetadata.length} link${tempLinksWithMetadata.length !== 1 ? 's' : ''} added` : 'Add your links',
983
+ onPress: () => startEditing('links', ''),
984
+ multiRow: true,
985
+ customContentBelow: tempLinksWithMetadata.length > 0 && (
986
+ <View style={styles.linksPreviewContainer}>
987
+ {tempLinksWithMetadata.slice(0, 2).map((link, index) => (
988
+ <View key={link.id || index} style={styles.linkPreviewItem}>
989
+ {link.image ? (
990
+ <Image source={{ uri: link.image }} style={styles.linkPreviewImage} />
991
+ ) : (
992
+ <View style={styles.linkPreviewImage}>
993
+ <Text style={styles.linkPreviewImageText}>
994
+ {link.title?.charAt(0).toUpperCase() || link.url.charAt(0).toUpperCase()}
995
+ </Text>
996
+ </View>
997
+ )}
998
+ <Text style={styles.linkPreviewTitle} numberOfLines={1}>
999
+ {link.title || link.url}
1000
+ </Text>
1001
+ </View>
1002
+ ))}
1003
+ {tempLinksWithMetadata.length > 2 && (
1004
+ <Text style={styles.linkPreviewMore}>
1005
+ +{tempLinksWithMetadata.length - 2} more
1006
+ </Text>
1007
+ )}
1008
+ </View>
1009
+ ),
1010
+ },
1011
+ ]}
1012
+ theme={theme}
1013
+ />
572
1014
  </View>
573
1015
 
574
1016
  {/* Quick Actions */}
575
1017
  <View style={styles.section}>
576
1018
  <Text style={styles.sectionTitle}>Quick Actions</Text>
577
1019
 
578
- <TouchableOpacity
579
- style={[styles.settingItem, styles.firstSettingItem]}
580
- onPress={() => toast.info('Privacy settings coming soon!')}
581
- >
582
- <View style={styles.settingInfo}>
583
- <OxyIcon name="shield-checkmark" size={20} color="#8E8E93" style={styles.settingIcon} />
584
- <View>
585
- <Text style={styles.settingLabel}>Privacy Settings</Text>
586
- <Text style={styles.settingDescription}>Control who can see your profile</Text>
587
- </View>
588
- </View>
589
- <OxyIcon name="chevron-forward" size={16} color="#ccc" />
590
- </TouchableOpacity>
591
-
592
- <TouchableOpacity
593
- style={[styles.settingItem, styles.lastSettingItem]}
594
- onPress={() => toast.info('Account verification coming soon!')}
595
- >
596
- <View style={styles.settingInfo}>
597
- <OxyIcon name="checkmark-circle" size={20} color="#30D158" style={styles.settingIcon} />
598
- <View>
599
- <Text style={styles.settingLabel}>Verify Account</Text>
600
- <Text style={styles.settingDescription}>Get a verified badge</Text>
601
- </View>
602
- </View>
603
- <OxyIcon name="chevron-forward" size={16} color="#ccc" />
604
- </TouchableOpacity>
1020
+ <GroupedSection
1021
+ items={[
1022
+ {
1023
+ id: 'privacy-settings',
1024
+ icon: 'shield-checkmark',
1025
+ iconColor: '#8E8E93',
1026
+ title: 'Privacy Settings',
1027
+ subtitle: 'Control who can see your profile',
1028
+ onPress: () => toast.info('Privacy settings coming soon!'),
1029
+ },
1030
+ {
1031
+ id: 'verify-account',
1032
+ icon: 'checkmark-circle',
1033
+ iconColor: '#30D158',
1034
+ title: 'Verify Account',
1035
+ subtitle: 'Get a verified badge',
1036
+ onPress: () => toast.info('Account verification coming soon!'),
1037
+ },
1038
+ ]}
1039
+ theme={theme}
1040
+ />
605
1041
  </View>
606
1042
  </>
607
1043
  )}
@@ -615,203 +1051,418 @@ const styles = StyleSheet.create({
615
1051
  flex: 1,
616
1052
  backgroundColor: '#f2f2f2',
617
1053
  },
618
- header: {
619
- paddingHorizontal: 20,
620
- paddingVertical: 10,
1054
+ content: {
1055
+ flex: 1,
1056
+ padding: 16,
1057
+ },
1058
+ contentEditing: {
1059
+ flex: 1,
1060
+ padding: 0,
1061
+ },
1062
+ section: {
1063
+ marginBottom: 24,
1064
+ },
1065
+ sectionTitle: {
1066
+ fontSize: 16,
1067
+ fontWeight: '600',
1068
+ color: '#333',
1069
+ marginBottom: 12,
1070
+ fontFamily: fontFamilies.phuduSemiBold,
1071
+ },
1072
+
1073
+ userIcon: {
1074
+ marginRight: 12,
1075
+ },
1076
+
1077
+ // Editing-only mode styles
1078
+ editingOnlyContainer: {
1079
+ flex: 1,
1080
+ },
1081
+ editingFieldContainer: {
621
1082
  backgroundColor: '#fff',
622
- borderBottomWidth: 1,
623
- borderBottomColor: '#e0e0e0',
1083
+ padding: 16,
1084
+ flex: 1,
624
1085
  },
625
- normalHeader: {
1086
+ editingFieldHeader: {
1087
+ marginBottom: 8,
626
1088
  flexDirection: 'row',
627
- justifyContent: 'space-between',
628
1089
  alignItems: 'center',
629
1090
  },
630
- editingHeader: {
631
- flexDirection: 'column',
632
- },
633
- editingHeaderTop: {
1091
+ editingFieldTitleContainer: {
634
1092
  flexDirection: 'row',
635
- justifyContent: 'space-between',
636
1093
  alignItems: 'center',
637
- marginBottom: 16,
638
1094
  },
639
- editingHeaderBottom: {
1095
+ editingFieldIcon: {
1096
+ marginRight: 12,
1097
+ },
1098
+ editingFieldTitle: {
1099
+ fontSize: 20,
1100
+ fontWeight: '600',
1101
+ color: '#000',
1102
+ },
1103
+ editingFieldContent: {
1104
+ flex: 1,
1105
+ },
1106
+ newValueSection: {
1107
+ flex: 1,
1108
+ },
1109
+ editingFieldLabel: {
1110
+ fontSize: 16,
1111
+ fontWeight: '600',
1112
+ color: '#333',
1113
+ marginBottom: 12,
1114
+ fontFamily: fontFamilies.phuduSemiBold,
1115
+ },
1116
+ editingFieldInput: {
1117
+ backgroundColor: '#fff',
1118
+ borderWidth: 2,
1119
+ borderColor: '#e0e0e0',
1120
+ borderRadius: 12,
1121
+ padding: 16,
1122
+ fontSize: 17,
1123
+ minHeight: 52,
1124
+ fontWeight: '400',
1125
+ },
1126
+ editingFieldTextArea: {
1127
+ backgroundColor: '#fff',
1128
+ borderWidth: 2,
1129
+ borderColor: '#e0e0e0',
1130
+ borderRadius: 12,
1131
+ padding: 16,
1132
+ fontSize: 17,
1133
+ minHeight: 120,
1134
+ textAlignVertical: 'top',
1135
+ fontWeight: '400',
1136
+ },
1137
+ // Custom editing header styles
1138
+ editingHeader: {
1139
+ paddingTop: Platform.OS === 'ios' ? 50 : 16,
1140
+ paddingBottom: 0,
1141
+ borderBottomWidth: 1,
1142
+ backgroundColor: '#fff',
1143
+ },
1144
+ editingHeaderContent: {
640
1145
  flexDirection: 'row',
641
1146
  alignItems: 'center',
642
- justifyContent: 'flex-start',
1147
+ paddingHorizontal: 16,
1148
+ minHeight: 44,
643
1149
  },
644
- headerTitle: {
645
- fontSize: 24,
646
- fontWeight: 'bold',
647
- color: '#000',
648
- fontFamily: fontFamilies.phuduBold,
1150
+ editingBackButton: {
1151
+ width: 32,
1152
+ height: 32,
1153
+ borderRadius: 16,
1154
+ backgroundColor: '#F8F9FA',
1155
+ alignItems: 'center',
1156
+ justifyContent: 'center',
1157
+ marginRight: 12,
649
1158
  },
650
- headerTitleWithIcon: {
1159
+ editingTitleContainer: {
1160
+ flex: 1,
651
1161
  flexDirection: 'column',
652
1162
  alignItems: 'flex-start',
653
- flex: 1,
654
- justifyContent: 'flex-start',
655
- maxWidth: '90%',
656
- },
657
- headerTitleLarge: {
658
- fontSize: 48,
659
- fontWeight: '800',
660
- color: '#000',
661
- fontFamily: fontFamilies.phuduExtraBold,
662
- textAlign: 'left',
1163
+ justifyContent: 'flex-end',
1164
+ paddingBottom: 8,
663
1165
  },
664
- headerIcon: {
665
- marginBottom: 2,
1166
+ editingTitleIcon: {
1167
+ marginBottom: 4,
1168
+ alignSelf: 'flex-start',
666
1169
  },
667
- cancelButton: {
668
- padding: 5,
1170
+ editingTitle: {
1171
+ fontSize: 18,
1172
+ fontWeight: '700',
1173
+ fontFamily: fontFamilies.phuduBold,
1174
+ letterSpacing: -0.3,
1175
+ lineHeight: 22,
1176
+ textAlign: 'left',
1177
+ alignSelf: 'flex-start',
669
1178
  },
670
- saveHeaderButton: {
1179
+ editingSaveButton: {
671
1180
  paddingHorizontal: 16,
672
1181
  paddingVertical: 8,
673
- borderRadius: 20,
1182
+ borderRadius: 18,
1183
+ backgroundColor: '#F8F9FA',
674
1184
  minWidth: 60,
675
1185
  alignItems: 'center',
676
1186
  justifyContent: 'center',
677
1187
  },
678
- saveIconButton: {
679
- padding: 5,
680
- },
681
- saveButtonText: {
682
- color: '#fff',
1188
+ editingSaveButtonText: {
683
1189
  fontSize: 16,
684
1190
  fontWeight: '600',
685
1191
  fontFamily: fontFamilies.phuduSemiBold,
686
1192
  },
687
- content: {
688
- flex: 1,
689
- padding: 16,
1193
+ editingHeaderBottom: {
1194
+ flexDirection: 'column',
1195
+ alignItems: 'flex-start',
1196
+ paddingHorizontal: 16,
1197
+ paddingBottom: 8,
1198
+ paddingTop: 8,
690
1199
  },
691
- contentEditing: {
692
- flex: 1,
693
- padding: 0,
1200
+ editingBottomIcon: {
1201
+ marginBottom: 8,
1202
+ alignSelf: 'flex-start',
694
1203
  },
695
- section: {
696
- marginBottom: 24,
1204
+ editingBottomTitle: {
1205
+ fontSize: 32,
1206
+ fontWeight: '700',
1207
+ fontFamily: fontFamilies.phuduBold,
1208
+ letterSpacing: -0.5,
1209
+ lineHeight: 36,
1210
+ textAlign: 'left',
1211
+ alignSelf: 'flex-start',
697
1212
  },
698
- sectionTitle: {
699
- fontSize: 16,
1213
+ // Links management styles
1214
+ addLinkSection: {
1215
+ marginBottom: 16,
1216
+ padding: 12,
1217
+ backgroundColor: '#F8F9FA',
1218
+ borderRadius: 8,
1219
+ borderWidth: 1,
1220
+ borderColor: '#E9ECEF',
1221
+ },
1222
+ addLinkLabel: {
1223
+ fontSize: 14,
700
1224
  fontWeight: '600',
701
1225
  color: '#333',
702
- marginBottom: 12,
703
- fontFamily: fontFamilies.phuduSemiBold,
1226
+ marginBottom: 8,
704
1227
  },
705
- settingItem: {
1228
+ addLinkInputContainer: {
1229
+ gap: 8,
1230
+ },
1231
+ addLinkInput: {
706
1232
  backgroundColor: '#fff',
707
- padding: 16,
1233
+ borderWidth: 1,
1234
+ borderColor: '#E9ECEF',
1235
+ borderRadius: 6,
1236
+ padding: 10,
1237
+ fontSize: 14,
1238
+ minHeight: 36,
1239
+ },
1240
+ addLinkButtons: {
708
1241
  flexDirection: 'row',
1242
+ gap: 6,
1243
+ },
1244
+ addLinkButton: {
1245
+ flex: 1,
1246
+ paddingVertical: 8,
1247
+ paddingHorizontal: 12,
1248
+ borderRadius: 6,
709
1249
  alignItems: 'center',
710
- justifyContent: 'space-between',
711
- marginBottom: 2,
1250
+ justifyContent: 'center',
1251
+ },
1252
+ cancelButton: {
1253
+ backgroundColor: '#F8F9FA',
1254
+ borderWidth: 1,
1255
+ borderColor: '#E9ECEF',
712
1256
  },
713
- firstSettingItem: {
714
- borderTopLeftRadius: 24,
715
- borderTopRightRadius: 24,
1257
+ cancelButtonText: {
1258
+ fontSize: 14,
1259
+ fontWeight: '600',
1260
+ color: '#6C757D',
716
1261
  },
717
- lastSettingItem: {
718
- borderBottomLeftRadius: 24,
719
- borderBottomRightRadius: 24,
720
- marginBottom: 8,
1262
+ addButton: {
1263
+ backgroundColor: '#007AFF',
1264
+ },
1265
+ addButtonText: {
1266
+ fontSize: 14,
1267
+ fontWeight: '600',
1268
+ color: '#fff',
721
1269
  },
722
- settingInfo: {
1270
+ addLinkTrigger: {
723
1271
  flexDirection: 'row',
724
1272
  alignItems: 'center',
725
- flex: 1,
1273
+ padding: 12,
1274
+ backgroundColor: '#F8F9FA',
1275
+ borderRadius: 8,
1276
+ borderWidth: 1,
1277
+ borderColor: '#E9ECEF',
1278
+ borderStyle: 'dashed',
1279
+ marginBottom: 16,
726
1280
  },
727
- settingIcon: {
728
- marginRight: 12,
1281
+ addLinkTriggerText: {
1282
+ fontSize: 14,
1283
+ fontWeight: '600',
1284
+ color: '#007AFF',
1285
+ marginLeft: 6,
729
1286
  },
730
- settingLabel: {
1287
+ linksList: {
1288
+ gap: 8,
1289
+ },
1290
+ linksListTitle: {
731
1291
  fontSize: 16,
732
- fontWeight: '500',
1292
+ fontWeight: '700',
733
1293
  color: '#333',
734
- marginBottom: 2,
1294
+ marginBottom: 6,
735
1295
  },
736
- settingDescription: {
737
- fontSize: 14,
738
- color: '#666',
1296
+ linkItem: {
1297
+ backgroundColor: '#fff',
1298
+ borderRadius: 8,
1299
+ borderWidth: 1,
1300
+ borderColor: '#E9ECEF',
1301
+ overflow: 'hidden',
739
1302
  },
740
- userIcon: {
741
- marginRight: 12,
1303
+ linkItemContent: {
1304
+ flexDirection: 'row',
1305
+ padding: 12,
1306
+ alignItems: 'center',
1307
+ },
1308
+ linkItemDragHandle: {
1309
+ width: 24,
1310
+ height: 24,
1311
+ alignItems: 'center',
1312
+ justifyContent: 'center',
1313
+ marginRight: 8,
742
1314
  },
743
- // Inline editing styles
744
- editingContainer: {
1315
+ linkItemInfo: {
745
1316
  flex: 1,
1317
+ marginRight: 8,
1318
+ },
1319
+ linkItemTitle: {
1320
+ fontSize: 14,
1321
+ fontWeight: '600',
1322
+ color: '#333',
1323
+ marginBottom: 2,
746
1324
  },
747
- editingActions: {
1325
+ linkItemDescription: {
1326
+ fontSize: 12,
1327
+ color: '#666',
1328
+ marginBottom: 2,
1329
+ },
1330
+ linkItemUrl: {
1331
+ fontSize: 12,
1332
+ color: '#6C757D',
1333
+ },
1334
+ linkItemActions: {
748
1335
  flexDirection: 'row',
1336
+ gap: 6,
1337
+ },
1338
+ linkItemButton: {
1339
+ width: 28,
1340
+ height: 28,
1341
+ borderRadius: 14,
1342
+ backgroundColor: '#F8F9FA',
749
1343
  alignItems: 'center',
1344
+ justifyContent: 'center',
1345
+ },
1346
+ linkItemDivider: {
1347
+ height: 1,
1348
+ backgroundColor: '#E9ECEF',
1349
+ marginHorizontal: 12,
750
1350
  },
751
- editingButton: {
1351
+ reorderHint: {
752
1352
  padding: 8,
1353
+ alignItems: 'center',
753
1354
  },
754
- editingButtonText: {
755
- fontSize: 16,
756
- fontWeight: '500',
1355
+ reorderHintText: {
1356
+ fontSize: 12,
1357
+ color: '#999',
1358
+ fontStyle: 'italic',
757
1359
  },
758
- inlineInput: {
759
- backgroundColor: '#f8f8f8',
760
- borderWidth: 1,
761
- borderColor: '#e0e0e0',
762
- borderRadius: 8,
763
- padding: 12,
764
- fontSize: 16,
765
- minHeight: 44,
1360
+ reorderButtons: {
1361
+ flexDirection: 'column',
1362
+ gap: 2,
766
1363
  },
767
- inlineTextArea: {
768
- backgroundColor: '#f8f8f8',
1364
+ reorderButton: {
1365
+ width: 20,
1366
+ height: 16,
1367
+ borderRadius: 3,
1368
+ backgroundColor: '#F8F9FA',
1369
+ alignItems: 'center',
1370
+ justifyContent: 'center',
769
1371
  borderWidth: 1,
770
- borderColor: '#e0e0e0',
771
- borderRadius: 8,
772
- padding: 12,
773
- fontSize: 16,
774
- minHeight: 100,
775
- textAlignVertical: 'top',
1372
+ borderColor: '#E9ECEF',
776
1373
  },
777
- // Editing-only mode styles
778
- editingOnlyContainer: {
779
- flex: 1,
1374
+ reorderButtonDisabled: {
1375
+ opacity: 0.3,
780
1376
  },
781
- editingFieldContainer: {
782
- backgroundColor: '#fff',
783
- padding: 16,
1377
+ linkItemImage: {
1378
+ width: 32,
1379
+ height: 32,
1380
+ borderRadius: 16,
1381
+ backgroundColor: '#007AFF',
1382
+ alignItems: 'center',
1383
+ justifyContent: 'center',
1384
+ marginRight: 8,
1385
+ },
1386
+ linkItemImageText: {
1387
+ fontSize: 12,
1388
+ fontWeight: '600',
1389
+ color: '#fff',
1390
+ },
1391
+ fetchingText: {
1392
+ fontSize: 12,
1393
+ color: '#007AFF',
1394
+ fontStyle: 'italic',
1395
+ },
1396
+ linksFieldContent: {
784
1397
  flex: 1,
1398
+ marginLeft: 12,
785
1399
  },
786
- editingFieldHeader: {
787
- marginBottom: 16,
1400
+ linksPreview: {
1401
+ marginTop: 4,
1402
+ flexDirection: 'column',
788
1403
  },
789
- editingFieldTitleContainer: {
1404
+ linksPreviewContainer: {
1405
+ marginTop: 4,
1406
+ flexDirection: 'column',
1407
+ width: '100%',
1408
+ },
1409
+ linkPreviewItem: {
790
1410
  flexDirection: 'row',
791
1411
  alignItems: 'center',
1412
+ marginBottom: 4,
792
1413
  },
793
- editingFieldIcon: {
794
- marginRight: 12,
1414
+ linkPreviewImage: {
1415
+ width: 20,
1416
+ height: 20,
1417
+ borderRadius: 10,
1418
+ backgroundColor: '#007AFF',
1419
+ alignItems: 'center',
1420
+ justifyContent: 'center',
1421
+ marginRight: 6,
795
1422
  },
796
- editingFieldTitle: {
797
- fontSize: 20,
1423
+ linkPreviewImageText: {
1424
+ fontSize: 10,
798
1425
  fontWeight: '600',
799
- color: '#000',
1426
+ color: '#fff',
800
1427
  },
801
- editingFieldContent: {
1428
+ linkPreviewTitle: {
1429
+ fontSize: 13,
1430
+ color: '#666',
802
1431
  flex: 1,
803
1432
  },
804
- newValueSection: {
1433
+ linkPreviewContent: {
805
1434
  flex: 1,
806
1435
  },
807
- editingFieldLabel: {
808
- fontSize: 16,
1436
+ linkPreviewSubtitle: {
1437
+ fontSize: 11,
1438
+ color: '#999',
1439
+ marginTop: 1,
1440
+ },
1441
+ linkPreviewMore: {
1442
+ fontSize: 12,
1443
+ color: '#999',
1444
+ fontStyle: 'italic',
1445
+ },
1446
+ // Location management styles
1447
+ addLocationSection: {
1448
+ marginBottom: 16,
1449
+ },
1450
+ addLocationLabel: {
1451
+ fontSize: 14,
809
1452
  fontWeight: '600',
810
1453
  color: '#333',
811
- marginBottom: 12,
1454
+ marginBottom: 8,
812
1455
  fontFamily: fontFamilies.phuduSemiBold,
813
1456
  },
814
- editingFieldInput: {
1457
+ searchingText: {
1458
+ fontSize: 12,
1459
+ color: '#007AFF',
1460
+ fontStyle: 'italic',
1461
+ },
1462
+ addLocationInputContainer: {
1463
+ marginBottom: 8,
1464
+ },
1465
+ addLocationInput: {
815
1466
  backgroundColor: '#fff',
816
1467
  borderWidth: 2,
817
1468
  borderColor: '#e0e0e0',
@@ -820,17 +1471,128 @@ const styles = StyleSheet.create({
820
1471
  fontSize: 17,
821
1472
  minHeight: 52,
822
1473
  fontWeight: '400',
1474
+ marginBottom: 8,
823
1475
  },
824
- editingFieldTextArea: {
1476
+ addLocationButtons: {
1477
+ flexDirection: 'row',
1478
+ gap: 8,
1479
+ },
1480
+ addLocationButton: {
1481
+ flex: 1,
1482
+ paddingVertical: 12,
1483
+ paddingHorizontal: 16,
1484
+ borderRadius: 8,
1485
+ alignItems: 'center',
1486
+ justifyContent: 'center',
1487
+ },
1488
+ addLocationTrigger: {
1489
+ flexDirection: 'row',
1490
+ alignItems: 'center',
1491
+ paddingVertical: 12,
1492
+ paddingHorizontal: 16,
1493
+ backgroundColor: '#F8F9FA',
1494
+ borderRadius: 8,
1495
+ marginBottom: 16,
1496
+ },
1497
+ addLocationTriggerText: {
1498
+ marginLeft: 8,
1499
+ fontSize: 16,
1500
+ color: '#007AFF',
1501
+ fontWeight: '500',
1502
+ },
1503
+ searchResults: {
825
1504
  backgroundColor: '#fff',
826
- borderWidth: 2,
1505
+ borderWidth: 1,
827
1506
  borderColor: '#e0e0e0',
828
- borderRadius: 12,
829
- padding: 16,
830
- fontSize: 17,
831
- minHeight: 120,
832
- textAlignVertical: 'top',
833
- fontWeight: '400',
1507
+ borderRadius: 8,
1508
+ maxHeight: 200,
1509
+ },
1510
+ searchResultItem: {
1511
+ padding: 12,
1512
+ borderBottomWidth: 1,
1513
+ borderBottomColor: '#f0f0f0',
1514
+ },
1515
+ searchResultName: {
1516
+ fontSize: 14,
1517
+ fontWeight: '500',
1518
+ color: '#333',
1519
+ marginBottom: 2,
1520
+ },
1521
+ searchResultType: {
1522
+ fontSize: 12,
1523
+ color: '#666',
1524
+ textTransform: 'capitalize',
1525
+ },
1526
+ locationsList: {
1527
+ marginTop: 8,
1528
+ },
1529
+ locationsListTitle: {
1530
+ fontSize: 14,
1531
+ fontWeight: '600',
1532
+ color: '#333',
1533
+ marginBottom: 12,
1534
+ fontFamily: fontFamilies.phuduSemiBold,
1535
+ },
1536
+ locationItem: {
1537
+ marginBottom: 8,
1538
+ },
1539
+ locationItemContent: {
1540
+ flexDirection: 'row',
1541
+ alignItems: 'center',
1542
+ padding: 12,
1543
+ backgroundColor: '#F8F9FA',
1544
+ borderRadius: 8,
1545
+ },
1546
+ locationItemDragHandle: {
1547
+ marginRight: 12,
1548
+ },
1549
+ locationItemInfo: {
1550
+ flex: 1,
1551
+ },
1552
+ locationItemHeader: {
1553
+ flexDirection: 'row',
1554
+ alignItems: 'center',
1555
+ marginBottom: 4,
1556
+ },
1557
+ locationItemName: {
1558
+ fontSize: 14,
1559
+ fontWeight: '500',
1560
+ color: '#333',
1561
+ flex: 1,
1562
+ },
1563
+ locationLabel: {
1564
+ backgroundColor: '#007AFF',
1565
+ paddingHorizontal: 6,
1566
+ paddingVertical: 2,
1567
+ borderRadius: 4,
1568
+ marginLeft: 8,
1569
+ },
1570
+ locationLabelText: {
1571
+ fontSize: 10,
1572
+ fontWeight: '600',
1573
+ color: '#fff',
1574
+ textTransform: 'uppercase',
1575
+ },
1576
+ locationCoordinates: {
1577
+ fontSize: 12,
1578
+ color: '#666',
1579
+ fontFamily: 'monospace',
1580
+ },
1581
+ locationItemActions: {
1582
+ marginLeft: 8,
1583
+ },
1584
+ locationItemButton: {
1585
+ width: 28,
1586
+ height: 28,
1587
+ borderRadius: 14,
1588
+ backgroundColor: '#F8F9FA',
1589
+ alignItems: 'center',
1590
+ justifyContent: 'center',
1591
+ },
1592
+ locationItemDivider: {
1593
+ height: 1,
1594
+ backgroundColor: '#E9ECEF',
1595
+ marginHorizontal: 12,
834
1596
  },
835
1597
  });
836
1598