@oxyhq/services 5.11.8 → 5.11.10

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 (199) hide show
  1. package/lib/commonjs/core/OxyServices.js +104 -10
  2. package/lib/commonjs/core/OxyServices.js.map +1 -1
  3. package/lib/commonjs/ui/components/AnimationExample.js +213 -0
  4. package/lib/commonjs/ui/components/AnimationExample.js.map +1 -0
  5. package/lib/commonjs/ui/components/FollowButton.js +58 -47
  6. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  7. package/lib/commonjs/ui/components/GroupedItem.js +2 -1
  8. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  9. package/lib/commonjs/ui/components/GroupedSection.js +3 -0
  10. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  11. package/lib/commonjs/ui/components/Header.js +25 -11
  12. package/lib/commonjs/ui/components/Header.js.map +1 -1
  13. package/lib/commonjs/ui/components/OxyProvider.js +69 -33
  14. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  15. package/lib/commonjs/ui/components/ProfileCard.js +5 -1
  16. package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
  17. package/lib/commonjs/ui/components/index.js +0 -7
  18. package/lib/commonjs/ui/components/index.js.map +1 -1
  19. package/lib/commonjs/ui/components/internal/TextField.js +8 -4
  20. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  21. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +161 -0
  22. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  23. package/lib/commonjs/ui/context/OxyContext.js +97 -38
  24. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  25. package/lib/commonjs/ui/hooks/useFollow.types.js +2 -0
  26. package/lib/commonjs/ui/hooks/useFollow.types.js.map +1 -0
  27. package/lib/commonjs/ui/navigation/OxyRouter.js +10 -0
  28. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  29. package/lib/commonjs/ui/screens/AccountCenterScreen.js +26 -14
  30. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  31. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +3 -3
  32. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  33. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +64 -15
  34. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  35. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +4 -4
  36. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  37. package/lib/commonjs/ui/screens/FeedbackScreen.js +72 -75
  38. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
  39. package/lib/commonjs/ui/screens/FileManagementScreen.js +286 -126
  40. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  41. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +322 -0
  42. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -0
  43. package/lib/commonjs/ui/screens/ProfileScreen.js +1 -1
  44. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  45. package/lib/commonjs/ui/screens/SessionManagementScreen.js +176 -174
  46. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  47. package/lib/commonjs/ui/screens/SignInScreen.js +43 -52
  48. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  49. package/lib/commonjs/ui/screens/SignUpScreen.js +6 -4
  50. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  51. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +386 -0
  52. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  53. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +25 -15
  54. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  55. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +16 -9
  56. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  57. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
  58. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  59. package/lib/commonjs/ui/styles/authStyles.js +1 -1
  60. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  61. package/lib/module/core/OxyServices.js +103 -9
  62. package/lib/module/core/OxyServices.js.map +1 -1
  63. package/lib/module/ui/components/AnimationExample.js +209 -0
  64. package/lib/module/ui/components/AnimationExample.js.map +1 -0
  65. package/lib/module/ui/components/FollowButton.js +58 -47
  66. package/lib/module/ui/components/FollowButton.js.map +1 -1
  67. package/lib/module/ui/components/GroupedItem.js +2 -1
  68. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  69. package/lib/module/ui/components/GroupedSection.js +3 -0
  70. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  71. package/lib/module/ui/components/Header.js +25 -11
  72. package/lib/module/ui/components/Header.js.map +1 -1
  73. package/lib/module/ui/components/OxyProvider.js +70 -34
  74. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  75. package/lib/module/ui/components/ProfileCard.js +5 -1
  76. package/lib/module/ui/components/ProfileCard.js.map +1 -1
  77. package/lib/module/ui/components/index.js +0 -1
  78. package/lib/module/ui/components/index.js.map +1 -1
  79. package/lib/module/ui/components/internal/TextField.js +8 -4
  80. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  81. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +156 -0
  82. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  83. package/lib/module/ui/context/OxyContext.js +97 -39
  84. package/lib/module/ui/context/OxyContext.js.map +1 -1
  85. package/lib/module/ui/hooks/useFollow.types.js +2 -0
  86. package/lib/module/ui/hooks/useFollow.types.js.map +1 -0
  87. package/lib/module/ui/navigation/OxyRouter.js +10 -0
  88. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  89. package/lib/module/ui/screens/AccountCenterScreen.js +12 -1
  90. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  91. package/lib/module/ui/screens/AccountOverviewScreen.js +3 -3
  92. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  93. package/lib/module/ui/screens/AccountSettingsScreen.js +64 -15
  94. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  95. package/lib/module/ui/screens/AccountSwitcherScreen.js +4 -4
  96. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  97. package/lib/module/ui/screens/FeedbackScreen.js +72 -75
  98. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
  99. package/lib/module/ui/screens/FileManagementScreen.js +285 -125
  100. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  101. package/lib/module/ui/screens/LanguageSelectorScreen.js +319 -0
  102. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -0
  103. package/lib/module/ui/screens/ProfileScreen.js +1 -1
  104. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  105. package/lib/module/ui/screens/SessionManagementScreen.js +177 -175
  106. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  107. package/lib/module/ui/screens/SignInScreen.js +44 -53
  108. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  109. package/lib/module/ui/screens/SignUpScreen.js +6 -4
  110. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  111. package/lib/module/ui/screens/WelcomeNewUserScreen.js +382 -0
  112. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  113. package/lib/module/ui/screens/internal/SignInPasswordStep.js +23 -14
  114. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  115. package/lib/module/ui/screens/internal/SignInUsernameStep.js +15 -9
  116. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  117. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
  118. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  119. package/lib/module/ui/styles/authStyles.js +1 -1
  120. package/lib/module/ui/styles/authStyles.js.map +1 -1
  121. package/lib/typescript/core/OxyServices.d.ts +95 -4
  122. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  123. package/lib/typescript/models/interfaces.d.ts +1 -5
  124. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  125. package/lib/typescript/models/session.d.ts +1 -4
  126. package/lib/typescript/models/session.d.ts.map +1 -1
  127. package/lib/typescript/ui/components/AnimationExample.d.ts +4 -0
  128. package/lib/typescript/ui/components/AnimationExample.d.ts.map +1 -0
  129. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  130. package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
  131. package/lib/typescript/ui/components/Header.d.ts +9 -0
  132. package/lib/typescript/ui/components/Header.d.ts.map +1 -1
  133. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  134. package/lib/typescript/ui/components/ProfileCard.d.ts +1 -3
  135. package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
  136. package/lib/typescript/ui/components/index.d.ts +0 -1
  137. package/lib/typescript/ui/components/index.d.ts.map +1 -1
  138. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  139. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts +27 -0
  140. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -0
  141. package/lib/typescript/ui/context/OxyContext.d.ts +6 -2
  142. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  143. package/lib/typescript/ui/hooks/useFollow.types.d.ts +33 -0
  144. package/lib/typescript/ui/hooks/useFollow.types.d.ts.map +1 -0
  145. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  146. package/lib/typescript/ui/navigation/types.d.ts +5 -0
  147. package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
  148. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  149. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/FileManagementScreen.d.ts +18 -1
  152. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  153. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +7 -0
  154. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -0
  155. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
  156. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  157. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  158. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  159. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts +13 -0
  160. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -0
  161. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +5 -5
  162. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  163. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +4 -4
  164. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
  165. package/lib/typescript/ui/styles/authStyles.d.ts +1 -1
  166. package/package.json +10 -2
  167. package/src/core/OxyServices.ts +107 -13
  168. package/src/models/interfaces.ts +2 -5
  169. package/src/models/session.ts +1 -4
  170. package/src/ui/components/AnimationExample.tsx +194 -0
  171. package/src/ui/components/FollowButton.tsx +65 -45
  172. package/src/ui/components/GroupedItem.tsx +1 -0
  173. package/src/ui/components/GroupedSection.tsx +1 -1
  174. package/src/ui/components/Header.tsx +36 -12
  175. package/src/ui/components/OxyProvider.tsx +66 -32
  176. package/src/ui/components/ProfileCard.tsx +6 -8
  177. package/src/ui/components/index.ts +0 -1
  178. package/src/ui/components/internal/TextField.tsx +12 -6
  179. package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -0
  180. package/src/ui/context/OxyContext.tsx +84 -54
  181. package/src/ui/hooks/useFollow.types.ts +33 -0
  182. package/src/ui/navigation/OxyRouter.tsx +10 -0
  183. package/src/ui/navigation/types.ts +6 -0
  184. package/src/ui/screens/AccountCenterScreen.tsx +13 -7
  185. package/src/ui/screens/AccountOverviewScreen.tsx +3 -3
  186. package/src/ui/screens/AccountSettingsScreen.tsx +65 -13
  187. package/src/ui/screens/AccountSwitcherScreen.tsx +4 -4
  188. package/src/ui/screens/FeedbackScreen.tsx +57 -80
  189. package/src/ui/screens/FileManagementScreen.tsx +278 -175
  190. package/src/ui/screens/LanguageSelectorScreen.tsx +322 -0
  191. package/src/ui/screens/ProfileScreen.tsx +6 -1
  192. package/src/ui/screens/SessionManagementScreen.tsx +148 -151
  193. package/src/ui/screens/SignInScreen.tsx +43 -62
  194. package/src/ui/screens/SignUpScreen.tsx +3 -5
  195. package/src/ui/screens/WelcomeNewUserScreen.tsx +272 -0
  196. package/src/ui/screens/internal/SignInPasswordStep.tsx +28 -13
  197. package/src/ui/screens/internal/SignInUsernameStep.tsx +21 -11
  198. package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -1
  199. package/src/ui/styles/authStyles.ts +1 -1
