@oxyhq/services 5.7.5 → 5.8.2

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 (303) 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 +69 -28
  5. package/lib/commonjs/index.js.map +1 -1
  6. package/lib/commonjs/ui/components/Avatar.js +15 -6
  7. package/lib/commonjs/ui/components/Avatar.js.map +1 -1
  8. package/lib/commonjs/ui/components/FollowButton.js +100 -12
  9. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  10. package/lib/commonjs/ui/components/GroupedItem.js +58 -13
  11. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  12. package/lib/commonjs/ui/components/GroupedSection.js +7 -1
  13. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  14. package/lib/commonjs/ui/components/Header.js +356 -0
  15. package/lib/commonjs/ui/components/Header.js.map +1 -0
  16. package/lib/commonjs/ui/components/OxyProvider.js +28 -7
  17. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  18. package/lib/commonjs/ui/components/index.js +7 -0
  19. package/lib/commonjs/ui/components/index.js.map +1 -1
  20. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +1 -1
  21. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -1
  22. package/lib/commonjs/ui/components/internal/TextField.js +606 -546
  23. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  24. package/lib/commonjs/ui/components/internal/TextField.md +436 -0
  25. package/lib/commonjs/ui/context/OxyContext.js +181 -199
  26. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  27. package/lib/commonjs/ui/hooks/index.js +6 -0
  28. package/lib/commonjs/ui/hooks/index.js.map +1 -1
  29. package/lib/commonjs/ui/hooks/useFollow.js +59 -2
  30. package/lib/commonjs/ui/hooks/useFollow.js.map +1 -1
  31. package/lib/commonjs/ui/hooks/useSessionSocket.js +5 -2
  32. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
  33. package/lib/commonjs/ui/navigation/OxyRouter.js +11 -1
  34. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  35. package/lib/commonjs/ui/screens/AccountCenterScreen.js +6 -6
  36. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  37. package/lib/commonjs/ui/screens/AccountManagementDemo.js +3 -3
  38. package/lib/commonjs/ui/screens/AccountManagementDemo.js.map +1 -1
  39. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +241 -598
  40. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  41. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +1160 -406
  42. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  43. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +135 -237
  44. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  45. package/lib/commonjs/ui/screens/AppInfoScreen.js +246 -463
  46. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  47. package/lib/commonjs/ui/screens/FeedbackScreen.js +3 -3
  48. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
  49. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js +808 -650
  50. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
  51. package/lib/commonjs/ui/screens/ProfileScreen.js +214 -37
  52. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  53. package/lib/commonjs/ui/screens/RecoverAccountScreen.js +51 -72
  54. package/lib/commonjs/ui/screens/RecoverAccountScreen.js.map +1 -1
  55. package/lib/commonjs/ui/screens/SessionManagementScreen.js +11 -29
  56. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  57. package/lib/commonjs/ui/screens/SignInScreen.js +30 -303
  58. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  59. package/lib/commonjs/ui/screens/SignUpScreen.js +4 -4
  60. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  61. package/lib/commonjs/ui/screens/UserLinksScreen.js +90 -0
  62. package/lib/commonjs/ui/screens/UserLinksScreen.js.map +1 -0
  63. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +19 -31
  64. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  65. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +7 -10
  66. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  67. package/lib/commonjs/ui/screens/internal/SignUpIdentityStep.js +11 -5
  68. package/lib/commonjs/ui/screens/internal/SignUpIdentityStep.js.map +1 -1
  69. package/lib/commonjs/ui/screens/internal/SignUpSecurityStep.js +11 -4
  70. package/lib/commonjs/ui/screens/internal/SignUpSecurityStep.js.map +1 -1
  71. package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js +9 -6
  72. package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js.map +1 -1
  73. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +3 -30
  74. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  75. package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js +37 -46
  76. package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
  77. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +9 -12
  78. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
  79. package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js +9 -12
  80. package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
  81. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +9 -12
  82. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
  83. package/lib/commonjs/ui/stores/authStore.js +36 -6
  84. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  85. package/lib/commonjs/ui/stores/followStore.js +106 -1
  86. package/lib/commonjs/ui/stores/followStore.js.map +1 -1
  87. package/lib/commonjs/ui/styles/authStyles.js +337 -0
  88. package/lib/commonjs/ui/styles/authStyles.js.map +1 -0
  89. package/lib/commonjs/ui/styles/index.js +11 -0
  90. package/lib/commonjs/ui/styles/index.js.map +1 -1
  91. package/lib/module/core/index.js +177 -41
  92. package/lib/module/core/index.js.map +1 -1
  93. package/lib/module/index.js +24 -4
  94. package/lib/module/index.js.map +1 -1
  95. package/lib/module/ui/components/Avatar.js +15 -6
  96. package/lib/module/ui/components/Avatar.js.map +1 -1
  97. package/lib/module/ui/components/FollowButton.js +101 -13
  98. package/lib/module/ui/components/FollowButton.js.map +1 -1
  99. package/lib/module/ui/components/GroupedItem.js +59 -14
  100. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  101. package/lib/module/ui/components/GroupedSection.js +7 -1
  102. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  103. package/lib/module/ui/components/Header.js +351 -0
  104. package/lib/module/ui/components/Header.js.map +1 -0
  105. package/lib/module/ui/components/OxyProvider.js +30 -9
  106. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  107. package/lib/module/ui/components/index.js +1 -0
  108. package/lib/module/ui/components/index.js.map +1 -1
  109. package/lib/module/ui/components/internal/GroupedPillButtons.js +1 -1
  110. package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -1
  111. package/lib/module/ui/components/internal/TextField.js +607 -547
  112. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  113. package/lib/module/ui/components/internal/TextField.md +436 -0
  114. package/lib/module/ui/context/OxyContext.js +180 -198
  115. package/lib/module/ui/context/OxyContext.js.map +1 -1
  116. package/lib/module/ui/hooks/index.js +1 -1
  117. package/lib/module/ui/hooks/index.js.map +1 -1
  118. package/lib/module/ui/hooks/useFollow.js +57 -1
  119. package/lib/module/ui/hooks/useFollow.js.map +1 -1
  120. package/lib/module/ui/hooks/useSessionSocket.js +5 -2
  121. package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
  122. package/lib/module/ui/navigation/OxyRouter.js +11 -1
  123. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  124. package/lib/module/ui/screens/AccountCenterScreen.js +6 -6
  125. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  126. package/lib/module/ui/screens/AccountManagementDemo.js +3 -3
  127. package/lib/module/ui/screens/AccountManagementDemo.js.map +1 -1
  128. package/lib/module/ui/screens/AccountOverviewScreen.js +242 -597
  129. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  130. package/lib/module/ui/screens/AccountSettingsScreen.js +1161 -407
  131. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  132. package/lib/module/ui/screens/AccountSwitcherScreen.js +135 -237
  133. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  134. package/lib/module/ui/screens/AppInfoScreen.js +248 -465
  135. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  136. package/lib/module/ui/screens/FeedbackScreen.js +3 -3
  137. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
  138. package/lib/module/ui/screens/PaymentGatewayScreen.js +809 -651
  139. package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
  140. package/lib/module/ui/screens/ProfileScreen.js +214 -37
  141. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  142. package/lib/module/ui/screens/RecoverAccountScreen.js +53 -74
  143. package/lib/module/ui/screens/RecoverAccountScreen.js.map +1 -1
  144. package/lib/module/ui/screens/SessionManagementScreen.js +11 -29
  145. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  146. package/lib/module/ui/screens/SignInScreen.js +32 -305
  147. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  148. package/lib/module/ui/screens/SignUpScreen.js +5 -5
  149. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  150. package/lib/module/ui/screens/UserLinksScreen.js +85 -0
  151. package/lib/module/ui/screens/UserLinksScreen.js.map +1 -0
  152. package/lib/module/ui/screens/internal/SignInPasswordStep.js +19 -31
  153. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  154. package/lib/module/ui/screens/internal/SignInUsernameStep.js +7 -10
  155. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  156. package/lib/module/ui/screens/internal/SignUpIdentityStep.js +11 -5
  157. package/lib/module/ui/screens/internal/SignUpIdentityStep.js.map +1 -1
  158. package/lib/module/ui/screens/internal/SignUpSecurityStep.js +11 -4
  159. package/lib/module/ui/screens/internal/SignUpSecurityStep.js.map +1 -1
  160. package/lib/module/ui/screens/karma/KarmaAboutScreen.js +9 -6
  161. package/lib/module/ui/screens/karma/KarmaAboutScreen.js.map +1 -1
  162. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +3 -30
  163. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  164. package/lib/module/ui/screens/karma/KarmaFAQScreen.js +37 -46
  165. package/lib/module/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
  166. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +9 -12
  167. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
  168. package/lib/module/ui/screens/karma/KarmaRewardsScreen.js +9 -12
  169. package/lib/module/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
  170. package/lib/module/ui/screens/karma/KarmaRulesScreen.js +9 -12
  171. package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
  172. package/lib/module/ui/stores/authStore.js +36 -6
  173. package/lib/module/ui/stores/authStore.js.map +1 -1
  174. package/lib/module/ui/stores/followStore.js +106 -1
  175. package/lib/module/ui/stores/followStore.js.map +1 -1
  176. package/lib/module/ui/styles/authStyles.js +332 -0
  177. package/lib/module/ui/styles/authStyles.js.map +1 -0
  178. package/lib/module/ui/styles/index.js +1 -0
  179. package/lib/module/ui/styles/index.js.map +1 -1
  180. package/lib/typescript/core/index.d.ts +68 -24
  181. package/lib/typescript/core/index.d.ts.map +1 -1
  182. package/lib/typescript/index.d.ts +13 -3
  183. package/lib/typescript/index.d.ts.map +1 -1
  184. package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
  185. package/lib/typescript/ui/components/FollowButton.d.ts +1 -0
  186. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  187. package/lib/typescript/ui/components/GroupedItem.d.ts +6 -0
  188. package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
  189. package/lib/typescript/ui/components/GroupedSection.d.ts +6 -0
  190. package/lib/typescript/ui/components/GroupedSection.d.ts.map +1 -1
  191. package/lib/typescript/ui/components/Header.d.ts +24 -0
  192. package/lib/typescript/ui/components/Header.d.ts.map +1 -0
  193. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  194. package/lib/typescript/ui/components/index.d.ts +1 -0
  195. package/lib/typescript/ui/components/index.d.ts.map +1 -1
  196. package/lib/typescript/ui/components/internal/TextField.d.ts +31 -16
  197. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  198. package/lib/typescript/ui/context/OxyContext.d.ts +5 -2
  199. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  200. package/lib/typescript/ui/hooks/index.d.ts +1 -1
  201. package/lib/typescript/ui/hooks/index.d.ts.map +1 -1
  202. package/lib/typescript/ui/hooks/useFollow.d.ts +20 -0
  203. package/lib/typescript/ui/hooks/useFollow.d.ts.map +1 -1
  204. package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
  205. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  206. package/lib/typescript/ui/navigation/types.d.ts +9 -2
  207. package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
  208. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  209. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  210. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  211. package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
  212. package/lib/typescript/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
  213. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
  214. package/lib/typescript/ui/screens/RecoverAccountScreen.d.ts +5 -1
  215. package/lib/typescript/ui/screens/RecoverAccountScreen.d.ts.map +1 -1
  216. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  217. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  218. package/lib/typescript/ui/screens/UserLinksScreen.d.ts +15 -0
  219. package/lib/typescript/ui/screens/UserLinksScreen.d.ts.map +1 -0
  220. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +1 -1
  221. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  222. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +0 -1
  223. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
  224. package/lib/typescript/ui/screens/internal/SignUpIdentityStep.d.ts.map +1 -1
  225. package/lib/typescript/ui/screens/internal/SignUpSecurityStep.d.ts.map +1 -1
  226. package/lib/typescript/ui/screens/karma/KarmaAboutScreen.d.ts.map +1 -1
  227. package/lib/typescript/ui/screens/karma/KarmaCenterScreen.d.ts.map +1 -1
  228. package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts.map +1 -1
  229. package/lib/typescript/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +1 -1
  230. package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts.map +1 -1
  231. package/lib/typescript/ui/screens/karma/KarmaRulesScreen.d.ts.map +1 -1
  232. package/lib/typescript/ui/stores/authStore.d.ts +3 -1
  233. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  234. package/lib/typescript/ui/stores/followStore.d.ts +10 -0
  235. package/lib/typescript/ui/stores/followStore.d.ts.map +1 -1
  236. package/lib/typescript/ui/styles/authStyles.d.ts +326 -0
  237. package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -0
  238. package/lib/typescript/ui/styles/index.d.ts +1 -0
  239. package/lib/typescript/ui/styles/index.d.ts.map +1 -1
  240. package/package.json +1 -4
  241. package/src/core/index.ts +195 -41
  242. package/src/index.ts +64 -4
  243. package/src/ui/components/Avatar.tsx +11 -5
  244. package/src/ui/components/FollowButton.tsx +95 -11
  245. package/src/ui/components/GroupedItem.tsx +57 -9
  246. package/src/ui/components/GroupedSection.tsx +12 -0
  247. package/src/ui/components/Header.tsx +405 -0
  248. package/src/ui/components/OxyProvider.tsx +37 -15
  249. package/src/ui/components/index.ts +1 -0
  250. package/src/ui/components/internal/GroupedPillButtons.tsx +1 -1
  251. package/src/ui/components/internal/TextField.md +436 -0
  252. package/src/ui/components/internal/TextField.tsx +720 -620
  253. package/src/ui/context/OxyContext.tsx +211 -195
  254. package/src/ui/hooks/index.ts +1 -1
  255. package/src/ui/hooks/useFollow.ts +63 -0
  256. package/src/ui/hooks/useSessionSocket.ts +5 -2
  257. package/src/ui/navigation/OxyRouter.tsx +11 -1
  258. package/src/ui/navigation/types.ts +10 -2
  259. package/src/ui/screens/AccountCenterScreen.tsx +5 -5
  260. package/src/ui/screens/AccountManagementDemo.tsx +9 -9
  261. package/src/ui/screens/AccountOverviewScreen.tsx +265 -414
  262. package/src/ui/screens/AccountSettingsScreen.tsx +1173 -403
  263. package/src/ui/screens/AccountSwitcherScreen.tsx +158 -202
  264. package/src/ui/screens/AppInfoScreen.tsx +270 -497
  265. package/src/ui/screens/FeedbackScreen.tsx +3 -3
  266. package/src/ui/screens/PaymentGatewayScreen.tsx +668 -365
  267. package/src/ui/screens/ProfileScreen.tsx +196 -33
  268. package/src/ui/screens/RecoverAccountScreen.tsx +46 -74
  269. package/src/ui/screens/SessionManagementScreen.tsx +14 -22
  270. package/src/ui/screens/SignInScreen.tsx +27 -294
  271. package/src/ui/screens/SignUpScreen.tsx +5 -5
  272. package/src/ui/screens/UserLinksScreen.tsx +96 -0
  273. package/src/ui/screens/internal/SignInPasswordStep.tsx +11 -22
  274. package/src/ui/screens/internal/SignInUsernameStep.tsx +3 -10
  275. package/src/ui/screens/internal/SignUpIdentityStep.tsx +2 -5
  276. package/src/ui/screens/internal/SignUpSecurityStep.tsx +3 -4
  277. package/src/ui/screens/karma/KarmaAboutScreen.tsx +9 -2
  278. package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -20
  279. package/src/ui/screens/karma/KarmaFAQScreen.tsx +40 -24
  280. package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +9 -3
  281. package/src/ui/screens/karma/KarmaRewardsScreen.tsx +9 -3
  282. package/src/ui/screens/karma/KarmaRulesScreen.tsx +9 -3
  283. package/src/ui/stores/authStore.ts +34 -7
  284. package/src/ui/stores/followStore.ts +102 -1
  285. package/src/ui/styles/authStyles.ts +352 -0
  286. package/src/ui/styles/index.ts +1 -0
  287. package/lib/commonjs/core/auth-manager.js +0 -440
  288. package/lib/commonjs/core/auth-manager.js.map +0 -1
  289. package/lib/commonjs/core/use-auth.js +0 -244
  290. package/lib/commonjs/core/use-auth.js.map +0 -1
  291. package/lib/module/core/auth-manager.js +0 -432
  292. package/lib/module/core/auth-manager.js.map +0 -1
  293. package/lib/module/core/use-auth.js +0 -235
  294. package/lib/module/core/use-auth.js.map +0 -1
  295. package/lib/typescript/core/auth-manager.d.ts +0 -136
  296. package/lib/typescript/core/auth-manager.d.ts.map +0 -1
  297. package/lib/typescript/core/use-auth.d.ts +0 -79
  298. package/lib/typescript/core/use-auth.d.ts.map +0 -1
  299. package/src/__tests__/middleware.test.ts +0 -105
  300. package/src/__tests__/setup.ts +0 -10
  301. package/src/__tests__/zero-config-auth.test.ts +0 -607
  302. package/src/core/auth-manager.ts +0 -500
  303. 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,180 @@ 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: 'preview-profile',
