@oxyhq/services 22.13.0 → 23.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (445) hide show
  1. package/README.md +1 -1
  2. package/lib/commonjs/index.js +74 -0
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/notifications/deviceNotifications.js +424 -0
  5. package/lib/commonjs/notifications/deviceNotifications.js.map +1 -0
  6. package/lib/commonjs/ui/boot/runProviderColdBoot.js +20 -64
  7. package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
  8. package/lib/commonjs/ui/components/AvatarCameraBadge.js +3 -2
  9. package/lib/commonjs/ui/components/AvatarCameraBadge.js.map +1 -1
  10. package/lib/commonjs/ui/components/OxyAuthChooser.js +5 -4
  11. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
  12. package/lib/commonjs/ui/components/OxyProvider.js +5 -6
  13. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  14. package/lib/commonjs/ui/components/OxySignInRequestSurface.js +147 -0
  15. package/lib/commonjs/ui/components/OxySignInRequestSurface.js.map +1 -0
  16. package/lib/commonjs/ui/components/ProfileButton.js +4 -3
  17. package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
  18. package/lib/commonjs/ui/components/SettingsIcon.js +3 -2
  19. package/lib/commonjs/ui/components/SettingsIcon.js.map +1 -1
  20. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js +7 -7
  21. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  22. package/lib/commonjs/ui/components/authChooser/SignInEntryView.js.map +1 -1
  23. package/lib/commonjs/ui/components/authChooser/SignInRequestView.js +65 -103
  24. package/lib/commonjs/ui/components/authChooser/SignInRequestView.js.map +1 -1
  25. package/lib/commonjs/ui/components/authChooser/TroubleDisclosure.js +0 -2
  26. package/lib/commonjs/ui/components/authChooser/TroubleDisclosure.js.map +1 -1
  27. package/lib/commonjs/ui/components/authChooser/primitives.js +5 -4
  28. package/lib/commonjs/ui/components/authChooser/primitives.js.map +1 -1
  29. package/lib/commonjs/ui/components/authChooser/requestSurfaces.js +14 -10
  30. package/lib/commonjs/ui/components/authChooser/requestSurfaces.js.map +1 -1
  31. package/lib/commonjs/ui/components/authChooser/types.js +11 -0
  32. package/lib/commonjs/ui/components/authChooser/types.js.map +1 -1
  33. package/lib/commonjs/ui/components/fileManagement/AnimatedButton.js +3 -2
  34. package/lib/commonjs/ui/components/fileManagement/AnimatedButton.js.map +1 -1
  35. package/lib/commonjs/ui/components/fileManagement/FileDetailsModal.js +3 -2
  36. package/lib/commonjs/ui/components/fileManagement/FileDetailsModal.js.map +1 -1
  37. package/lib/commonjs/ui/components/fileManagement/FileViewer.js +15 -14
  38. package/lib/commonjs/ui/components/fileManagement/FileViewer.js.map +1 -1
  39. package/lib/commonjs/ui/components/fileManagement/UploadPreview.js +6 -5
  40. package/lib/commonjs/ui/components/fileManagement/UploadPreview.js.map +1 -1
  41. package/lib/commonjs/ui/components/modals/DeleteAccountModal.js +3 -2
  42. package/lib/commonjs/ui/components/modals/DeleteAccountModal.js.map +1 -1
  43. package/lib/commonjs/ui/components/payment/PaymentDetailsStep.js +6 -6
  44. package/lib/commonjs/ui/components/payment/PaymentDetailsStep.js.map +1 -1
  45. package/lib/commonjs/ui/components/payment/PaymentMethodStep.js +5 -5
  46. package/lib/commonjs/ui/components/payment/PaymentMethodStep.js.map +1 -1
  47. package/lib/commonjs/ui/components/payment/PaymentReviewStep.js +10 -9
  48. package/lib/commonjs/ui/components/payment/PaymentReviewStep.js.map +1 -1
  49. package/lib/commonjs/ui/components/payment/PaymentSuccessStep.js +4 -3
  50. package/lib/commonjs/ui/components/payment/PaymentSuccessStep.js.map +1 -1
  51. package/lib/commonjs/ui/components/payment/PaymentSummaryStep.js +6 -5
  52. package/lib/commonjs/ui/components/payment/PaymentSummaryStep.js.map +1 -1
  53. package/lib/commonjs/ui/components/styles/shadow.js.map +1 -1
  54. package/lib/commonjs/ui/context/OxyContext.js +14 -45
  55. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  56. package/lib/commonjs/ui/context/commitSessionFlow.js +0 -43
  57. package/lib/commonjs/ui/context/commitSessionFlow.js.map +1 -1
  58. package/lib/commonjs/ui/context/useOxyAccountGraph.js +3 -4
  59. package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
  60. package/lib/commonjs/ui/hooks/queries/userCache.js +17 -17
  61. package/lib/commonjs/ui/hooks/queries/userCache.js.map +1 -1
  62. package/lib/commonjs/ui/hooks/useAssets.js.map +1 -1
  63. package/lib/commonjs/ui/hooks/useAuth.js +4 -2
  64. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  65. package/lib/commonjs/ui/hooks/useOnlineStatus.js +3 -2
  66. package/lib/commonjs/ui/hooks/useOnlineStatus.js.map +1 -1
  67. package/lib/commonjs/ui/hooks/useSessionManagement.js +0 -2
  68. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  69. package/lib/commonjs/ui/screens/AccountMembersScreen.js +4 -3
  70. package/lib/commonjs/ui/screens/AccountMembersScreen.js.map +1 -1
  71. package/lib/commonjs/ui/screens/ConnectedAppsScreen.js +1 -1
  72. package/lib/commonjs/ui/screens/ConnectedAppsScreen.js.map +1 -1
  73. package/lib/commonjs/ui/screens/CreateAccountScreen.js +7 -6
  74. package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
  75. package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +4 -3
  76. package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
  77. package/lib/commonjs/ui/screens/FAQScreen.js +3 -2
  78. package/lib/commonjs/ui/screens/FAQScreen.js.map +1 -1
  79. package/lib/commonjs/ui/screens/FeedbackScreen.js +96 -96
  80. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
  81. package/lib/commonjs/ui/screens/FileManagementScreen.js +19 -18
  82. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  83. package/lib/commonjs/ui/screens/HistoryViewScreen.js +4 -29
  84. package/lib/commonjs/ui/screens/HistoryViewScreen.js.map +1 -1
  85. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +4 -3
  86. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
  87. package/lib/commonjs/ui/screens/ManageAccountScreen.js +2 -2
  88. package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
  89. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js +3 -3
  90. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
  91. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +10 -9
  92. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  93. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +1 -0
  94. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
  95. package/lib/commonjs/ui/screens/ProfileScreen.js +12 -13
  96. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  97. package/lib/commonjs/ui/screens/UserListScreen.js +4 -4
  98. package/lib/commonjs/ui/screens/UserListScreen.js.map +1 -1
  99. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +6 -6
  100. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  101. package/lib/commonjs/ui/screens/fileManagement/FileLibraryError.js +4 -3
  102. package/lib/commonjs/ui/screens/fileManagement/FileLibraryError.js.map +1 -1
  103. package/lib/commonjs/ui/screens/fileManagement/FileListSection.js +4 -3
  104. package/lib/commonjs/ui/screens/fileManagement/FileListSection.js.map +1 -1
  105. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +5 -7
  106. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  107. package/lib/commonjs/ui/screens/fileManagement/UploadBar.js +3 -2
  108. package/lib/commonjs/ui/screens/fileManagement/UploadBar.js.map +1 -1
  109. package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js +3 -2
  110. package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js.map +1 -1
  111. package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js +5 -4
  112. package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -1
  113. package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js +4 -3
  114. package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js.map +1 -1
  115. package/lib/commonjs/ui/utils/fileManagement.js +24 -24
  116. package/lib/commonjs/ui/utils/fileManagement.js.map +1 -1
  117. package/lib/commonjs/ui/utils/netConnectivity.js +28 -0
  118. package/lib/commonjs/ui/utils/netConnectivity.js.map +1 -0
  119. package/lib/commonjs/ui/utils/oauthReturn.js +15 -28
  120. package/lib/commonjs/ui/utils/oauthReturn.js.map +1 -1
  121. package/lib/module/index.js +18 -0
  122. package/lib/module/index.js.map +1 -1
  123. package/lib/module/notifications/deviceNotifications.js +414 -0
  124. package/lib/module/notifications/deviceNotifications.js.map +1 -0
  125. package/lib/module/ui/boot/runProviderColdBoot.js +21 -65
  126. package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
  127. package/lib/module/ui/components/AvatarCameraBadge.js +1 -1
  128. package/lib/module/ui/components/AvatarCameraBadge.js.map +1 -1
  129. package/lib/module/ui/components/OxyAuthChooser.js +5 -4
  130. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
  131. package/lib/module/ui/components/OxyProvider.js +5 -6
  132. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  133. package/lib/module/ui/components/OxySignInRequestSurface.js +141 -0
  134. package/lib/module/ui/components/OxySignInRequestSurface.js.map +1 -0
  135. package/lib/module/ui/components/ProfileButton.js +1 -1
  136. package/lib/module/ui/components/ProfileButton.js.map +1 -1
  137. package/lib/module/ui/components/SettingsIcon.js +1 -1
  138. package/lib/module/ui/components/SettingsIcon.js.map +1 -1
  139. package/lib/module/ui/components/authChooser/AccountsMenuView.js +1 -1
  140. package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  141. package/lib/module/ui/components/authChooser/SignInEntryView.js.map +1 -1
  142. package/lib/module/ui/components/authChooser/SignInRequestView.js +66 -104
  143. package/lib/module/ui/components/authChooser/SignInRequestView.js.map +1 -1
  144. package/lib/module/ui/components/authChooser/TroubleDisclosure.js +0 -2
  145. package/lib/module/ui/components/authChooser/TroubleDisclosure.js.map +1 -1
  146. package/lib/module/ui/components/authChooser/primitives.js +1 -1
  147. package/lib/module/ui/components/authChooser/primitives.js.map +1 -1
  148. package/lib/module/ui/components/authChooser/requestSurfaces.js +13 -9
  149. package/lib/module/ui/components/authChooser/requestSurfaces.js.map +1 -1
  150. package/lib/module/ui/components/authChooser/types.js +11 -0
  151. package/lib/module/ui/components/authChooser/types.js.map +1 -1
  152. package/lib/module/ui/components/fileManagement/AnimatedButton.js +1 -1
  153. package/lib/module/ui/components/fileManagement/AnimatedButton.js.map +1 -1
  154. package/lib/module/ui/components/fileManagement/FileDetailsModal.js +1 -1
  155. package/lib/module/ui/components/fileManagement/FileDetailsModal.js.map +1 -1
  156. package/lib/module/ui/components/fileManagement/FileViewer.js +1 -1
  157. package/lib/module/ui/components/fileManagement/FileViewer.js.map +1 -1
  158. package/lib/module/ui/components/fileManagement/UploadPreview.js +1 -1
  159. package/lib/module/ui/components/fileManagement/UploadPreview.js.map +1 -1
  160. package/lib/module/ui/components/modals/DeleteAccountModal.js +1 -1
  161. package/lib/module/ui/components/modals/DeleteAccountModal.js.map +1 -1
  162. package/lib/module/ui/components/payment/PaymentDetailsStep.js +1 -1
  163. package/lib/module/ui/components/payment/PaymentDetailsStep.js.map +1 -1
  164. package/lib/module/ui/components/payment/PaymentMethodStep.js +1 -1
  165. package/lib/module/ui/components/payment/PaymentMethodStep.js.map +1 -1
  166. package/lib/module/ui/components/payment/PaymentReviewStep.js +1 -1
  167. package/lib/module/ui/components/payment/PaymentReviewStep.js.map +1 -1
  168. package/lib/module/ui/components/payment/PaymentSuccessStep.js +1 -1
  169. package/lib/module/ui/components/payment/PaymentSuccessStep.js.map +1 -1
  170. package/lib/module/ui/components/payment/PaymentSummaryStep.js +1 -1
  171. package/lib/module/ui/components/payment/PaymentSummaryStep.js.map +1 -1
  172. package/lib/module/ui/components/styles/shadow.js.map +1 -1
  173. package/lib/module/ui/context/OxyContext.js +16 -47
  174. package/lib/module/ui/context/OxyContext.js.map +1 -1
  175. package/lib/module/ui/context/commitSessionFlow.js +0 -42
  176. package/lib/module/ui/context/commitSessionFlow.js.map +1 -1
  177. package/lib/module/ui/context/useOxyAccountGraph.js +3 -4
  178. package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
  179. package/lib/module/ui/hooks/queries/userCache.js +17 -17
  180. package/lib/module/ui/hooks/queries/userCache.js.map +1 -1
  181. package/lib/module/ui/hooks/useAssets.js.map +1 -1
  182. package/lib/module/ui/hooks/useAuth.js +4 -2
  183. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  184. package/lib/module/ui/hooks/useOnlineStatus.js +3 -2
  185. package/lib/module/ui/hooks/useOnlineStatus.js.map +1 -1
  186. package/lib/module/ui/hooks/useSessionManagement.js +0 -2
  187. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  188. package/lib/module/ui/screens/AccountMembersScreen.js +1 -1
  189. package/lib/module/ui/screens/AccountMembersScreen.js.map +1 -1
  190. package/lib/module/ui/screens/ConnectedAppsScreen.js +1 -1
  191. package/lib/module/ui/screens/ConnectedAppsScreen.js.map +1 -1
  192. package/lib/module/ui/screens/CreateAccountScreen.js +1 -1
  193. package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
  194. package/lib/module/ui/screens/EditProfileFieldScreen.js +1 -1
  195. package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
  196. package/lib/module/ui/screens/FAQScreen.js +1 -1
  197. package/lib/module/ui/screens/FAQScreen.js.map +1 -1
  198. package/lib/module/ui/screens/FeedbackScreen.js +91 -90
  199. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
  200. package/lib/module/ui/screens/FileManagementScreen.js +2 -1
  201. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  202. package/lib/module/ui/screens/HistoryViewScreen.js +4 -29
  203. package/lib/module/ui/screens/HistoryViewScreen.js.map +1 -1
  204. package/lib/module/ui/screens/LanguageSelectorScreen.js +1 -1
  205. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
  206. package/lib/module/ui/screens/ManageAccountScreen.js +1 -1
  207. package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
  208. package/lib/module/ui/screens/PaymentGatewayScreen.js +1 -1
  209. package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
  210. package/lib/module/ui/screens/PremiumSubscriptionScreen.js +2 -2
  211. package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  212. package/lib/module/ui/screens/PrivacySettingsScreen.js +1 -0
  213. package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
  214. package/lib/module/ui/screens/ProfileScreen.js +4 -5
  215. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  216. package/lib/module/ui/screens/UserListScreen.js +3 -3
  217. package/lib/module/ui/screens/UserListScreen.js.map +1 -1
  218. package/lib/module/ui/screens/WelcomeNewUserScreen.js +5 -6
  219. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  220. package/lib/module/ui/screens/fileManagement/FileLibraryError.js +1 -1
  221. package/lib/module/ui/screens/fileManagement/FileLibraryError.js.map +1 -1
  222. package/lib/module/ui/screens/fileManagement/FileListSection.js +1 -1
  223. package/lib/module/ui/screens/fileManagement/FileListSection.js.map +1 -1
  224. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +3 -5
  225. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  226. package/lib/module/ui/screens/fileManagement/UploadBar.js +1 -1
  227. package/lib/module/ui/screens/fileManagement/UploadBar.js.map +1 -1
  228. package/lib/module/ui/screens/trust/TrustCenterScreen.js +1 -1
  229. package/lib/module/ui/screens/trust/TrustCenterScreen.js.map +1 -1
  230. package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js +3 -3
  231. package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -1
  232. package/lib/module/ui/screens/trust/TrustRewardsScreen.js +1 -1
  233. package/lib/module/ui/screens/trust/TrustRewardsScreen.js.map +1 -1
  234. package/lib/module/ui/utils/fileManagement.js +25 -25
  235. package/lib/module/ui/utils/fileManagement.js.map +1 -1
  236. package/lib/module/ui/utils/netConnectivity.js +23 -0
  237. package/lib/module/ui/utils/netConnectivity.js.map +1 -0
  238. package/lib/module/ui/utils/oauthReturn.js +15 -27
  239. package/lib/module/ui/utils/oauthReturn.js.map +1 -1
  240. package/lib/typescript/commonjs/index.d.ts +5 -0
  241. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  242. package/lib/typescript/commonjs/notifications/deviceNotifications.d.ts +173 -0
  243. package/lib/typescript/commonjs/notifications/deviceNotifications.d.ts.map +1 -0
  244. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts +11 -30
  245. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  246. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts +5 -3
  247. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
  248. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  249. package/lib/typescript/commonjs/ui/components/OxySignInRequestSurface.d.ts +108 -0
  250. package/lib/typescript/commonjs/ui/components/OxySignInRequestSurface.d.ts.map +1 -0
  251. package/lib/typescript/commonjs/ui/components/SettingsIcon.d.ts +1 -1
  252. package/lib/typescript/commonjs/ui/components/SettingsIcon.d.ts.map +1 -1
  253. package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
  254. package/lib/typescript/commonjs/ui/components/authChooser/SignInRequestView.d.ts +18 -18
  255. package/lib/typescript/commonjs/ui/components/authChooser/SignInRequestView.d.ts.map +1 -1
  256. package/lib/typescript/commonjs/ui/components/authChooser/TroubleDisclosure.d.ts +3 -10
  257. package/lib/typescript/commonjs/ui/components/authChooser/TroubleDisclosure.d.ts.map +1 -1
  258. package/lib/typescript/commonjs/ui/components/authChooser/requestSurfaces.d.ts +8 -4
  259. package/lib/typescript/commonjs/ui/components/authChooser/requestSurfaces.d.ts.map +1 -1
  260. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts +18 -0
  261. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -1
  262. package/lib/typescript/commonjs/ui/components/payment/types.d.ts +6 -6
  263. package/lib/typescript/commonjs/ui/components/payment/types.d.ts.map +1 -1
  264. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  265. package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts +0 -40
  266. package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts.map +1 -1
  267. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +4 -7
  268. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
  269. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +0 -1
  270. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  271. package/lib/typescript/commonjs/ui/hooks/queries/userCache.d.ts +2 -2
  272. package/lib/typescript/commonjs/ui/hooks/queries/userCache.d.ts.map +1 -1
  273. package/lib/typescript/commonjs/ui/hooks/useAssets.d.ts +1 -1
  274. package/lib/typescript/commonjs/ui/hooks/useAssets.d.ts.map +1 -1
  275. package/lib/typescript/commonjs/ui/hooks/useAsyncAction.d.ts +2 -2
  276. package/lib/typescript/commonjs/ui/hooks/useAsyncAction.d.ts.map +1 -1
  277. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +4 -2
  278. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  279. package/lib/typescript/commonjs/ui/hooks/useOnlineStatus.d.ts +3 -2
  280. package/lib/typescript/commonjs/ui/hooks/useOnlineStatus.d.ts.map +1 -1
  281. package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
  282. package/lib/typescript/commonjs/ui/screens/FeedbackScreen.d.ts +2 -2
  283. package/lib/typescript/commonjs/ui/screens/FeedbackScreen.d.ts.map +1 -1
  284. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
  285. package/lib/typescript/commonjs/ui/screens/HistoryViewScreen.d.ts.map +1 -1
  286. package/lib/typescript/commonjs/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  287. package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
  288. package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  289. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  290. package/lib/typescript/commonjs/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -1
  291. package/lib/typescript/commonjs/ui/types/navigation.d.ts +20 -19
  292. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  293. package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts +5 -5
  294. package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts.map +1 -1
  295. package/lib/typescript/commonjs/ui/utils/netConnectivity.d.ts +17 -0
  296. package/lib/typescript/commonjs/ui/utils/netConnectivity.d.ts.map +1 -0
  297. package/lib/typescript/commonjs/ui/utils/oauthReturn.d.ts +15 -12
  298. package/lib/typescript/commonjs/ui/utils/oauthReturn.d.ts.map +1 -1
  299. package/lib/typescript/module/index.d.ts +5 -0
  300. package/lib/typescript/module/index.d.ts.map +1 -1
  301. package/lib/typescript/module/notifications/deviceNotifications.d.ts +173 -0
  302. package/lib/typescript/module/notifications/deviceNotifications.d.ts.map +1 -0
  303. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts +11 -30
  304. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  305. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts +5 -3
  306. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
  307. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  308. package/lib/typescript/module/ui/components/OxySignInRequestSurface.d.ts +108 -0
  309. package/lib/typescript/module/ui/components/OxySignInRequestSurface.d.ts.map +1 -0
  310. package/lib/typescript/module/ui/components/SettingsIcon.d.ts +1 -1
  311. package/lib/typescript/module/ui/components/SettingsIcon.d.ts.map +1 -1
  312. package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
  313. package/lib/typescript/module/ui/components/authChooser/SignInRequestView.d.ts +18 -18
  314. package/lib/typescript/module/ui/components/authChooser/SignInRequestView.d.ts.map +1 -1
  315. package/lib/typescript/module/ui/components/authChooser/TroubleDisclosure.d.ts +3 -10
  316. package/lib/typescript/module/ui/components/authChooser/TroubleDisclosure.d.ts.map +1 -1
  317. package/lib/typescript/module/ui/components/authChooser/requestSurfaces.d.ts +8 -4
  318. package/lib/typescript/module/ui/components/authChooser/requestSurfaces.d.ts.map +1 -1
  319. package/lib/typescript/module/ui/components/authChooser/types.d.ts +18 -0
  320. package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -1
  321. package/lib/typescript/module/ui/components/payment/types.d.ts +6 -6
  322. package/lib/typescript/module/ui/components/payment/types.d.ts.map +1 -1
  323. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  324. package/lib/typescript/module/ui/context/commitSessionFlow.d.ts +0 -40
  325. package/lib/typescript/module/ui/context/commitSessionFlow.d.ts.map +1 -1
  326. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +4 -7
  327. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
  328. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +0 -1
  329. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  330. package/lib/typescript/module/ui/hooks/queries/userCache.d.ts +2 -2
  331. package/lib/typescript/module/ui/hooks/queries/userCache.d.ts.map +1 -1
  332. package/lib/typescript/module/ui/hooks/useAssets.d.ts +1 -1
  333. package/lib/typescript/module/ui/hooks/useAssets.d.ts.map +1 -1
  334. package/lib/typescript/module/ui/hooks/useAsyncAction.d.ts +2 -2
  335. package/lib/typescript/module/ui/hooks/useAsyncAction.d.ts.map +1 -1
  336. package/lib/typescript/module/ui/hooks/useAuth.d.ts +4 -2
  337. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  338. package/lib/typescript/module/ui/hooks/useOnlineStatus.d.ts +3 -2
  339. package/lib/typescript/module/ui/hooks/useOnlineStatus.d.ts.map +1 -1
  340. package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
  341. package/lib/typescript/module/ui/screens/FeedbackScreen.d.ts +2 -2
  342. package/lib/typescript/module/ui/screens/FeedbackScreen.d.ts.map +1 -1
  343. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
  344. package/lib/typescript/module/ui/screens/HistoryViewScreen.d.ts.map +1 -1
  345. package/lib/typescript/module/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  346. package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
  347. package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  348. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  349. package/lib/typescript/module/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -1
  350. package/lib/typescript/module/ui/types/navigation.d.ts +20 -19
  351. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  352. package/lib/typescript/module/ui/utils/fileManagement.d.ts +5 -5
  353. package/lib/typescript/module/ui/utils/fileManagement.d.ts.map +1 -1
  354. package/lib/typescript/module/ui/utils/netConnectivity.d.ts +17 -0
  355. package/lib/typescript/module/ui/utils/netConnectivity.d.ts.map +1 -0
  356. package/lib/typescript/module/ui/utils/oauthReturn.d.ts +15 -12
  357. package/lib/typescript/module/ui/utils/oauthReturn.d.ts.map +1 -1
  358. package/package.json +18 -8
  359. package/src/index.ts +32 -0
  360. package/src/notifications/deviceNotifications.ts +458 -0
  361. package/src/ui/boot/__tests__/coldBootNoIdpRedirect.test.ts +215 -0
  362. package/src/ui/boot/runProviderColdBoot.ts +24 -101
  363. package/src/ui/components/AvatarCameraBadge.tsx +1 -1
  364. package/src/ui/components/OxyAuthChooser.tsx +5 -4
  365. package/src/ui/components/OxyProvider.tsx +5 -6
  366. package/src/ui/components/OxySignInRequestSurface.tsx +202 -0
  367. package/src/ui/components/ProfileButton.tsx +1 -1
  368. package/src/ui/components/SettingsIcon.tsx +1 -1
  369. package/src/ui/components/authChooser/AccountsMenuView.tsx +1 -1
  370. package/src/ui/components/authChooser/SignInEntryView.tsx +9 -3
  371. package/src/ui/components/authChooser/SignInRequestView.tsx +78 -111
  372. package/src/ui/components/authChooser/TroubleDisclosure.tsx +3 -11
  373. package/src/ui/components/authChooser/primitives.tsx +1 -1
  374. package/src/ui/components/authChooser/requestSurfaces.tsx +15 -11
  375. package/src/ui/components/authChooser/types.ts +19 -0
  376. package/src/ui/components/fileManagement/AnimatedButton.tsx +1 -1
  377. package/src/ui/components/fileManagement/FileDetailsModal.tsx +1 -1
  378. package/src/ui/components/fileManagement/FileViewer.tsx +1 -1
  379. package/src/ui/components/fileManagement/UploadPreview.tsx +1 -1
  380. package/src/ui/components/modals/DeleteAccountModal.tsx +1 -1
  381. package/src/ui/components/payment/PaymentDetailsStep.tsx +1 -1
  382. package/src/ui/components/payment/PaymentMethodStep.tsx +1 -1
  383. package/src/ui/components/payment/PaymentReviewStep.tsx +1 -1
  384. package/src/ui/components/payment/PaymentSuccessStep.tsx +1 -1
  385. package/src/ui/components/payment/PaymentSummaryStep.tsx +1 -1
  386. package/src/ui/components/payment/types.ts +24 -24
  387. package/src/ui/components/styles/shadow.tsx +2 -2
  388. package/src/ui/context/OxyContext.tsx +15 -46
  389. package/src/ui/context/__tests__/commitSessionFlow.test.ts +0 -89
  390. package/src/ui/context/commitSessionFlow.ts +0 -61
  391. package/src/ui/context/oxyContextTypes.ts +4 -7
  392. package/src/ui/context/useOxyAccountGraph.ts +4 -4
  393. package/src/ui/hooks/queries/userCache.ts +169 -148
  394. package/src/ui/hooks/useAssets.ts +1 -1
  395. package/src/ui/hooks/useAsyncAction.ts +2 -2
  396. package/src/ui/hooks/useAuth.ts +4 -2
  397. package/src/ui/hooks/useOnlineStatus.ts +3 -2
  398. package/src/ui/hooks/useSessionManagement.ts +0 -2
  399. package/src/ui/screens/AccountMembersScreen.tsx +1 -1
  400. package/src/ui/screens/ConnectedAppsScreen.tsx +1 -1
  401. package/src/ui/screens/CreateAccountScreen.tsx +1 -1
  402. package/src/ui/screens/EditProfileFieldScreen.tsx +1 -1
  403. package/src/ui/screens/FAQScreen.tsx +1 -1
  404. package/src/ui/screens/FeedbackScreen.tsx +740 -574
  405. package/src/ui/screens/FileManagementScreen.tsx +2 -1
  406. package/src/ui/screens/HistoryViewScreen.tsx +4 -19
  407. package/src/ui/screens/LanguageSelectorScreen.tsx +1 -1
  408. package/src/ui/screens/ManageAccountScreen.tsx +1 -1
  409. package/src/ui/screens/PaymentGatewayScreen.tsx +1 -1
  410. package/src/ui/screens/PremiumSubscriptionScreen.tsx +2 -2
  411. package/src/ui/screens/PrivacySettingsScreen.tsx +1 -0
  412. package/src/ui/screens/ProfileScreen.tsx +6 -4
  413. package/src/ui/screens/UserListScreen.tsx +3 -3
  414. package/src/ui/screens/WelcomeNewUserScreen.tsx +6 -6
  415. package/src/ui/screens/fileManagement/FileLibraryError.tsx +1 -1
  416. package/src/ui/screens/fileManagement/FileListSection.tsx +1 -1
  417. package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +3 -2
  418. package/src/ui/screens/fileManagement/UploadBar.tsx +1 -1
  419. package/src/ui/screens/trust/TrustCenterScreen.tsx +1 -1
  420. package/src/ui/screens/trust/TrustLeaderboardScreen.tsx +4 -3
  421. package/src/ui/screens/trust/TrustRewardsScreen.tsx +1 -1
  422. package/src/ui/types/navigation.ts +20 -19
  423. package/src/ui/utils/fileManagement.ts +108 -92
  424. package/src/ui/utils/netConnectivity.ts +27 -0
  425. package/src/ui/utils/oauthReturn.ts +15 -30
  426. package/lib/commonjs/ui/oauth/legacyRedirectLanes.js +0 -48
  427. package/lib/commonjs/ui/oauth/legacyRedirectLanes.js.map +0 -1
  428. package/lib/commonjs/ui/utils/crossOriginRestore.js +0 -140
  429. package/lib/commonjs/ui/utils/crossOriginRestore.js.map +0 -1
  430. package/lib/module/ui/oauth/legacyRedirectLanes.js +0 -44
  431. package/lib/module/ui/oauth/legacyRedirectLanes.js.map +0 -1
  432. package/lib/module/ui/utils/crossOriginRestore.js +0 -131
  433. package/lib/module/ui/utils/crossOriginRestore.js.map +0 -1
  434. package/lib/typescript/commonjs/ui/oauth/legacyRedirectLanes.d.ts +0 -40
  435. package/lib/typescript/commonjs/ui/oauth/legacyRedirectLanes.d.ts.map +0 -1
  436. package/lib/typescript/commonjs/ui/utils/crossOriginRestore.d.ts +0 -43
  437. package/lib/typescript/commonjs/ui/utils/crossOriginRestore.d.ts.map +0 -1
  438. package/lib/typescript/module/ui/oauth/legacyRedirectLanes.d.ts +0 -40
  439. package/lib/typescript/module/ui/oauth/legacyRedirectLanes.d.ts.map +0 -1
  440. package/lib/typescript/module/ui/utils/crossOriginRestore.d.ts +0 -43
  441. package/lib/typescript/module/ui/utils/crossOriginRestore.d.ts.map +0 -1
  442. package/src/ui/boot/__tests__/coldBootWebAuthMode.test.ts +0 -281
  443. package/src/ui/oauth/legacyRedirectLanes.ts +0 -43
  444. package/src/ui/utils/__tests__/crossOriginRestore.test.ts +0 -160
  445. package/src/ui/utils/crossOriginRestore.ts +0 -168