@@ -15,6 +15,7 @@ import {
15
15
  import type { BaseScreenProps } from '../navigation/types';
16
16
  import { useOxy } from '../context/OxyContext';
17
17
  import Avatar from '../components/Avatar';
18
+ import type { FileMetadata } from '../../models/interfaces';
18
19
  import OxyIcon from '../components/icon/OxyIcon';
19
20
  import { Ionicons } from '@expo/vector-icons';
20
21
  import { toast } from '../../lib/sonner';
@@ -29,7 +30,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
29
30
  goBack,
30
31
  navigate,
31
32
  }) => {
32
- const { user, oxyServices, isLoading: authLoading, isAuthenticated } = useOxy();
33
+ const { user, oxyServices, isLoading: authLoading, isAuthenticated, showBottomSheet } = useOxy();
33
34
  const updateUser = useAuthStore((state) => state.updateUser);
34
35
  const [isLoading, setIsLoading] = useState(false);
35
36
  const [isSaving, setIsSaving] = useState(false);
@@ -45,7 +46,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
45
46
  const [bio, setBio] = useState('');
46
47
  const [location, setLocation] = useState('');
47
48
  const [links, setLinks] = useState<string[]>([]);
48
- const [avatarUrl, setAvatarUrl] = useState('');
49
+ const [avatarFileId, setAvatarFileId] = useState('');
49
50
 
50
51
  // Editing states
51
52
  const [editingField, setEditingField] = useState<string | null>(null);
@@ -173,7 +174,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
173
174
  setLinks([]);
174
175
  setTempLinksWithMetadata([]);
175
176
  }