1024
+ icon: 'eye',
1025
+ iconColor: '#007AFF',
1026
+ title: 'Preview Profile',
1027
+ subtitle: 'See how your profile looks to others',
1028
+ onPress: () => navigate?.('Profile', { userId: user?.id }),
1029
+ },
1030
+ {
1031
+ id: 'privacy-settings',
1032
+ icon: 'shield-checkmark',
1033
+ iconColor: '#8E8E93',
1034
+ title: 'Privacy Settings',
1035
+ subtitle: 'Control who can see your profile',
1036
+ onPress: () => toast.info('Privacy settings coming soon!'),
1037
+ },
1038
+ {
1039
+ id: 'verify-account',
1040
+ icon: 'checkmark-circle',
1041
+ iconColor: '#30D158',
1042
+ title: 'Verify Account',
1043
+ subtitle: 'Get a verified badge',
1044
+ onPress: () => toast.info('Account verification coming soon!'),
1045
+ },
1046
+ ]}
1047
+ theme={theme}
1048
+ />
605
1049
  </View>
606
1050
  </>
607
1051
  )}
@@ -615,203 +1059,418 @@ const styles = StyleSheet.create({
615
1059
  flex: 1,
616
1060
  backgroundColor: '#f2f2f2',
617
1061
  },
618
- header: {
619
- paddingHorizontal: 20,
620
- paddingVertical: 10,
1062
+ content: {
1063
+ flex: 1,
1064
+ padding: 16,
1065
+ },
1066
+ contentEditing: {
1067
+ flex: 1,
1068
+ padding: 0,
1069
+ },
1070
+ section: {
1071
+ marginBottom: 24,
1072
+ },
1073
+ sectionTitle: {
1074
+ fontSize: 16,
1075
+ fontWeight: '600',
1076
+ color: '#333',
1077
+ marginBottom: 12,
1078
+ fontFamily: fontFamilies.phuduSemiBold,
1079
+ },
1080
+
1081
+ userIcon: {
1082
+ marginRight: 12,
1083
+ },
1084
+
1085
+ // Editing-only mode styles
1086
+ editingOnlyContainer: {
1087
+ flex: 1,
1088
+ },
1089
+ editingFieldContainer: {
621
1090
  backgroundColor: '#fff',
622
- borderBottomWidth: 1,
623
- borderBottomColor: '#e0e0e0',
1091
+ padding: 16,
1092
+ flex: 1,
624
1093
  },
625
- normalHeader: {
1094
+ editingFieldHeader: {
1095
+ marginBottom: 8,
626
1096
  flexDirection: 'row',
627
- justifyContent: 'space-between',
628
1097
  alignItems: 'center',
629
1098
  },
630
- editingHeader: {
631
- flexDirection: 'column',
632
- },
633
- editingHeaderTop: {
1099
+ editingFieldTitleContainer: {
634
1100
  flexDirection: 'row',
635
- justifyContent: 'space-between',
636
1101
  alignItems: 'center',
637
- marginBottom: 16,
638
1102
  },
639
- editingHeaderBottom: {
1103
+ editingFieldIcon: {
1104
+ marginRight: 12,
1105
+ },
1106
+ editingFieldTitle: {
1107
+ fontSize: 20,
1108
+ fontWeight: '600',
1109
+ color: '#000',
1110
+ },
1111
+ editingFieldContent: {
1112
+ flex: 1,
1113
+ },
1114
+ newValueSection: {
1115
+ flex: 1,
1116
+ },
1117
+ editingFieldLabel: {
1118
+ fontSize: 16,
1119
+ fontWeight: '600',
1120
+ color: '#333',
1121
+ marginBottom: 12,
1122
+ fontFamily: fontFamilies.phuduSemiBold,
1123
+ },
1124
+ editingFieldInput: {
1125
+ backgroundColor: '#fff',
1126
+ borderWidth: 2,
1127
+ borderColor: '#e0e0e0',
1128
+ borderRadius: 12,
1129
+ padding: 16,
1130
+ fontSize: 17,
1131
+ minHeight: 52,
1132
+ fontWeight: '400',
1133
+ },
1134
+ editingFieldTextArea: {
1135
+ backgroundColor: '#fff',
1136
+ borderWidth: 2,
1137
+ borderColor: '#e0e0e0',
1138
+ borderRadius: 12,
1139
+ padding: 16,
1140
+ fontSize: 17,
1141
+ minHeight: 120,
1142
+ textAlignVertical: 'top',
1143
+ fontWeight: '400',
1144
+ },
1145
+ // Custom editing header styles
1146
+ editingHeader: {
1147
+ paddingTop: Platform.OS === 'ios' ? 50 : 16,
1148
+ paddingBottom: 0,
1149
+ borderBottomWidth: 1,
1150
+ backgroundColor: '#fff',
1151
+ },
1152
+ editingHeaderContent: {
640
1153
  flexDirection: 'row',
641
1154
  alignItems: 'center',
642
- justifyContent: 'flex-start',
1155
+ paddingHorizontal: 16,
1156
+ minHeight: 44,
643
1157
  },
644
- headerTitle: {
645
- fontSize: 24,
646
- fontWeight: 'bold',
647
- color: '#000',
648
- fontFamily: fontFamilies.phuduBold,
1158
+ editingBackButton: {
1159
+ width: 32,
1160
+ height: 32,
1161
+ borderRadius: 16,
1162
+ backgroundColor: '#F8F9FA',
1163
+ alignItems: 'center',
1164
+ justifyContent: 'center',
1165
+ marginRight: 12,
649
1166
  },
650
- headerTitleWithIcon: {
1167
+ editingTitleContainer: {
1168
+ flex: 1,
651
1169
  flexDirection: 'column',
652
1170
  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',
1171
+ justifyContent: 'flex-end',
1172
+ paddingBottom: 8,
663
1173
  },
664
- headerIcon: {
665
- marginBottom: 2,
1174
+ editingTitleIcon: {
1175
+ marginBottom: 4,
1176
+ alignSelf: 'flex-start',
666
1177
  },
667
- cancelButton: {
668
- padding: 5,
1178
+ editingTitle: {
1179
+ fontSize: 18,
1180
+ fontWeight: '700',
1181
+ fontFamily: fontFamilies.phuduBold,
1182
+ letterSpacing: -0.3,
1183
+ lineHeight: 22,
1184
+ textAlign: 'left',
1185
+ alignSelf: 'flex-start',
669
1186
  },
670
- saveHeaderButton: {
1187
+ editingSaveButton: {
671
1188
  paddingHorizontal: 16,
672
1189
  paddingVertical: 8,
673
- borderRadius: 20,
1190
+ borderRadius: 18,
1191
+ backgroundColor: '#F8F9FA',
674
1192
  minWidth: 60,
675
1193
  alignItems: 'center',
676
1194
  justifyContent: 'center',
677
1195
  },
678
- saveIconButton: {
679
- padding: 5,
680
- },
681
- saveButtonText: {
682
- color: '#fff',
1196
+ editingSaveButtonText: {
683
1197
  fontSize: 16,
684
1198
  fontWeight: '600',
685
1199
  fontFamily: fontFamilies.phuduSemiBold,
686
1200
  },
687
- content: {
688
- flex: 1,
689
- padding: 16,
1201
+ editingHeaderBottom: {
1202
+ flexDirection: 'column',
1203
+ alignItems: 'flex-start',
1204
+ paddingHorizontal: 16,
1205
+ paddingBottom: 8,
1206
+ paddingTop: 8,
690
1207
  },
691
- contentEditing: {
692
- flex: 1,
693
- padding: 0,
1208
+ editingBottomIcon: {
1209
+ marginBottom: 8,
1210
+ alignSelf: 'flex-start',
694
1211
  },
695
- section: {
696
- marginBottom: 24,
1212
+ editingBottomTitle: {
1213
+ fontSize: 32,
1214
+ fontWeight: '700',
1215
+ fontFamily: fontFamilies.phuduBold,
1216
+ letterSpacing: -0.5,
1217
+ lineHeight: 36,
1218
+ textAlign: 'left',
1219
+ alignSelf: 'flex-start',
697
1220
  },
698
- sectionTitle: {
699
- fontSize: 16,
1221
+ // Links management styles
1222
+ addLinkSection: {
1223
+ marginBottom: 16,
1224
+ padding: 12,
1225
+ backgroundColor: '#F8F9FA',
1226
+ borderRadius: 8,
1227
+ borderWidth: 1,
1228
+ borderColor: '#E9ECEF',
1229
+ },
1230
+ addLinkLabel: {
1231
+ fontSize: 14,
700
1232
  fontWeight: '600',
701
1233
  color: '#333',
702
- marginBottom: 12,
703
- fontFamily: fontFamilies.phuduSemiBold,
1234
+ marginBottom: 8,
704
1235
  },
705
- settingItem: {
1236
+ addLinkInputContainer: {
1237
+ gap: 8,
1238
+ },
1239
+ addLinkInput: {
706
1240
  backgroundColor: '#fff',
707
- padding: 16,
1241
+ borderWidth: 1,
1242
+ borderColor: '#E9ECEF',
1243
+ borderRadius: 6,
1244
+ padding: 10,
1245
+ fontSize: 14,
1246
+ minHeight: 36,
1247
+ },
1248
+ addLinkButtons: {
708
1249
  flexDirection: 'row',
1250
+ gap: 6,
1251
+ },
1252
+ addLinkButton: {
1253
+ flex: 1,
1254
+ paddingVertical: 8,
1255
+ paddingHorizontal: 12,
1256
+ borderRadius: 6,
709
1257
  alignItems: 'center',
710
- justifyContent: 'space-between',
711
- marginBottom: 2,
1258
+ justifyContent: 'center',
1259
+ },
1260
+ cancelButton: {
1261
+ backgroundColor: '#F8F9FA',
1262
+ borderWidth: 1,
1263
+ borderColor: '#E9ECEF',
712
1264
  },
713
- firstSettingItem: {
714
- borderTopLeftRadius: 24,
715
- borderTopRightRadius: 24,
1265
+ cancelButtonText: {
1266
+ fontSize: 14,
1267
+ fontWeight: '600',
1268
+ color: '#6C757D',
716
1269
  },
717
- lastSettingItem: {
718
- borderBottomLeftRadius: 24,
719
- borderBottomRightRadius: 24,
720
- marginBottom: 8,
1270
+ addButton: {
1271
+ backgroundColor: '#007AFF',
1272
+ },
1273
+ addButtonText: {
1274
+ fontSize: 14,
1275
+ fontWeight: '600',
1276
+ color: '#fff',
721
1277
  },
722
- settingInfo: {
1278
+ addLinkTrigger: {
723
1279
  flexDirection: 'row',
724
1280
  alignItems: 'center',
725
- flex: 1,
1281
+ padding: 12,
1282
+ backgroundColor: '#F8F9FA',
1283
+ borderRadius: 8,
1284
+ borderWidth: 1,
1285
+ borderColor: '#E9ECEF',
1286
+ borderStyle: 'dashed',
1287
+ marginBottom: 16,
726
1288
  },
727
- settingIcon: {
728
- marginRight: 12,
1289
+ addLinkTriggerText: {
1290
+ fontSize: 14,
1291
+ fontWeight: '600',
1292
+ color: '#007AFF',
1293
+ marginLeft: 6,
729
1294
  },
730
- settingLabel: {
1295
+ linksList: {
1296
+ gap: 8,
1297
+ },
1298
+ linksListTitle: {
731
1299
  fontSize: 16,
732
- fontWeight: '500',
1300
+ fontWeight: '700',
733
1301
  color: '#333',
734
- marginBottom: 2,
1302
+ marginBottom: 6,
735
1303
  },
736
- settingDescription: {
737
- fontSize: 14,
738
- color: '#666',
1304
+ linkItem: {
1305
+ backgroundColor: '#fff',
1306
+ borderRadius: 8,
1307
+ borderWidth: 1,
1308
+ borderColor: '#E9ECEF',
1309
+ overflow: 'hidden',
739
1310
  },
740
- userIcon: {
741
- marginRight: 12,
1311
+ linkItemContent: {
1312
+ flexDirection: 'row',
1313
+ padding: 12,
1314
+ alignItems: 'center',
1315
+ },
1316
+ linkItemDragHandle: {
1317
+ width: 24,
1318
+ height: 24,
1319
+ alignItems: 'center',
1320
+ justifyContent: 'center',
1321
+ marginRight: 8,
742
1322
  },
743
- // Inline editing styles
744
- editingContainer: {
1323
+ linkItemInfo: {
745
1324
  flex: 1,
1325
+ marginRight: 8,
1326
+ },
1327
+ linkItemTitle: {
1328
+ fontSize: 14,
1329
+ fontWeight: '600',
1330
+ color: '#333',
1331
+ marginBottom: 2,
746
1332
  },
747
- editingActions: {
1333
+ linkItemDescription: {
1334
+ fontSize: 12,
1335
+ color: '#666',
1336
+ marginBottom: 2,
1337
+ },
1338
+ linkItemUrl: {
1339
+ fontSize: 12,
1340
+ color: '#6C757D',
1341
+ },
1342
+ linkItemActions: {
748
1343
  flexDirection: 'row',
1344
+ gap: 6,
1345
+ },
1346
+ linkItemButton: {
1347
+ width: 28,
1348
+ height: 28,
1349
+ borderRadius: 14,
1350
+ backgroundColor: '#F8F9FA',
749
1351
  alignItems: 'center',
1352
+ justifyContent: 'center',
1353
+ },
1354
+ linkItemDivider: {
1355
+ height: 1,
1356
+ backgroundColor: '#E9ECEF',
1357
+ marginHorizontal: 12,
750
1358
  },
751
- editingButton: {
1359
+ reorderHint: {
752
1360
  padding: 8,
1361
+ alignItems: 'center',
753
1362
  },
754
- editingButtonText: {
755
- fontSize: 16,
756
- fontWeight: '500',
1363
+ reorderHintText: {
1364
+ fontSize: 12,
1365
+ color: '#999',
1366
+ fontStyle: 'italic',
757
1367
  },
758
- inlineInput: {
759
- backgroundColor: '#f8f8f8',
760
- borderWidth: 1,
761
- borderColor: '#e0e0e0',
762
- borderRadius: 8,
763
- padding: 12,
764
- fontSize: 16,
765
- minHeight: 44,
1368
+ reorderButtons: {
1369
+ flexDirection: 'column',
1370
+ gap: 2,
766
1371
  },
767
- inlineTextArea: {
768
- backgroundColor: '#f8f8f8',
1372
+ reorderButton: {
1373
+ width: 20,
1374
+ height: 16,
1375
+ borderRadius: 3,
1376
+ backgroundColor: '#F8F9FA',
1377
+ alignItems: 'center',
1378
+ justifyContent: 'center',
769
1379
  borderWidth: 1,
770
- borderColor: '#e0e0e0',
771
- borderRadius: 8,
772
- padding: 12,
773
- fontSize: 16,
774
- minHeight: 100,
775
- textAlignVertical: 'top',
1380
+ borderColor: '#E9ECEF',
776
1381
  },
777
- // Editing-only mode styles
778
- editingOnlyContainer: {
779
- flex: 1,
1382
+ reorderButtonDisabled: {
1383
+ opacity: 0.3,
780
1384
  },
781
- editingFieldContainer: {
782
- backgroundColor: '#fff',
783
- padding: 16,
1385
+ linkItemImage: {
1386
+ width: 32,
1387
+ height: 32,
1388
+ borderRadius: 16,
1389
+ backgroundColor: '#007AFF',
1390
+ alignItems: 'center',
1391
+ justifyContent: 'center',
1392
+ marginRight: 8,
1393
+ },
1394
+ linkItemImageText: {
1395
+ fontSize: 12,
1396
+ fontWeight: '600',
1397
+ color: '#fff',
1398
+ },
1399
+ fetchingText: {
1400
+ fontSize: 12,
1401
+ color: '#007AFF',
1402
+ fontStyle: 'italic',
1403
+ },
1404
+ linksFieldContent: {
784
1405
  flex: 1,
1406
+ marginLeft: 12,
785
1407
  },
786
- editingFieldHeader: {
787
- marginBottom: 16,
1408
+ linksPreview: {
1409
+ marginTop: 4,
1410
+ flexDirection: 'column',
788
1411
  },
789
- editingFieldTitleContainer: {
1412
+ linksPreviewContainer: {
1413
+ marginTop: 4,
1414
+ flexDirection: 'column',
1415
+ width: '100%',
1416
+ },
1417
+ linkPreviewItem: {
790
1418
  flexDirection: 'row',
791
1419
  alignItems: 'center',
1420
+ marginBottom: 4,
792
1421
  },
793
- editingFieldIcon: {
794
- marginRight: 12,
1422
+ linkPreviewImage: {
1423
+ width: 20,
1424
+ height: 20,
1425
+ borderRadius: 10,
1426
+ backgroundColor: '#007AFF',
1427
+ alignItems: 'center',
1428
+ justifyContent: 'center',
1429
+ marginRight: 6,
795
1430
  },
796
- editingFieldTitle: {
797
- fontSize: 20,
1431
+ linkPreviewImageText: {
1432
+ fontSize: 10,
798
1433
  fontWeight: '600',
799
- color: '#000',
1434
+ color: '#fff',
800
1435
  },
801
- editingFieldContent: {
1436
+ linkPreviewTitle: {
1437
+ fontSize: 13,
1438
+ color: '#666',
802
1439
  flex: 1,
803
1440
  },
804
- newValueSection: {
1441
+ linkPreviewContent: {
805
1442
  flex: 1,
806
1443
  },
807
- editingFieldLabel: {
808
- fontSize: 16,
1444
+ linkPreviewSubtitle: {
1445
+ fontSize: 11,
1446
+ color: '#999',
1447
+ marginTop: 1,
1448
+ },
1449
+ linkPreviewMore: {
1450
+ fontSize: 12,
1451
+ color: '#999',
1452
+ fontStyle: 'italic',
1453
+ },
1454
+ // Location management styles
1455
+ addLocationSection: {
1456
+ marginBottom: 16,
1457
+ },
1458
+ addLocationLabel: {
1459
+ fontSize: 14,
809
1460
  fontWeight: '600',
810
1461
  color: '#333',
811
- marginBottom: 12,
1462
+ marginBottom: 8,
812
1463
  fontFamily: fontFamilies.phuduSemiBold,
813
1464
  },
814
- editingFieldInput: {
1465
+ searchingText: {
1466
+ fontSize: 12,
1467
+ color: '#007AFF',
1468
+ fontStyle: 'italic',
1469
+ },
1470
+ addLocationInputContainer: {
1471
+ marginBottom: 8,
1472
+ },
1473
+ addLocationInput: {
815
1474
  backgroundColor: '#fff',
816
1475
  borderWidth: 2,
817
1476
  borderColor: '#e0e0e0',
@@ -820,17 +1479,128 @@ const styles = StyleSheet.create({
820
1479
  fontSize: 17,
821
1480
  minHeight: 52,
822
1481
  fontWeight: '400',
1482
+ marginBottom: 8,
823
1483
  },
824
- editingFieldTextArea: {
1484
+ addLocationButtons: {
1485
+ flexDirection: 'row',
1486
+ gap: 8,
1487
+ },
1488
+ addLocationButton: {
1489
+ flex: 1,
1490
+ paddingVertical: 12,
1491
+ paddingHorizontal: 16,
1492
+ borderRadius: 8,
1493
+ alignItems: 'center',
1494
+ justifyContent: 'center',
1495
+ },
1496
+ addLocationTrigger: {
1497
+ flexDirection: 'row',
1498
+ alignItems: 'center',
1499
+ paddingVertical: 12,
1500
+ paddingHorizontal: 16,
1501
+ backgroundColor: '#F8F9FA',
1502
+ borderRadius: 8,
1503
+ marginBottom: 16,
1504
+ },
1505
+ addLocationTriggerText: {
1506
+ marginLeft: 8,
1507
+ fontSize: 16,
1508
+ color: '#007AFF',
1509
+ fontWeight: '500',
1510
+ },
1511
+ searchResults: {
825
1512
  backgroundColor: '#fff',
826
- borderWidth: 2,
1513
+ borderWidth: 1,
827
1514
  borderColor: '#e0e0e0',
828
- borderRadius: 12,
829
- padding: 16,
830
- fontSize: 17,
831
- minHeight: 120,
832
- textAlignVertical: 'top',
833
- fontWeight: '400',
1515
+ borderRadius: 8,
1516
+ maxHeight: 200,
1517
+ },
1518
+ searchResultItem: {
1519
+ padding: 12,
1520
+ borderBottomWidth: 1,
1521
+ borderBottomColor: '#f0f0f0',
1522
+ },
1523
+ searchResultName: {
1524
+ fontSize: 14,
1525
+ fontWeight: '500',
1526
+ color: '#333',
1527
+ marginBottom: 2,
1528
+ },
1529
+ searchResultType: {
1530
+ fontSize: 12,
1531
+ color: '#666',
1532
+ textTransform: 'capitalize',
1533
+ },
1534
+ locationsList: {
1535
+ marginTop: 8,
1536
+ },
1537
+ locationsListTitle: {
1538
+ fontSize: 14,
1539
+ fontWeight: '600',
1540
+ color: '#333',
1541
+ marginBottom: 12,
1542
+ fontFamily: fontFamilies.phuduSemiBold,
1543
+ },
1544
+ locationItem: {
1545
+ marginBottom: 8,
1546
+ },
1547
+ locationItemContent: {
1548
+ flexDirection: 'row',
1549
+ alignItems: 'center',
1550
+ padding: 12,
1551
+ backgroundColor: '#F8F9FA',
1552
+ borderRadius: 8,
1553
+ },
1554
+ locationItemDragHandle: {
1555
+ marginRight: 12,
1556
+ },
1557
+ locationItemInfo: {
1558
+ flex: 1,
1559
+ },
1560
+ locationItemHeader: {
1561
+ flexDirection: 'row',
1562
+ alignItems: 'center',
1563
+ marginBottom: 4,
1564
+ },
1565
+ locationItemName: {
1566
+ fontSize: 14,
1567
+ fontWeight: '500',
1568
+ color: '#333',
1569
+ flex: 1,
1570
+ },
1571
+ locationLabel: {
1572
+ backgroundColor: '#007AFF',
1573
+ paddingHorizontal: 6,
1574
+ paddingVertical: 2,
1575
+ borderRadius: 4,
1576
+ marginLeft: 8,
1577
+ },
1578
+ locationLabelText: {
1579
+ fontSize: 10,
1580
+ fontWeight: '600',
1581
+ color: '#fff',
1582
+ textTransform: 'uppercase',
1583
+ },
1584
+ locationCoordinates: {
1585
+ fontSize: 12,
1586
+ color: '#666',
1587
+ fontFamily: 'monospace',
1588
+ },
1589
+ locationItemActions: {
1590
+ marginLeft: 8,
1591
+ },
1592
+ locationItemButton: {
1593
+ width: 28,
1594
+ height: 28,
1595
+ borderRadius: 14,
1596
+ backgroundColor: '#F8F9FA',
1597
+ alignItems: 'center',
1598
+ justifyContent: 'center',
1599
+ },
1600
+ locationItemDivider: {
1601
+ height: 1,
1602
+ backgroundColor: '#E9ECEF',
1603
+ marginHorizontal: 12,
834
1604
  },
835
1605
  });
836
1606