@@ -1,30 +1,33 @@
1
- import type React from 'react';
2
- import { useState, useRef, useCallback, useMemo } from 'react';
1
+ import Ionicons from "@expo/vector-icons/Ionicons";
2
+ import type MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
3
+ import { toast } from "@oxyhq/bloom";
4
+ import { Button } from "@oxyhq/bloom/button";
3
5
  import {
4
- View,
5
- Platform,
6
- Animated,
7
- StyleSheet,
8
- } from 'react-native';
9
- import type { BaseScreenProps } from '../types/navigation';
10
- import { normalizeTheme } from '@oxyhq/core';
11
- import { packageInfo } from '@oxyhq/core';
12
- import { useTheme } from '@oxyhq/bloom/theme';
13
- import { Ionicons, type MaterialCommunityIcons } from '@expo/vector-icons';
14
- import { toast } from '@oxyhq/bloom';
15
- import { H2, Text } from '@oxyhq/bloom/typography';
16
- import { Button } from '@oxyhq/bloom/button';
17
- import { TextField, TextFieldInput } from '@oxyhq/bloom/text-field';
18
- import { Switch } from '@oxyhq/bloom/switch';
19
- import { SettingsListGroup, SettingsListItem } from '@oxyhq/bloom/settings-list';
20
- import { SettingsIcon } from '../components/SettingsIcon';
21
- import { useI18n } from '../hooks/useI18n';
22
- import { useSurfaceHeader } from '../hooks/useSurfaceHeader';
23
- import { useOxy } from '../context/OxyContext';
24
-
25
- import { useFeedbackForm } from '../components/feedback/useFeedbackForm';
26
- import { FEEDBACK_TYPES, PRIORITY_LEVELS, CATEGORIES } from '../components/feedback/constants';
27
- import type { FeedbackData } from '../components/feedback/types';
6
+ SettingsListGroup,
7
+ SettingsListItem,
8
+ } from "@oxyhq/bloom/settings-list";
9
+ import { Switch } from "@oxyhq/bloom/switch";
10
+ import { TextField, TextFieldInput } from "@oxyhq/bloom/text-field";
11
+ import { useTheme } from "@oxyhq/bloom/theme";
12
+ import { H2, Text } from "@oxyhq/bloom/typography";
13
+ import { normalizeTheme } from "@oxyhq/core";
14
+ import { packageInfo } from "@oxyhq/core";
15
+ import type React from "react";
16
+ import { useCallback, useMemo, useRef, useState } from "react";
17
+ import { Animated, Platform, StyleSheet, View } from "react-native";
18
+ import { SettingsIcon } from "../components/SettingsIcon";
19
+ import { useOxy } from "../context/OxyContext";
20
+ import { useI18n } from "../hooks/useI18n";
21
+ import { useSurfaceHeader } from "../hooks/useSurfaceHeader";
22
+ import type { BaseScreenProps } from "../types/navigation";
23
+
24
+ import {
25
+ CATEGORIES,
26
+ FEEDBACK_TYPES,
27
+ PRIORITY_LEVELS,
28
+ } from "../components/feedback/constants";
29
+ import type { FeedbackData } from "../components/feedback/types";
30
+ import { useFeedbackForm } from "../components/feedback/useFeedbackForm";
28
31
 