176
- setAvatarUrl(user.avatar?.url || '');
177
+ setAvatarFileId(typeof user.avatar === 'string' ? user.avatar : '');
177
178
  }
178
179
  }, [user]);
179
180
 
@@ -203,8 +204,8 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
203
204
  }
204
205
 
205
206
  // Handle avatar
206
- if (avatarUrl !== user.avatar?.url) {
207
- updates.avatar = { url: avatarUrl };
207
+ if (avatarFileId !== (typeof user.avatar === 'string' ? user.avatar : '')) {
208
+ updates.avatar = avatarFileId;
208
209
  }
209
210
 
210
211
  await updateUser(updates, oxyServices);
@@ -225,14 +226,55 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
225
226
  }
226
227
  };
227
228
 
228
- const handleAvatarUpdate = () => {
229
- // Always use confirmAction for both web and native
229
+ const handleAvatarRemove = () => {
230
230
  confirmAction('Remove your profile picture?', () => {
231
- setAvatarUrl('');
231
+ setAvatarFileId('');
232
232
  toast.success('Avatar removed');
233
233
  });
234
234
  };
235
235
 
236
+ const openAvatarPicker = useCallback(() => {
237
+ showBottomSheet?.({
238
+ screen: 'FileManagement',
239
+ props: {
240
+ selectMode: true,
241
+ multiSelect: false,
242
+ afterSelect: 'back',
243
+ onSelect: (file: FileMetadata) => {
244
+ if (!file.contentType.startsWith('image/')) {
245
+ toast.error('Please select an image file');
246
+ return;
247
+ }
248
+ // If already selected, do nothing
249
+ if (file.id === avatarFileId) {
250
+ toast.info?.('Avatar unchanged');
251
+ return;
252
+ }
253
+ setAvatarFileId(file.id);
254
+ toast.success('Avatar selected');
255
+ // Auto-save avatar immediately (does not close edit profile screen)
256
+ (async () => {
257
+ try {
258
+ console.log('[AccountSettings] Auto-saving avatar', file.id);
259
+ setIsSaving(true);
260
+ await updateUser({ avatar: file.id }, oxyServices);
261
+ // Force refresh current user cache (updateUser already does a fetch with force=true internally)
262
+ // Extra safeguard: ensure avatarFileId reflects saved id (already set) and trigger any dependent UI.
263
+ toast.success('Avatar updated');
264
+ } catch (e: any) {
265
+ console.error('[AccountSettings] Failed to auto-save avatar', e);
266
+ toast.error(e.message || 'Failed to update avatar');
267
+ } finally {
268
+ setIsSaving(false);
269
+ }
270
+ })();
271
+ },
272
+ // Limit to images client-side by using photos view if later exposed
273
+ disabledMimeTypes: ['video/', 'audio/', 'application/pdf']
274
+ }
275
+ });
276
+ }, [showBottomSheet, oxyServices, avatarFileId, updateUser]);
277
+
236
278
  const startEditing = (type: string, currentValue: string) => {
237
279
  switch (type) {
238
280
  case 'displayName':
@@ -871,19 +913,29 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
871
913
  {/* Profile Picture Section */}
872
914
  <View style={styles.section}>
873
915
  <Text style={styles.sectionTitle}>Profile Picture</Text>
874
-
875
916
  <GroupedSection
876
917
  items={[
877
918
  {
878
919
  id: 'profile-photo',
879
- icon: avatarUrl ? undefined : 'person',
920
+ icon: avatarFileId ? undefined : 'person',
880
921
  iconColor: '#007AFF',
881
- image: avatarUrl || undefined,
922
+ // Use download URL (includes token + fallback) instead of raw stream for reliability
923
+ image: avatarFileId ? oxyServices.getFileDownloadUrl(avatarFileId, 'thumb') : undefined,
882
924
  imageSize: 40,
883
925
  title: 'Profile Photo',
884
- subtitle: avatarUrl ? 'Tap to change your profile picture' : 'Tap to add a profile picture',
885
- onPress: handleAvatarUpdate,
926
+ subtitle: avatarFileId ? 'Tap to change your profile picture' : 'Tap to add a profile picture',
927
+ onPress: openAvatarPicker,
886
928
  },
929
+ ...(avatarFileId ? [
930
+ {
931
+ id: 'remove-profile-photo',
932
+ icon: 'trash',
933
+ iconColor: '#FF3B30',
934
+ title: 'Remove Photo',
935
+ subtitle: 'Delete current profile picture',
936
+ onPress: handleAvatarRemove,
937
+ }
938
+ ] : []),
887
939
  ]}
888
940
  theme={theme}
889
941
  />
@@ -294,8 +294,8 @@ const ModernAccountSwitcherScreen: React.FC<BaseScreenProps> = ({
294
294
 
295
295
  <View style={[styles.settingItem, styles.firstSettingItem, styles.lastSettingItem, styles.currentAccountCard]}>
296
296
  <View style={styles.userIcon}>
297
- {user.avatar?.url ? (
298
- <Image source={{ uri: user.avatar.url }} style={styles.accountAvatarImage} />
297
+ {typeof user.avatar === 'string' && user.avatar ? (
298
+ <Image source={{ uri: oxyServices.getFileDownloadUrl(user.avatar as string, 'thumb') }} style={styles.accountAvatarImage} />
299
299
  ) : (
300
300
  <View style={styles.accountAvatarFallback}>
301
301
  <Text style={styles.accountAvatarText}>
@@ -356,8 +356,8 @@ const ModernAccountSwitcherScreen: React.FC<BaseScreenProps> = ({
356
356
  <View style={styles.accountAvatarFallback}>
357
357
  <ActivityIndicator size="small" color="#007AFF" />
358
358
  </View>
359
- ) : userProfile?.avatar?.url ? (
360
- <Image source={{ uri: userProfile.avatar.url }} style={styles.accountAvatarImage} />
359
+ ) : (typeof userProfile?.avatar === 'string' && userProfile.avatar) ? (
360
+ <Image source={{ uri: oxyServices.getFileDownloadUrl(userProfile.avatar as string, 'thumb') }} style={styles.accountAvatarImage} />
361
361
  ) : (
362
362
  <View style={styles.accountAvatarFallback}>
363
363
  <Text style={styles.accountAvatarText}>
@@ -20,6 +20,7 @@ import { useThemeColors } from '../styles';
20
20
  import { Ionicons } from '@expo/vector-icons';
21
21
  import { toast } from '../../lib/sonner';
22
22
  import { packageInfo } from '../../constants/version';
23
+ import { GroupedSection } from '../components';
23
24
 
24
25
  // Types for better type safety
25
26
  interface FeedbackData {
@@ -64,6 +65,10 @@ const createStyles = (colors: any, theme: string) => StyleSheet.create({
64
65
  container: {
65
66
  flex: 1,
66
67
  },
68
+ fullBleed: {
69
+ width: '100%',
70
+ alignSelf: 'stretch',
71
+ },
67
72
  scrollContent: {
68
73
  flexGrow: 1,
69
74
  paddingHorizontal: 24,
@@ -622,6 +627,42 @@ const FeedbackScreen: React.FC<BaseScreenProps> = ({
622
627
  }, [feedbackData, user, isTypeStepValid, isDetailsStepValid, isContactStepValid, resetForm]);
623
628
 
624
629
  // Step components
630
+ // Memoized grouped section items
631
+ const feedbackTypeItems = useMemo(() => FEEDBACK_TYPES.map(type => ({
632
+ id: type.id,
633
+ icon: type.icon,
634
+ iconColor: type.color,
635
+ title: type.label,
636
+ subtitle: type.description,
637
+ onPress: () => { updateField('type', type.id); updateField('category', ''); },
638
+ selected: feedbackData.type === type.id,
639
+ showChevron: false,
640
+ multiRow: true,
641
+ dense: true,
642
+ })), [feedbackData.type, updateField]);
643
+
644
+ const categoryItems = useMemo(() => (feedbackData.type ? (CATEGORIES[feedbackData.type as keyof typeof CATEGORIES] || []).map(cat => ({
645
+ id: cat,
646
+ icon: feedbackData.category === cat ? 'checkmark-circle' : 'ellipse-outline',
647
+ iconColor: feedbackData.category === cat ? colors.primary : colors.secondaryText,
648
+ title: cat,
649
+ onPress: () => updateField('category', cat),
650
+ selected: feedbackData.category === cat,
651
+ showChevron: false,
652
+ dense: true,
653
+ })) : []), [feedbackData.type, feedbackData.category, colors.primary, colors.secondaryText, updateField]);
654
+
655
+ const priorityItems = useMemo(() => PRIORITY_LEVELS.map(p => ({
656
+ id: p.id,
657
+ icon: p.icon,
658
+ iconColor: p.color,
659
+ title: p.label,
660
+ onPress: () => updateField('priority', p.id),
661
+ selected: feedbackData.priority === p.id,
662
+ showChevron: false,
663
+ dense: true,
664
+ })), [feedbackData.priority, updateField]);
665
+
625
666
  const renderTypeStep = useCallback(() => (
626
667
  <Animated.View style={[
627
668
  styles.stepContainer,
@@ -635,63 +676,16 @@ const FeedbackScreen: React.FC<BaseScreenProps> = ({
635
676
  Choose the category that best describes your feedback
636
677
  </Text>
637
678
  </View>
638
-
639
- <View style={styles.typeGrid}>
640
- {FEEDBACK_TYPES.map((type) => (
641
- <TouchableOpacity
642
- key={type.id}
643
- style={[
644
- styles.typeCard,
645
- {
646
- borderColor: feedbackData.type === type.id ? type.color : colors.border,
647
- backgroundColor: feedbackData.type === type.id ? type.color + '10' : colors.inputBackground,
648
- }
649
- ]}
650
- onPress={() => {
651
- updateField('type', type.id);
652
- updateField('category', '');
653
- }}
654
- >
655
- <View style={[styles.typeIcon, { backgroundColor: type.color + '20', padding: 12, borderRadius: 12 }]}>
656
- <Ionicons name={type.icon as any} size={24} color={type.color} />
657
- </View>
658
- <Text style={[styles.typeLabel, { color: colors.text }]}>
659
- {type.label}
660
- </Text>
661
- <Text style={[styles.typeDescription, { color: colors.secondaryText }]}>
662
- {type.description}
663
- </Text>
664
- </TouchableOpacity>
665
- ))}
679
+ <View style={styles.fullBleed}>
680
+ <GroupedSection items={feedbackTypeItems} theme={theme as 'light' | 'dark'} />
666
681
  </View>
667
682
 
668
683
  {feedbackData.type && (
669
684
  <View style={styles.categoryContainer}>
670
- <Text style={[styles.modernLabel, { color: colors.secondaryText, marginBottom: 12 }]}>
671
- Category
672
- </Text>
673
- {CATEGORIES[feedbackData.type as keyof typeof CATEGORIES]?.map((category) => (
674
- <TouchableOpacity
675
- key={category}
676
- style={[
677
- styles.categoryButton,
678
- {
679
- borderColor: feedbackData.category === category ? colors.primary : colors.border,
680
- backgroundColor: feedbackData.category === category ? colors.primary + '10' : colors.inputBackground,
681
- }
682
- ]}
683
- onPress={() => updateField('category', category)}
684
- >
685
- <Ionicons
686
- name={feedbackData.category === category ? 'checkmark-circle' : 'ellipse-outline'}
687
- size={20}
688
- color={feedbackData.category === category ? colors.primary : colors.secondaryText}
689
- />
690
- <Text style={[styles.categoryText, { color: colors.text }]}>
691
- {category}
692
- </Text>
693
- </TouchableOpacity>
694
- ))}
685
+ <Text style={[styles.modernLabel, { color: colors.secondaryText, marginBottom: 8 }]}>Category</Text>
686
+ <View style={styles.fullBleed}>
687
+ <GroupedSection items={categoryItems} theme={theme as 'light' | 'dark'} />
688
+ </View>
695
689
  </View>
696
690
  )}
697
691
 
@@ -730,7 +724,7 @@ const FeedbackScreen: React.FC<BaseScreenProps> = ({
730
724
  </TouchableOpacity>
731
725
  </View>
732
726
  </Animated.View>
733
- ), [fadeAnim, slideAnim, colors, feedbackData, updateField, goBack, nextStep, isTypeStepValid, styles]);
727
+ ), [fadeAnim, slideAnim, colors, feedbackData, feedbackTypeItems, categoryItems, updateField, goBack, nextStep, isTypeStepValid, styles, theme]);
734
728
 
735
729
  const renderDetailsStep = useCallback(() => (
736
730
  <Animated.View style={[
@@ -776,28 +770,11 @@ const FeedbackScreen: React.FC<BaseScreenProps> = ({
776
770
  styles={styles}
777
771
  />
778
772
 
779
- <View style={styles.priorityContainer}>
780
- <Text style={[styles.modernLabel, { color: colors.secondaryText, marginBottom: 12 }]}>
781
- Priority Level
782
- </Text>
783
- {PRIORITY_LEVELS.map((priority) => (
784
- <TouchableOpacity
785
- key={priority.id}
786
- style={[
787
- styles.priorityButton,
788
- {
789
- borderColor: feedbackData.priority === priority.id ? priority.color : colors.border,
790
- backgroundColor: feedbackData.priority === priority.id ? priority.color + '10' : colors.inputBackground,
791
- }
792
- ]}
793
- onPress={() => updateField('priority', priority.id)}
794
- >
795
- <Ionicons name={priority.icon as any} size={20} color={priority.color} />
796
- <Text style={[styles.priorityLabel, { color: colors.text }]}>
797
- {priority.label}
798
- </Text>
799
- </TouchableOpacity>
800
- ))}
773
+ <View style={{ marginBottom: 24 }}>
774
+ <Text style={[styles.modernLabel, { color: colors.secondaryText, marginBottom: 8 }]}>Priority Level</Text>
775
+ <View style={styles.fullBleed}>
776
+ <GroupedSection items={priorityItems} theme={theme as 'light' | 'dark'} />
777
+ </View>
801
778
  </View>
802
779
 
803
780
  <View style={styles.navigationButtons}>
@@ -826,7 +803,7 @@ const FeedbackScreen: React.FC<BaseScreenProps> = ({
826
803
  </TouchableOpacity>
827
804
  </View>
828
805
  </Animated.View>
829
- ), [fadeAnim, slideAnim, colors, feedbackData, updateField, setErrorMessage, prevStep, nextStep, isDetailsStepValid, styles]);
806
+ ), [fadeAnim, slideAnim, colors, feedbackData, updateField, setErrorMessage, prevStep, nextStep, isDetailsStepValid, styles, priorityItems, theme]);
830
807
 
831
808
  const renderContactStep = useCallback(() => (
832
809
  <Animated.View style={[
@@ -1032,16 +1009,16 @@ const FeedbackScreen: React.FC<BaseScreenProps> = ({
1032
1009
 
1033
1010
  return (
1034
1011
  <KeyboardAvoidingView
1035
- style={[styles.container, { backgroundColor: colors.background }]}
1012
+ style={[styles.container, { backgroundColor: theme === 'dark' ? colors.background : '#F7F9FC' }]}
1036
1013
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
1037
1014
  >
1038
1015
  <StatusBar
1039
1016
  barStyle={theme === 'dark' ? 'light-content' : 'dark-content'}
1040
- backgroundColor={colors.background}
1017
+ backgroundColor={theme === 'dark' ? colors.background : '#F7F9FC'}
1041
1018
  />
1042
1019
 
1043
1020
  <ScrollView
1044
- contentContainerStyle={styles.scrollContent}
1021
+ contentContainerStyle={[styles.scrollContent, { backgroundColor: 'transparent' }]}
1045
1022
  showsVerticalScrollIndicator={false}
1046
1023
  keyboardShouldPersistTaps="handled"
1047
1024
  >