29
32
  /** Step transition timing — measured animation, not a design token. */
30
33
  const STEP_FADE_OUT_MS = 250;
@@ -36,564 +39,727 @@ const LAST_STEP_INDEX = TOTAL_STEPS - 1;
36
39
  const SUCCESS_RESET_DELAY_MS = 3000;
37
40
 
38
41
  /** Maps each feedback type to a Bloom theme role + MaterialCommunityIcons glyph. */
39
- const TYPE_ICON_GLYPH: Record<FeedbackData['type'], React.ComponentProps<typeof MaterialCommunityIcons>['name']> = {
40
- bug: 'bug',
41
- feature: 'lightbulb-on',
42
- general: 'chat',
43
- support: 'help-circle',
42
+ const TYPE_ICON_GLYPH: Record<
43
+ FeedbackData["type"],
44
+ React.ComponentProps<typeof MaterialCommunityIcons>["name"]
45
+ > = {
46
+ bug: "bug",
47
+ feature: "lightbulb-on",
48
+ general: "chat",
49
+ support: "help-circle",
44
50
  };
45
51
 
46
52
  /** Maps each priority level to a MaterialCommunityIcons glyph. */
47
- const PRIORITY_ICON_GLYPH: Record<FeedbackData['priority'], React.ComponentProps<typeof MaterialCommunityIcons>['name']> = {
48
- low: 'arrow-down',
49
- medium: 'minus',
50
- high: 'arrow-up',
51
- critical: 'alert',
53
+ const PRIORITY_ICON_GLYPH: Record<
54
+ FeedbackData["priority"],
55
+ React.ComponentProps<typeof MaterialCommunityIcons>["name"]
56
+ > = {
57
+ low: "arrow-down",
58
+ medium: "minus",
59
+ high: "arrow-up",
60
+ critical: "alert",
52
61
  };
53
62
 
54
63
  const FeedbackScreen: React.FC<BaseScreenProps> = ({
55
- goBack,
56
- onClose,
57
- theme,
64
+ goBack,
65
+ onClose,
66
+ theme,
58
67
  }) => {
59
- const { user, oxyServices } = useOxy();
60
- const normalizedTheme = normalizeTheme(theme);
61
- const bloomTheme = useTheme();
62
- const colors = bloomTheme.colors;
63
- const { t } = useI18n();
64
-
65
- useSurfaceHeader({ title: t('feedback.title') || 'Feedback' });
66
-
67
- const { feedbackData, feedbackState, setFeedbackState, updateField, resetForm } = useFeedbackForm();
68
-
69
- const [currentStep, setCurrentStep] = useState(0);
70
- const [, setErrorMessage] = useState('');
71
-
72
- const fadeAnim = useRef(new Animated.Value(1)).current;
73
- const slideAnim = useRef(new Animated.Value(0)).current;
74
-
75
- /** Theme-role color for each feedback type pill. */
76
- const typeColor = useCallback((id: FeedbackData['type']): string => {
77
- switch (id) {
78
- case 'bug':
79
- return colors.negative;
80
- case 'feature':
81
- return colors.info;
82
- case 'general':
83
- return colors.success;
84
- case 'support':
85
- return colors.warning;
86
- default:
87
- return colors.primary;
88
- }
89
- }, [colors]);
90
-
91
- /** Theme-role color for each priority level. */
92
- const priorityColor = useCallback((id: FeedbackData['priority']): string => {
93
- switch (id) {
94
- case 'low':
95
- return colors.success;
96
- case 'medium':
97
- return colors.warning;
98
- case 'high':
99
- return colors.negative;
100
- case 'critical':
101
- return colors.negative;
102
- default:
103
- return colors.primary;
104
- }
105
- }, [colors]);
106
-
107
- const animateTransition = useCallback((nextStep: number) => {
108
- Animated.timing(fadeAnim, {
109
- toValue: 0,
110
- duration: STEP_FADE_OUT_MS,
111
- useNativeDriver: Platform.OS !== 'web',
112
- }).start(() => {
113
- setCurrentStep(nextStep);
114
- slideAnim.setValue(STEP_SLIDE_FROM);
115
-
116
- Animated.parallel([
117
- Animated.timing(fadeAnim, {
118
- toValue: 1,
119
- duration: STEP_FADE_IN_MS,
120
- useNativeDriver: Platform.OS !== 'web',
121
- }),
122
- Animated.timing(slideAnim, {
123
- toValue: 0,
124
- duration: STEP_SLIDE_IN_MS,
125
- useNativeDriver: Platform.OS !== 'web',
126
- }),
127
- ]).start();
128
- });
129
- }, [fadeAnim, slideAnim]);
130
-
131
- const nextStep = useCallback(() => {
132
- if (currentStep < LAST_STEP_INDEX) {
133
- animateTransition(currentStep + 1);
134
- }
135
- }, [currentStep, animateTransition]);
136
-
137
- const prevStep = useCallback(() => {
138
- if (currentStep > 0) {
139
- animateTransition(currentStep - 1);
140
- }
141
- }, [currentStep, animateTransition]);
142
-
143
- const isTypeStepValid = useCallback(() => {
144
- return Boolean(feedbackData.type && feedbackData.category);
145
- }, [feedbackData.type, feedbackData.category]);
146
-
147
- const isDetailsStepValid = useCallback(() => {
148
- return Boolean(feedbackData.title.trim() && feedbackData.description.trim());
149
- }, [feedbackData.title, feedbackData.description]);
150
-
151
- const isContactStepValid = useCallback(() => {
152
- return Boolean(feedbackData.contactEmail.trim() || user?.email);
153
- }, [feedbackData.contactEmail, user?.email]);
154
-
155
- const handleSubmitFeedback = useCallback(async () => {
156
- if (!isTypeStepValid() || !isDetailsStepValid() || !isContactStepValid()) {
157
- toast.error(t('feedback.toasts.fillRequired') || 'Please fill in all required fields');
158
- return;
159
- }
160
-
161
- try {
162
- setFeedbackState({ status: 'submitting', message: '' });
163
- setErrorMessage('');
164
-
165
- const feedbackPayload = {
166
- type: feedbackData.type,
167
- title: feedbackData.title,
168
- description: feedbackData.description,
169
- priority: feedbackData.priority,
170
- category: feedbackData.category,
171
- contactEmail: feedbackData.contactEmail || user?.email,
172
- systemInfo: feedbackData.systemInfo ? {
173
- platform: Platform.OS,
174
- version: Platform.Version?.toString() || 'Unknown',
175
- appVersion: packageInfo.version,
176
- userId: user?.id,
177
- username: user?.username,
178
- timestamp: new Date().toISOString(),
179
- } : undefined,
180
- };
181
-
182
- await oxyServices.submitFeedback(feedbackPayload);
183
-
184
- setFeedbackState({ status: 'success', message: t('feedback.toasts.submitSuccess') || 'Feedback submitted successfully!' });
185
- toast.success(t('feedback.toasts.thanks') || 'Thank you for your feedback!');
186
-
187
- setTimeout(() => {
188
- resetForm();
189
- setCurrentStep(0);
190
- }, SUCCESS_RESET_DELAY_MS);
191
-
192
- } catch (error: unknown) {
193
- const message = (error instanceof Error ? error.message : null)
194
- || (t('feedback.toasts.submitFailed') || 'Failed to submit feedback');
195
- setFeedbackState({ status: 'error', message });
196
- toast.error(message);
197
- }
198
- }, [feedbackData, user, oxyServices, isTypeStepValid, isDetailsStepValid, isContactStepValid, resetForm, setFeedbackState, t]);
199
-
200
- const feedbackTypeData = useMemo(() => FEEDBACK_TYPES.map(type => ({
201
- ...type,
202
- isSelected: feedbackData.type === type.id,
203
- })), [feedbackData.type]);
204
-
205
- const categoryData = useMemo(() => (feedbackData.type ? (CATEGORIES[feedbackData.type] || []).map(cat => ({
206
- name: cat,
207
- isSelected: feedbackData.category === cat,
208
- })) : []), [feedbackData.type, feedbackData.category]);
209
-
210
- const priorityData = useMemo(() => PRIORITY_LEVELS.map(p => ({
211
- ...p,
212
- isSelected: feedbackData.priority === p.id,
213
- })), [feedbackData.priority]);
214
-
215
- const renderProgress = () => (
216
- <View
217
- className="flex-row justify-center gap-space-8 pt-space-8 pb-space-16"
218
- accessibilityRole="progressbar"
219
- accessibilityLabel={`Step ${currentStep + 1} of ${TOTAL_STEPS}`}
220
- >
221
- {Array.from({ length: TOTAL_STEPS }, (_, index) => (
222
- <View
223
- key={index}
224
- className={
225
- currentStep === index
226
- ? 'h-space-8 rounded-radius-max bg-fill-brand'
227
- : 'h-space-8 rounded-radius-max bg-fill-secondary'
228
- }
229
- style={currentStep === index ? styles.progressDotActive : styles.progressDot}
230
- />
231
- ))}
232
- </View>
233
- );
234
-
235
- const renderStepHeader = (title: string, subtitle: string) => (
236
- <View className="w-full gap-space-8 mb-space-24">
237
- <H2 className="text-headerBold font-headerBold text-text">{title}</H2>
238
- <Text className="text-body font-body text-text-secondary">{subtitle}</Text>
239
- </View>
240
- );
241
-
242
- const renderTypeStep = () => (
243
- <Animated.View style={[styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }]}>
244
- {renderStepHeader(
245
- t('feedback.type.title') || 'What type of feedback?',
246
- t('feedback.type.subtitle') || 'Choose the category that best describes your feedback',
247
- )}
248
-
249
- <View className="w-full">
250
- <SettingsListGroup>
251
- {feedbackTypeData.map(type => (
252
- <SettingsListItem
253
- key={type.id}
254
- icon={<SettingsIcon name={TYPE_ICON_GLYPH[type.id]} color={typeColor(type.id)} />}
255
- title={type.label}
256
- description={type.description}
257
- onPress={() => { updateField('type', type.id); updateField('category', ''); }}
258
- showChevron={false}
259
- />
260
- ))}
261
- </SettingsListGroup>
262
- </View>
263
-
264
- {feedbackData.type ? (
265
- <View className="w-full mt-space-24">
266
- <Text className="text-caption font-caption text-text-secondary mb-space-8">
267
- {t('feedback.category.label') || 'Category'}
268
- </Text>
269
- <View className="w-full">
270
- <SettingsListGroup>
271
- {categoryData.map(cat => (
272
- <SettingsListItem
273
- key={cat.name}
274
- icon={(
275
- <SettingsIcon
276
- name={cat.isSelected ? 'check-circle' : 'circle-outline'}
277
- color={cat.isSelected ? colors.primary : colors.textSecondary}
278
- />
279
- )}
280
- title={cat.name}
281
- onPress={() => updateField('category', cat.name)}
282
- showChevron={false}
283
- />
284
- ))}
285
- </SettingsListGroup>
286
- </View>
287
- </View>
288
- ) : null}
289
-
290
- <View className="flex-row gap-space-8 mt-space-24 w-full">
291
- <Button
292
- variant="secondary"
293
- onPress={goBack}
294
- accessibilityLabel="Go back"
295
- icon={<Ionicons name="arrow-back" size={16} color={colors.text} />}
296
- style={styles.navButton}
297
- >
298
- {t('common.actions.back') || 'Back'}
299
- </Button>
300
-
301
- <Button
302
- variant="primary"
303
- onPress={nextStep}
304
- disabled={!isTypeStepValid()}
305
- accessibilityLabel="Continue to next step"
306
- iconPosition="right"
307
- icon={<Ionicons name="arrow-forward" size={16} color={colors.primaryForeground} />}
308
- style={styles.navButton}
309
- >
310
- {t('common.actions.next') || 'Next'}
311
- </Button>
312
- </View>
313
- </Animated.View>
314
- );
315
-
316
- const renderDetailsStep = () => (
317
- <Animated.View style={[styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }]}>
318
- {renderStepHeader(
319
- t('feedback.details.title') || 'Tell us more',
320
- t('feedback.details.subtitle') || 'Provide details about your feedback',
321
- )}
322
-
323
- <View className="w-full gap-space-16">
324
- <TextField>
325
- <TextFieldInput
326
- floatingLabel
327
- label={t('feedback.fields.title.label') || 'Title'}
328
- value={feedbackData.title}
329
- onChangeText={(text) => { updateField('title', text); setErrorMessage(''); }}
330
- placeholder={t('feedback.fields.title.placeholder') || 'Brief summary of your feedback'}
331
- testID="feedback-title-input"
332
- accessibilityLabel="Feedback title"
333
- accessibilityHint="Enter a brief summary of your feedback"
334
- />
335
- </TextField>
336
-
337
- <TextField>
338
- <TextFieldInput
339
- floatingLabel
340
- multiline
341
- numberOfLines={6}
342
- label={t('feedback.fields.description.label') || 'Description'}
343
- value={feedbackData.description}
344
- onChangeText={(text) => { updateField('description', text); setErrorMessage(''); }}
345
- placeholder={t('feedback.fields.description.placeholder') || 'Please provide detailed information...'}
346
- testID="feedback-description-input"
347
- accessibilityLabel="Feedback description"
348
- accessibilityHint="Provide detailed information about your feedback"
349
- />
350
- </TextField>
351
- </View>
352
-
353
- <View className="w-full mt-space-24">
354
- <Text className="text-caption font-caption text-text-secondary mb-space-8">
355
- {t('feedback.priority.label') || 'Priority Level'}
356
- </Text>
357
- <View className="w-full">
358
- <SettingsListGroup>
359
- {priorityData.map(p => (
360
- <SettingsListItem
361
- key={p.id}
362
- icon={<SettingsIcon name={PRIORITY_ICON_GLYPH[p.id]} color={priorityColor(p.id)} />}
363
- title={p.label}
364
- onPress={() => updateField('priority', p.id)}
365
- showChevron={false}
366
- />
367
- ))}
368
- </SettingsListGroup>
369
- </View>
370
- </View>
371
-
372
- <View className="flex-row gap-space-8 mt-space-24 w-full">
373
- <Button
374
- variant="secondary"
375
- onPress={prevStep}
376
- accessibilityLabel="Go back"
377
- icon={<Ionicons name="arrow-back" size={16} color={colors.text} />}
378
- style={styles.navButton}
379
- >
380
- {t('common.actions.back') || 'Back'}
381
- </Button>
382
-
383
- <Button
384
- variant="primary"
385
- onPress={nextStep}
386
- disabled={!isDetailsStepValid()}
387
- accessibilityLabel="Continue to next step"
388
- iconPosition="right"
389
- icon={<Ionicons name="arrow-forward" size={16} color={colors.primaryForeground} />}
390
- style={styles.navButton}
391
- >
392
- {t('common.actions.next') || 'Next'}
393
- </Button>
394
- </View>
395
- </Animated.View>
396
- );
397
-
398
- const renderContactStep = () => (
399
- <Animated.View style={[styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }]}>
400
- {renderStepHeader(
401
- t('feedback.contact.title') || 'Contact Information',
402
- t('feedback.contact.subtitle') || 'Help us get back to you',
403
- )}
404
-
405
- <View className="w-full">
406
- <TextField>
407
- <TextFieldInput
408
- floatingLabel
409
- label={t('feedback.fields.email.label') || 'Email Address'}
410
- value={feedbackData.contactEmail}
411
- onChangeText={(text) => { updateField('contactEmail', text); setErrorMessage(''); }}
412
- placeholder={user?.email || (t('feedback.fields.email.placeholder') || 'Enter your email address')}
413
- keyboardType="email-address"
414
- autoCapitalize="none"
415
- autoCorrect={false}
416
- testID="feedback-email-input"
417
- accessibilityLabel="Email address"
418
- accessibilityHint="Enter your email so we can respond"
419
- />
420
- </TextField>
421
- </View>
422
-
423
- <View className="w-full mt-space-16">
424
- <SettingsListGroup>
425
- <SettingsListItem
426
- icon={<SettingsIcon name="information" color={colors.info} />}
427
- title={t('feedback.contact.includeSystemInfo') || 'Include system information to help us better understand your issue'}
428
- rightElement={(
429
- <Switch
430
- value={feedbackData.systemInfo}
431
- onValueChange={(value) => updateField('systemInfo', value)}
432
- testID="feedback-system-info-switch"
433
- />
434
- )}
435
- showChevron={false}
436
- />
437
- </SettingsListGroup>
438
- </View>
439
-
440
- <View className="flex-row gap-space-8 mt-space-24 w-full">
441
- <Button
442
- variant="secondary"
443
- onPress={prevStep}
444
- accessibilityLabel="Go back"
445
- icon={<Ionicons name="arrow-back" size={16} color={colors.text} />}
446
- style={styles.navButton}
447
- >
448
- {t('common.actions.back') || 'Back'}
449
- </Button>
450
-
451
- <Button
452
- variant="primary"
453
- onPress={nextStep}
454
- disabled={!isContactStepValid()}
455
- accessibilityLabel="Continue to summary"
456
- iconPosition="right"
457
- icon={<Ionicons name="arrow-forward" size={16} color={colors.primaryForeground} />}
458
- style={styles.navButton}
459
- >
460
- {t('common.actions.next') || 'Next'}
461
- </Button>
462
- </View>
463
- </Animated.View>
464
- );
465
-
466
- const renderSummaryRow = (label: string, value: string | undefined) => (
467
- <View className="flex-row gap-space-12">
468
- <Text className="text-body font-body text-text-secondary" style={styles.summaryLabel}>{label}</Text>
469
- <Text className="text-body font-body text-text flex-1">{value}</Text>
470
- </View>
471
- );
472
-
473
- const renderSummaryStep = () => (
474
- <Animated.View style={[styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }]}>
475
- {renderStepHeader(
476
- t('feedback.summary.title') || 'Summary',
477
- t('feedback.summary.subtitle') || 'Please review your feedback before submitting',
478
- )}
479
-
480
- <View className="w-full gap-space-12 p-space-16 rounded-radius-20 bg-fill mb-space-24">
481
- {renderSummaryRow(
482
- t('feedback.summary.type') || 'Type:',
483
- FEEDBACK_TYPES.find(ft => ft.id === feedbackData.type)?.label,
484
- )}
485
- {renderSummaryRow(
486
- t('feedback.summary.category') || 'Category:',
487
- feedbackData.category,
488
- )}
489
- {renderSummaryRow(
490
- t('feedback.summary.priority') || 'Priority:',
491
- PRIORITY_LEVELS.find(p => p.id === feedbackData.priority)?.label,
492
- )}
493
- {renderSummaryRow(
494
- t('feedback.summary.titleField') || 'Title:',
495
- feedbackData.title,
496
- )}
497
- {renderSummaryRow(
498
- t('feedback.summary.contact') || 'Contact:',
499
- feedbackData.contactEmail || user?.email,
500
- )}
501
- </View>
502
-
503
- <Button
504
- variant="primary"
505
- onPress={handleSubmitFeedback}
506
- disabled={feedbackState.status === 'submitting'}
507
- loading={feedbackState.status === 'submitting'}
508
- testID="submit-feedback-button"
509
- accessibilityLabel="Submit feedback"
510
- iconPosition="right"
511
- icon={<Ionicons name="send" size={18} color={colors.primaryForeground} />}
512
- style={styles.fullWidthButton}
513
- >
514
- {t('feedback.actions.submit') || 'Submit Feedback'}
515
- </Button>
516
-
517
- <View className="flex-row mt-space-16 w-full">
518
- <Button
519
- variant="secondary"
520
- onPress={prevStep}
521
- accessibilityLabel="Go back"
522
- icon={<Ionicons name="arrow-back" size={16} color={colors.text} />}
523
- style={styles.navButton}
524
- >
525
- {t('common.actions.back') || 'Back'}
526
- </Button>
527
- </View>
528
- </Animated.View>
529
- );
530
-
531
- const renderSuccessStep = () => (
532
- <Animated.View style={[styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }]}>
533
- <View className="w-full items-center justify-center p-space-32 gap-space-16">
534
- <View className="p-space-24 rounded-radius-max bg-fill-secondary">
535
- <Ionicons name="checkmark-circle" size={48} color={colors.success} />
536
- </View>
537
- <H2 className="text-headerBold font-headerBold text-text text-center">
538
- {t('feedback.success.thanks') || 'Thank You!'}
539
- </H2>
540
- <Text className="text-body font-body text-text-secondary text-center">
541
- {t('feedback.success.message') || "Your feedback has been submitted successfully. We'll review it and get back to you soon."}
542
- </Text>
543
- <Button
544
- variant="primary"
545
- onPress={() => { resetForm(); setCurrentStep(0); }}
546
- accessibilityLabel="Submit another feedback"
547
- style={styles.fullWidthButton}
548
- >
549
- {t('feedback.actions.submitAnother') || 'Submit Another'}
550
- </Button>
551
- </View>
552
- </Animated.View>
553
- );
554
-
555
- const renderCurrentStep = () => {
556
- if (feedbackState.status === 'success') return renderSuccessStep();
557
- switch (currentStep) {
558
- case 0: return renderTypeStep();
559
- case 1: return renderDetailsStep();
560
- case 2: return renderContactStep();
561
- case 3: return renderSummaryStep();
562
- default: return renderTypeStep();
563
- }
564
- };
565
-
566
- return (
567
- <>
568
-
569
- <View className="px-screen-margin pt-space-24 pb-space-32">
570
- {feedbackState.status !== 'success' ? renderProgress() : null}
571
- {renderCurrentStep()}
572
- </View>
573
- </>
574
- );
68
+ const { user, oxyServices } = useOxy();
69
+ const normalizedTheme = normalizeTheme(theme);
70
+ const bloomTheme = useTheme();
71
+ const colors = bloomTheme.colors;
72
+ const { t } = useI18n();
73
+
74
+ useSurfaceHeader({ title: t("feedback.title") || "Feedback" });
75
+
76
+ const {
77
+ feedbackData,
78
+ feedbackState,
79
+ setFeedbackState,
80
+ updateField,
81
+ resetForm,
82
+ } = useFeedbackForm();
83
+
84
+ const [currentStep, setCurrentStep] = useState(0);
85
+ const [, setErrorMessage] = useState("");
86
+
87
+ const fadeAnim = useRef(new Animated.Value(1)).current;
88
+ const slideAnim = useRef(new Animated.Value(0)).current;
89
+
90
+ /** Theme-role color for each feedback type pill. */
91
+ const typeColor = useCallback(
92
+ (id: FeedbackData["type"]): string => {
93
+ switch (id) {
94
+ case "bug":
95
+ return colors.negative;
96
+ case "feature":
97
+ return colors.info;
98
+ case "general":
99
+ return colors.success;
100
+ case "support":
101
+ return colors.warning;
102
+ default:
103
+ return colors.primary;
104
+ }
105
+ },
106
+ [colors],
107
+ );
108
+
109
+ /** Theme-role color for each priority level. */
110
+ const priorityColor = useCallback(
111
+ (id: FeedbackData["priority"]): string => {
112
+ switch (id) {
113
+ case "low":
114
+ return colors.success;
115
+ case "medium":
116
+ return colors.warning;
117
+ case "high":
118
+ return colors.negative;
119
+ case "critical":
120
+ return colors.negative;
121
+ default:
122
+ return colors.primary;
123
+ }
124
+ },
125
+ [colors],
126
+ );
127
+
128
+ const animateTransition = useCallback(
129
+ (nextStep: number) => {
130
+ Animated.timing(fadeAnim, {
131
+ toValue: 0,
132
+ duration: STEP_FADE_OUT_MS,
133
+ useNativeDriver: Platform.OS !== "web",
134
+ }).start(() => {
135
+ setCurrentStep(nextStep);
136
+ slideAnim.setValue(STEP_SLIDE_FROM);
137
+
138
+ Animated.parallel([
139
+ Animated.timing(fadeAnim, {
140
+ toValue: 1,
141
+ duration: STEP_FADE_IN_MS,
142
+ useNativeDriver: Platform.OS !== "web",
143
+ }),
144
+ Animated.timing(slideAnim, {
145
+ toValue: 0,
146
+ duration: STEP_SLIDE_IN_MS,
147
+ useNativeDriver: Platform.OS !== "web",
148
+ }),
149
+ ]).start();
150
+ });
151
+ },
152
+ [fadeAnim, slideAnim],
153
+ );
154
+
155
+ const nextStep = useCallback(() => {
156
+ if (currentStep < LAST_STEP_INDEX) {
157
+ animateTransition(currentStep + 1);
158
+ }
159
+ }, [currentStep, animateTransition]);
160
+
161
+ const prevStep = useCallback(() => {
162
+ if (currentStep > 0) {
163
+ animateTransition(currentStep - 1);
164
+ }
165
+ }, [currentStep, animateTransition]);
166
+
167
+ const isTypeStepValid = useCallback(() => {
168
+ return Boolean(feedbackData.type && feedbackData.category);
169
+ }, [feedbackData.type, feedbackData.category]);
170
+
171
+ const isDetailsStepValid = useCallback(() => {
172
+ return Boolean(
173
+ feedbackData.title.trim() && feedbackData.description.trim(),
174
+ );
175
+ }, [feedbackData.title, feedbackData.description]);
176
+
177
+ const isContactStepValid = useCallback(() => {
178
+ return Boolean(feedbackData.contactEmail.trim() || user?.email);
179
+ }, [feedbackData.contactEmail, user?.email]);
180
+
181
+ const handleSubmitFeedback = useCallback(async () => {
182
+ if (!isTypeStepValid() || !isDetailsStepValid() || !isContactStepValid()) {
183
+ toast.error(
184
+ t("feedback.toasts.fillRequired") ||
185
+ "Please fill in all required fields",
186
+ );
187
+ return;
188
+ }
189
+
190
+ try {
191
+ setFeedbackState({ status: "submitting", message: "" });
192
+ setErrorMessage("");
193
+
194
+ const feedbackPayload = {
195
+ type: feedbackData.type,
196
+ title: feedbackData.title,
197
+ description: feedbackData.description,
198
+ priority: feedbackData.priority,
199
+ category: feedbackData.category,
200
+ contactEmail: feedbackData.contactEmail || user?.email,
201
+ systemInfo: feedbackData.systemInfo
202
+ ? {
203
+ platform: Platform.OS,
204
+ version: Platform.Version?.toString() || "Unknown",
205
+ appVersion: packageInfo.version,
206
+ userId: user?.id,
207
+ username: user?.username,
208
+ timestamp: new Date().toISOString(),
209
+ }
210
+ : undefined,
211
+ };
212
+
213
+ await oxyServices.submitFeedback(feedbackPayload);
214
+
215
+ setFeedbackState({
216
+ status: "success",
217
+ message:
218
+ t("feedback.toasts.submitSuccess") ||
219
+ "Feedback submitted successfully!",
220
+ });
221
+ toast.success(
222
+ t("feedback.toasts.thanks") || "Thank you for your feedback!",
223
+ );
224
+
225
+ setTimeout(() => {
226
+ resetForm();
227
+ setCurrentStep(0);
228
+ }, SUCCESS_RESET_DELAY_MS);
229
+ } catch (error: unknown) {
230
+ const message =
231
+ (error instanceof Error ? error.message : null) ||
232
+ t("feedback.toasts.submitFailed") ||
233
+ "Failed to submit feedback";
234
+ setFeedbackState({ status: "error", message });
235
+ toast.error(message);
236
+ }
237
+ }, [
238
+ feedbackData,
239
+ user,
240
+ oxyServices,
241
+ isTypeStepValid,
242
+ isDetailsStepValid,
243
+ isContactStepValid,
244
+ resetForm,
245
+ setFeedbackState,
246
+ t,
247
+ ]);
248
+
249
+ const feedbackTypeData = useMemo(
250
+ () =>
251
+ FEEDBACK_TYPES.map((type) => ({
252
+ ...type,
253
+ isSelected: feedbackData.type === type.id,
254
+ })),
255
+ [feedbackData.type],
256
+ );
257
+
258
+ const categoryData = useMemo(
259
+ () =>
260
+ feedbackData.type
261
+ ? (CATEGORIES[feedbackData.type] || []).map((cat) => ({
262
+ name: cat,
263
+ isSelected: feedbackData.category === cat,
264
+ }))
265
+ : [],
266
+ [feedbackData.type, feedbackData.category],
267
+ );
268
+
269
+ const priorityData = useMemo(
270
+ () =>
271
+ PRIORITY_LEVELS.map((p) => ({
272
+ ...p,
273
+ isSelected: feedbackData.priority === p.id,
274
+ })),
275
+ [feedbackData.priority],
276
+ );
277
+
278
+ const renderProgress = () => (
279
+ <View
280
+ className="flex-row justify-center gap-space-8 pt-space-8 pb-space-16"
281
+ accessibilityRole="progressbar"
282
+ accessibilityLabel={`Step ${currentStep + 1} of ${TOTAL_STEPS}`}
283
+ >
284
+ {Array.from({ length: TOTAL_STEPS }, (_, index) => (
285
+ <View
286
+ key={index}
287
+ className={
288
+ currentStep === index
289
+ ? "h-space-8 rounded-radius-max bg-fill-brand"
290
+ : "h-space-8 rounded-radius-max bg-fill-secondary"
291
+ }
292
+ style={
293
+ currentStep === index
294
+ ? styles.progressDotActive
295
+ : styles.progressDot
296
+ }
297
+ />
298
+ ))}
299
+ </View>
300
+ );
301
+
302
+ const renderStepHeader = (title: string, subtitle: string) => (
303
+ <View className="w-full gap-space-8 mb-space-24">
304
+ <H2 className="text-headerBold font-headerBold text-text">{title}</H2>
305
+ <Text className="text-body font-body text-text-secondary">
306
+ {subtitle}
307
+ </Text>
308
+ </View>
309
+ );
310
+
311
+ const renderTypeStep = () => (
312
+ <Animated.View
313
+ style={[
314
+ styles.stepContainer,
315
+ { opacity: fadeAnim, transform: [{ translateX: slideAnim }] },
316
+ ]}
317
+ >
318
+ {renderStepHeader(
319
+ t("feedback.type.title") || "What type of feedback?",
320
+ t("feedback.type.subtitle") ||
321
+ "Choose the category that best describes your feedback",
322
+ )}
323
+
324
+ <View className="w-full">
325
+ <SettingsListGroup>
326
+ {feedbackTypeData.map((type) => (
327
+ <SettingsListItem
328
+ key={type.id}
329
+ icon={
330
+ <SettingsIcon
331
+ name={TYPE_ICON_GLYPH[type.id]}
332
+ color={typeColor(type.id)}
333
+ />
334
+ }
335
+ title={type.label}
336
+ description={type.description}
337
+ onPress={() => {
338
+ updateField("type", type.id);
339
+ updateField("category", "");
340
+ }}
341
+ showChevron={false}
342
+ />
343
+ ))}
344
+ </SettingsListGroup>
345
+ </View>
346
+
347
+ {feedbackData.type ? (
348
+ <View className="w-full mt-space-24">
349
+ <Text className="text-caption font-caption text-text-secondary mb-space-8">
350
+ {t("feedback.category.label") || "Category"}
351
+ </Text>
352
+ <View className="w-full">
353
+ <SettingsListGroup>
354
+ {categoryData.map((cat) => (
355
+ <SettingsListItem
356
+ key={cat.name}
357
+ icon={
358
+ <SettingsIcon
359
+ name={cat.isSelected ? "check-circle" : "circle-outline"}
360
+ color={
361
+ cat.isSelected ? colors.primary : colors.textSecondary
362
+ }
363
+ />
364
+ }
365
+ title={cat.name}
366
+ onPress={() => updateField("category", cat.name)}
367
+ showChevron={false}
368
+ />
369
+ ))}
370
+ </SettingsListGroup>
371
+ </View>
372
+ </View>
373
+ ) : null}
374
+
375
+ <View className="flex-row gap-space-8 mt-space-24 w-full">
376
+ <Button
377
+ variant="secondary"
378
+ onPress={goBack}
379
+ accessibilityLabel="Go back"
380
+ icon={<Ionicons name="arrow-back" size={16} color={colors.text} />}
381
+ style={styles.navButton}
382
+ >
383
+ {t("common.actions.back") || "Back"}
384
+ </Button>
385
+
386
+ <Button
387
+ variant="primary"
388
+ onPress={nextStep}
389
+ disabled={!isTypeStepValid()}
390
+ accessibilityLabel="Continue to next step"
391
+ iconPosition="right"
392
+ icon={
393
+ <Ionicons
394
+ name="arrow-forward"
395
+ size={16}
396
+ color={colors.primaryForeground}
397
+ />
398
+ }
399
+ style={styles.navButton}
400
+ >
401
+ {t("common.actions.next") || "Next"}
402
+ </Button>
403
+ </View>
404
+ </Animated.View>
405
+ );
406
+
407
+ const renderDetailsStep = () => (
408
+ <Animated.View
409
+ style={[
410
+ styles.stepContainer,
411
+ { opacity: fadeAnim, transform: [{ translateX: slideAnim }] },
412
+ ]}
413
+ >
414
+ {renderStepHeader(
415
+ t("feedback.details.title") || "Tell us more",
416
+ t("feedback.details.subtitle") || "Provide details about your feedback",
417
+ )}
418
+
419
+ <View className="w-full gap-space-16">
420
+ <TextField>
421
+ <TextFieldInput
422
+ floatingLabel
423
+ label={t("feedback.fields.title.label") || "Title"}
424
+ value={feedbackData.title}
425
+ onChangeText={(text) => {
426
+ updateField("title", text);
427
+ setErrorMessage("");
428
+ }}
429
+ placeholder={
430
+ t("feedback.fields.title.placeholder") ||
431
+ "Brief summary of your feedback"
432
+ }
433
+ testID="feedback-title-input"
434
+ accessibilityLabel="Feedback title"
435
+ accessibilityHint="Enter a brief summary of your feedback"
436
+ />
437
+ </TextField>
438
+
439
+ <TextField>
440
+ <TextFieldInput
441
+ floatingLabel
442
+ multiline
443
+ numberOfLines={6}
444
+ label={t("feedback.fields.description.label") || "Description"}
445
+ value={feedbackData.description}
446
+ onChangeText={(text) => {
447
+ updateField("description", text);
448
+ setErrorMessage("");
449
+ }}
450
+ placeholder={
451
+ t("feedback.fields.description.placeholder") ||
452
+ "Please provide detailed information..."
453
+ }
454
+ testID="feedback-description-input"
455
+ accessibilityLabel="Feedback description"
456
+ accessibilityHint="Provide detailed information about your feedback"
457
+ />
458
+ </TextField>
459
+ </View>
460
+
461
+ <View className="w-full mt-space-24">
462
+ <Text className="text-caption font-caption text-text-secondary mb-space-8">
463
+ {t("feedback.priority.label") || "Priority Level"}
464
+ </Text>
465
+ <View className="w-full">
466
+ <SettingsListGroup>
467
+ {priorityData.map((p) => (
468
+ <SettingsListItem
469
+ key={p.id}
470
+ icon={
471
+ <SettingsIcon
472
+ name={PRIORITY_ICON_GLYPH[p.id]}
473
+ color={priorityColor(p.id)}
474
+ />
475
+ }
476
+ title={p.label}
477
+ onPress={() => updateField("priority", p.id)}
478
+ showChevron={false}
479
+ />
480
+ ))}
481
+ </SettingsListGroup>
482
+ </View>
483
+ </View>
484
+
485
+ <View className="flex-row gap-space-8 mt-space-24 w-full">
486
+ <Button
487
+ variant="secondary"
488
+ onPress={prevStep}
489
+ accessibilityLabel="Go back"
490
+ icon={<Ionicons name="arrow-back" size={16} color={colors.text} />}
491
+ style={styles.navButton}
492
+ >
493
+ {t("common.actions.back") || "Back"}
494
+ </Button>
495
+
496
+ <Button
497
+ variant="primary"
498
+ onPress={nextStep}
499
+ disabled={!isDetailsStepValid()}
500
+ accessibilityLabel="Continue to next step"
501
+ iconPosition="right"
502
+ icon={
503
+ <Ionicons
504
+ name="arrow-forward"
505
+ size={16}
506
+ color={colors.primaryForeground}
507
+ />
508
+ }
509
+ style={styles.navButton}
510
+ >
511
+ {t("common.actions.next") || "Next"}
512
+ </Button>
513
+ </View>
514
+ </Animated.View>
515
+ );
516
+
517
+ const renderContactStep = () => (
518
+ <Animated.View
519
+ style={[
520
+ styles.stepContainer,
521
+ { opacity: fadeAnim, transform: [{ translateX: slideAnim }] },
522
+ ]}
523
+ >
524
+ {renderStepHeader(
525
+ t("feedback.contact.title") || "Contact Information",
526
+ t("feedback.contact.subtitle") || "Help us get back to you",
527
+ )}
528
+
529
+ <View className="w-full">
530
+ <TextField>
531
+ <TextFieldInput
532
+ floatingLabel
533
+ label={t("feedback.fields.email.label") || "Email Address"}
534
+ value={feedbackData.contactEmail}
535
+ onChangeText={(text) => {
536
+ updateField("contactEmail", text);
537
+ setErrorMessage("");
538
+ }}
539
+ placeholder={
540
+ user?.email ||
541
+ t("feedback.fields.email.placeholder") ||
542
+ "Enter your email address"
543
+ }
544
+ keyboardType="email-address"
545
+ autoCapitalize="none"
546
+ autoCorrect={false}
547
+ testID="feedback-email-input"
548
+ accessibilityLabel="Email address"
549
+ accessibilityHint="Enter your email so we can respond"
550
+ />
551
+ </TextField>
552
+ </View>
553
+
554
+ <View className="w-full mt-space-16">
555
+ <SettingsListGroup>
556
+ <SettingsListItem
557
+ icon={<SettingsIcon name="information" color={colors.info} />}
558
+ title={
559
+ t("feedback.contact.includeSystemInfo") ||
560
+ "Include system information to help us better understand your issue"
561
+ }
562
+ rightElement={
563
+ <Switch
564
+ value={feedbackData.systemInfo}
565
+ onValueChange={(value) => updateField("systemInfo", value)}
566
+ testID="feedback-system-info-switch"
567
+ />
568
+ }
569
+ showChevron={false}
570
+ />
571
+ </SettingsListGroup>
572
+ </View>
573
+
574
+ <View className="flex-row gap-space-8 mt-space-24 w-full">
575
+ <Button
576
+ variant="secondary"
577
+ onPress={prevStep}
578
+ accessibilityLabel="Go back"
579
+ icon={<Ionicons name="arrow-back" size={16} color={colors.text} />}
580
+ style={styles.navButton}
581
+ >
582
+ {t("common.actions.back") || "Back"}
583
+ </Button>
584
+
585
+ <Button
586
+ variant="primary"
587
+ onPress={nextStep}
588
+ disabled={!isContactStepValid()}
589
+ accessibilityLabel="Continue to summary"
590
+ iconPosition="right"
591
+ icon={
592
+ <Ionicons
593
+ name="arrow-forward"
594
+ size={16}
595
+ color={colors.primaryForeground}
596
+ />
597
+ }
598
+ style={styles.navButton}
599
+ >
600
+ {t("common.actions.next") || "Next"}
601
+ </Button>
602
+ </View>
603
+ </Animated.View>
604
+ );
605
+
606
+ const renderSummaryRow = (label: string, value: string | undefined) => (
607
+ <View className="flex-row gap-space-12">
608
+ <Text
609
+ className="text-body font-body text-text-secondary"
610
+ style={styles.summaryLabel}
611
+ >
612
+ {label}
613
+ </Text>
614
+ <Text className="text-body font-body text-text flex-1">{value}</Text>
615
+ </View>
616
+ );
617
+
618
+ const renderSummaryStep = () => (
619
+ <Animated.View
620
+ style={[
621
+ styles.stepContainer,
622
+ { opacity: fadeAnim, transform: [{ translateX: slideAnim }] },
623
+ ]}
624
+ >
625
+ {renderStepHeader(
626
+ t("feedback.summary.title") || "Summary",
627
+ t("feedback.summary.subtitle") ||
628
+ "Please review your feedback before submitting",
629
+ )}
630
+
631
+ <View className="w-full gap-space-12 p-space-16 rounded-radius-20 bg-fill mb-space-24">
632
+ {renderSummaryRow(
633
+ t("feedback.summary.type") || "Type:",
634
+ FEEDBACK_TYPES.find((ft) => ft.id === feedbackData.type)?.label,
635
+ )}
636
+ {renderSummaryRow(
637
+ t("feedback.summary.category") || "Category:",
638
+ feedbackData.category,
639
+ )}
640
+ {renderSummaryRow(
641
+ t("feedback.summary.priority") || "Priority:",
642
+ PRIORITY_LEVELS.find((p) => p.id === feedbackData.priority)?.label,
643
+ )}
644
+ {renderSummaryRow(
645
+ t("feedback.summary.titleField") || "Title:",
646
+ feedbackData.title,
647
+ )}
648
+ {renderSummaryRow(
649
+ t("feedback.summary.contact") || "Contact:",
650
+ feedbackData.contactEmail || user?.email,
651
+ )}
652
+ </View>
653
+
654
+ <Button
655
+ variant="primary"
656
+ onPress={handleSubmitFeedback}
657
+ disabled={feedbackState.status === "submitting"}
658
+ loading={feedbackState.status === "submitting"}
659
+ testID="submit-feedback-button"
660
+ accessibilityLabel="Submit feedback"
661
+ iconPosition="right"
662
+ icon={
663
+ <Ionicons name="send" size={18} color={colors.primaryForeground} />
664
+ }
665
+ style={styles.fullWidthButton}
666
+ >
667
+ {t("feedback.actions.submit") || "Submit Feedback"}
668
+ </Button>
669
+
670
+ <View className="flex-row mt-space-16 w-full">
671
+ <Button
672
+ variant="secondary"
673
+ onPress={prevStep}
674
+ accessibilityLabel="Go back"
675
+ icon={<Ionicons name="arrow-back" size={16} color={colors.text} />}
676
+ style={styles.navButton}
677
+ >
678
+ {t("common.actions.back") || "Back"}
679
+ </Button>
680
+ </View>
681
+ </Animated.View>
682
+ );
683
+
684
+ const renderSuccessStep = () => (
685
+ <Animated.View
686
+ style={[
687
+ styles.stepContainer,
688
+ { opacity: fadeAnim, transform: [{ translateX: slideAnim }] },
689
+ ]}
690
+ >
691
+ <View className="w-full items-center justify-center p-space-32 gap-space-16">
692
+ <View className="p-space-24 rounded-radius-max bg-fill-secondary">
693
+ <Ionicons name="checkmark-circle" size={48} color={colors.success} />
694
+ </View>
695
+ <H2 className="text-headerBold font-headerBold text-text text-center">
696
+ {t("feedback.success.thanks") || "Thank You!"}
697
+ </H2>
698
+ <Text className="text-body font-body text-text-secondary text-center">
699
+ {t("feedback.success.message") ||
700
+ "Your feedback has been submitted successfully. We'll review it and get back to you soon."}
701
+ </Text>
702
+ <Button
703
+ variant="primary"
704
+ onPress={() => {
705
+ resetForm();
706
+ setCurrentStep(0);
707
+ }}
708
+ accessibilityLabel="Submit another feedback"
709
+ style={styles.fullWidthButton}
710
+ >
711
+ {t("feedback.actions.submitAnother") || "Submit Another"}
712
+ </Button>
713
+ </View>
714
+ </Animated.View>
715
+ );
716
+
717
+ const renderCurrentStep = () => {
718
+ if (feedbackState.status === "success") return renderSuccessStep();
719
+ switch (currentStep) {
720
+ case 0:
721
+ return renderTypeStep();
722
+ case 1:
723
+ return renderDetailsStep();
724
+ case 2:
725
+ return renderContactStep();
726
+ case 3:
727
+ return renderSummaryStep();
728
+ default:
729
+ return renderTypeStep();
730
+ }
731
+ };
732
+
733
+ return (
734
+ <>
735
+ <View className="px-screen-margin pt-space-24 pb-space-32">
736
+ {feedbackState.status !== "success" ? renderProgress() : null}
737
+ {renderCurrentStep()}
738
+ </View>
739
+ </>
740
+ );
575
741
  };
576
742
 
577
743
  const styles = StyleSheet.create({
578
- stepContainer: {
579
- flex: 1,
580
- alignItems: 'flex-start',
581
- },
582
- navButton: {
583
- flex: 1,
584
- },
585
- fullWidthButton: {
586
- width: '100%',
587
- },
588
- summaryLabel: {
589
- width: 90,
590
- },
591
- progressDot: {
592
- width: 8,
593
- },
594
- progressDotActive: {
595
- width: 24,
596
- },
744
+ stepContainer: {
745
+ flex: 1,
746
+ alignItems: "flex-start",
747
+ },
748
+ navButton: {
749
+ flex: 1,
750
+ },
751
+ fullWidthButton: {
752
+ width: "100%",
753
+ },
754
+ summaryLabel: {
755
+ width: 90,
756
+ },
757
+ progressDot: {
758
+ width: 8,
759
+ },
760
+ progressDotActive: {
761
+ width: 24,
762
+ },
597
763
  });
598
764
 
599
765
  export default FeedbackScreen;