create-gufran-expo-app 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/lib/createApp.js +7 -2
  2. package/package.json +1 -1
  3. package/template/App.tsx +118 -0
  4. package/template/ReactotronConfig.js +5 -0
  5. package/template/android/app/build.gradle +184 -0
  6. package/template/android/app/debug.keystore +0 -0
  7. package/template/android/app/google-services.json +29 -0
  8. package/template/android/app/proguard-rules.pro +14 -0
  9. package/template/android/app/src/debug/AndroidManifest.xml +7 -0
  10. package/template/android/app/src/debugOptimized/AndroidManifest.xml +7 -0
  11. package/template/android/app/src/main/AndroidManifest.xml +28 -0
  12. package/template/android/app/src/main/assets/fonts/Outfit-Bold.ttf +0 -0
  13. package/template/android/app/src/main/assets/fonts/Outfit-Light.ttf +0 -0
  14. package/template/android/app/src/main/assets/fonts/Outfit-Medium.ttf +0 -0
  15. package/template/android/app/src/main/assets/fonts/Outfit-Regular.ttf +0 -0
  16. package/template/android/app/src/main/assets/fonts/Outfit-SemiBold.ttf +0 -0
  17. package/template/android/app/src/main/java/com/club/yakka/MainActivity.kt +61 -0
  18. package/template/android/app/src/main/java/com/club/yakka/MainApplication.kt +60 -0
  19. package/template/android/app/src/main/res/drawable/ic_launcher_background.xml +6 -0
  20. package/template/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  21. package/template/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png +0 -0
  22. package/template/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png +0 -0
  23. package/template/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png +0 -0
  24. package/template/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png +0 -0
  25. package/template/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png +0 -0
  26. package/template/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  27. package/template/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
  28. package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  29. package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp +0 -0
  30. package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  31. package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  32. package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp +0 -0
  33. package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  34. package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  35. package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp +0 -0
  36. package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  37. package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  38. package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp +0 -0
  39. package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  40. package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  41. package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp +0 -0
  42. package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  43. package/template/android/app/src/main/res/values/colors.xml +6 -0
  44. package/template/android/app/src/main/res/values/strings.xml +5 -0
  45. package/template/android/app/src/main/res/values/styles.xml +14 -0
  46. package/template/android/app/src/main/res/values-night/colors.xml +1 -0
  47. package/template/android/build.gradle +33 -0
  48. package/template/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  49. package/template/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  50. package/template/android/gradle.properties +65 -0
  51. package/template/android/gradlew +251 -0
  52. package/template/android/gradlew.bat +94 -0
  53. package/template/android/settings.gradle +39 -0
  54. package/template/app.json +69 -0
  55. package/template/assets/adaptive-icon.png +0 -0
  56. package/template/assets/adaptive-icon1.png +0 -0
  57. package/template/assets/app_icon.png +0 -0
  58. package/template/assets/favicon.png +0 -0
  59. package/template/assets/icon.png +0 -0
  60. package/template/assets/splash-icon.png +0 -0
  61. package/template/babel-plugin-disable-font-scaling.js +41 -0
  62. package/template/babel.config.js +28 -0
  63. package/template/firebase.json +5 -0
  64. package/template/index.ts +24 -0
  65. package/template/ios/.xcode.env +11 -0
  66. package/template/ios/ClubYakka/AppDelegate.swift +74 -0
  67. package/template/ios/ClubYakka/ClubYakka-Bridging-Header.h +3 -0
  68. package/template/ios/ClubYakka/ClubYakka.entitlements +8 -0
  69. package/template/ios/ClubYakka/GoogleService-Info.plist +30 -0
  70. package/template/ios/ClubYakka/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png +0 -0
  71. package/template/ios/ClubYakka/Images.xcassets/AppIcon.appiconset/Contents.json +14 -0
  72. package/template/ios/ClubYakka/Images.xcassets/Contents.json +6 -0
  73. package/template/ios/ClubYakka/Images.xcassets/SplashScreenBackground.colorset/Contents.json +20 -0
  74. package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +23 -0
  75. package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/image.png +0 -0
  76. package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png +0 -0
  77. package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png +0 -0
  78. package/template/ios/ClubYakka/Info.plist +101 -0
  79. package/template/ios/ClubYakka/PrivacyInfo.xcprivacy +50 -0
  80. package/template/ios/ClubYakka/SplashScreen.storyboard +48 -0
  81. package/template/ios/ClubYakka/Supporting/Expo.plist +12 -0
  82. package/template/ios/ClubYakka.xcodeproj/project.pbxproj +669 -0
  83. package/template/ios/ClubYakka.xcodeproj/xcshareddata/xcschemes/ClubYakka.xcscheme +88 -0
  84. package/template/ios/ClubYakka.xcworkspace/contents.xcworkspacedata +10 -0
  85. package/template/ios/ClubYakka.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +5 -0
  86. package/template/ios/Podfile +84 -0
  87. package/template/ios/Podfile.lock +2698 -0
  88. package/template/ios/Podfile.properties.json +8 -0
  89. package/template/metro.config.js +17 -0
  90. package/template/package.json +70 -0
  91. package/template/patches/react-native-background-upload+6.6.0.patch +704 -0
  92. package/template/react-native.config.js +7 -0
  93. package/template/src/assets/fonts/Outfit-Bold.ttf +0 -0
  94. package/template/src/assets/fonts/Outfit-Light.ttf +0 -0
  95. package/template/src/assets/fonts/Outfit-Medium.ttf +0 -0
  96. package/template/src/assets/fonts/Outfit-Regular.ttf +0 -0
  97. package/template/src/assets/fonts/Outfit-SemiBold.ttf +0 -0
  98. package/template/src/assets/icons/BG.svg +33 -0
  99. package/template/src/assets/icons/Ic_Users.svg +6 -0
  100. package/template/src/assets/icons/arrow-left-white.svg +4 -0
  101. package/template/src/assets/icons/arrow-left.svg +4 -0
  102. package/template/src/assets/icons/bell.svg +4 -0
  103. package/template/src/assets/icons/bottomSheetIcone.svg +3 -0
  104. package/template/src/assets/icons/call.svg +4 -0
  105. package/template/src/assets/icons/camera.svg +0 -0
  106. package/template/src/assets/icons/chatAppleGreenBG.svg +22 -0
  107. package/template/src/assets/icons/check.svg +4 -0
  108. package/template/src/assets/icons/check_Radio.svg +4 -0
  109. package/template/src/assets/icons/chevron-right.svg +3 -0
  110. package/template/src/assets/icons/clubDefauldImage.png +0 -0
  111. package/template/src/assets/icons/curvBackgroundView.svg +3 -0
  112. package/template/src/assets/icons/defaultClub.png +0 -0
  113. package/template/src/assets/icons/email.svg +3 -0
  114. package/template/src/assets/icons/emptyUser.svg +5 -0
  115. package/template/src/assets/icons/eye-Hide.svg +8 -0
  116. package/template/src/assets/icons/eye.svg +4 -0
  117. package/template/src/assets/icons/gallery.svg +4 -0
  118. package/template/src/assets/icons/home.svg +4 -0
  119. package/template/src/assets/icons/ic_Calendar.svg +14 -0
  120. package/template/src/assets/icons/ic_Calendar_blue.svg +12 -0
  121. package/template/src/assets/icons/ic_Calendar_white.svg +12 -0
  122. package/template/src/assets/icons/ic_Chat.svg +14 -0
  123. package/template/src/assets/icons/ic_ChatAppleGreen.svg +21 -0
  124. package/template/src/assets/icons/ic_ChatAppleWhite.svg +21 -0
  125. package/template/src/assets/icons/ic_Download.svg +6 -0
  126. package/template/src/assets/icons/ic_Events.svg +6 -0
  127. package/template/src/assets/icons/ic_HeadCoachIcon.svg +3 -0
  128. package/template/src/assets/icons/ic_Membership.svg +10 -0
  129. package/template/src/assets/icons/ic_Notification.svg +6 -0
  130. package/template/src/assets/icons/ic_Raffles.svg +14 -0
  131. package/template/src/assets/icons/ic_Referral_Members.svg +12 -0
  132. package/template/src/assets/icons/ic_Shop.svg +13 -0
  133. package/template/src/assets/icons/ic_Teams.svg +8 -0
  134. package/template/src/assets/icons/ic_Volunteer.svg +9 -0
  135. package/template/src/assets/icons/ic_add.svg +3 -0
  136. package/template/src/assets/icons/ic_addCircle.svg +5 -0
  137. package/template/src/assets/icons/ic_chatSend.svg +4 -0
  138. package/template/src/assets/icons/ic_chat_blue_bg.svg +22 -0
  139. package/template/src/assets/icons/ic_clock_blue.svg +4 -0
  140. package/template/src/assets/icons/ic_delete.svg +8 -0
  141. package/template/src/assets/icons/ic_more.svg +5 -0
  142. package/template/src/assets/icons/ic_mute.svg +10 -0
  143. package/template/src/assets/icons/ic_pdf.svg +3 -0
  144. package/template/src/assets/icons/ic_pending_AppleGreen.svg +11 -0
  145. package/template/src/assets/icons/ic_right_appleGreen.svg +11 -0
  146. package/template/src/assets/icons/ic_unread_chat_blue_bg.svg +23 -0
  147. package/template/src/assets/icons/ic_volunteer_Member.svg +8 -0
  148. package/template/src/assets/icons/index.ts +144 -0
  149. package/template/src/assets/icons/location-blue.svg +4 -0
  150. package/template/src/assets/icons/location-white.svg +4 -0
  151. package/template/src/assets/icons/location.svg +4 -0
  152. package/template/src/assets/icons/lock.svg +5 -0
  153. package/template/src/assets/icons/log-out.svg +5 -0
  154. package/template/src/assets/icons/login_logo.svg +9 -0
  155. package/template/src/assets/icons/mail.svg +4 -0
  156. package/template/src/assets/icons/or_saprater.svg +5 -0
  157. package/template/src/assets/icons/password.svg +4 -0
  158. package/template/src/assets/icons/profile-appleGreen.svg +6 -0
  159. package/template/src/assets/icons/search.svg +3 -0
  160. package/template/src/assets/icons/settings.svg +4 -0
  161. package/template/src/assets/icons/success.svg +4 -0
  162. package/template/src/assets/icons/unCheck_Radio.svg +3 -0
  163. package/template/src/assets/icons/uncheck.svg +3 -0
  164. package/template/src/assets/icons/upload_Image.svg +6 -0
  165. package/template/src/assets/icons/upload_Image_Member.svg +6 -0
  166. package/template/src/assets/icons/user.svg +4 -0
  167. package/template/src/assets/icons/wifi.svg +1 -0
  168. package/template/src/assets/images/Splash.png +0 -0
  169. package/template/src/assets/images/SplashLogo.png +0 -0
  170. package/template/src/assets/images/background.png +0 -0
  171. package/template/src/assets/images/clubDefauldImage.png +0 -0
  172. package/template/src/assets/images/index.tsx +9 -0
  173. package/template/src/assets/index.ts +1 -0
  174. package/template/src/components/common/AddMemberModal.tsx +543 -0
  175. package/template/src/components/common/AppLoader.tsx +91 -0
  176. package/template/src/components/common/Button.tsx +173 -0
  177. package/template/src/components/common/ClubCard.tsx +248 -0
  178. package/template/src/components/common/Icon.tsx +93 -0
  179. package/template/src/components/common/IconAlt.tsx +65 -0
  180. package/template/src/components/common/IconButton.tsx +92 -0
  181. package/template/src/components/common/ImagePicker.tsx +451 -0
  182. package/template/src/components/common/LoadingSpinner.tsx +30 -0
  183. package/template/src/components/common/OTPInput.tsx +128 -0
  184. package/template/src/components/common/ReminderCalendar.tsx +129 -0
  185. package/template/src/components/common/ReminderCardItem.tsx +91 -0
  186. package/template/src/components/common/SafeAreaWrapper.tsx +27 -0
  187. package/template/src/components/common/SetReminderModal.tsx +308 -0
  188. package/template/src/components/common/SlowInternet.js +57 -0
  189. package/template/src/components/common/TeamCard.tsx +297 -0
  190. package/template/src/components/common/TextInput.tsx +227 -0
  191. package/template/src/components/common/ToastConfig.tsx +103 -0
  192. package/template/src/components/common/ToastManager.ts +86 -0
  193. package/template/src/components/common/UploadProgressModal.tsx +284 -0
  194. package/template/src/components/common/WrapperContainer.tsx +39 -0
  195. package/template/src/components/common/index.ts +19 -0
  196. package/template/src/constants/Constants.tsx +7 -0
  197. package/template/src/constants/Fonts.tsx +30 -0
  198. package/template/src/constants/index.ts +45 -0
  199. package/template/src/constants/strings.ts +211 -0
  200. package/template/src/constants/theme.ts +72 -0
  201. package/template/src/contexts/AuthContext.tsx +268 -0
  202. package/template/src/contexts/index.ts +2 -0
  203. package/template/src/hooks/index.ts +3 -0
  204. package/template/src/hooks/useImageUpload.ts +199 -0
  205. package/template/src/index.ts +8 -0
  206. package/template/src/navigation/AuthStack.tsx +67 -0
  207. package/template/src/navigation/MainStack.tsx +183 -0
  208. package/template/src/navigation/MiddleStack.tsx +35 -0
  209. package/template/src/navigation/RootNavigator.tsx +101 -0
  210. package/template/src/navigation/index.ts +5 -0
  211. package/template/src/navigation/navigationRef.ts +5 -0
  212. package/template/src/providers/QueryProvider.tsx +30 -0
  213. package/template/src/screens/DetailsScreen.tsx +271 -0
  214. package/template/src/screens/HomeScreen.tsx +736 -0
  215. package/template/src/screens/ProfileScreen.tsx +202 -0
  216. package/template/src/screens/SettingsScreen.tsx +253 -0
  217. package/template/src/screens/SportHubScreen.tsx +280 -0
  218. package/template/src/screens/auth/AddMamber.tsx +428 -0
  219. package/template/src/screens/auth/ForgotPasswordScreen.tsx +176 -0
  220. package/template/src/screens/auth/LoginScreen.tsx +286 -0
  221. package/template/src/screens/auth/OTPVerifyScreen.tsx +359 -0
  222. package/template/src/screens/auth/RegisterScreen.tsx +430 -0
  223. package/template/src/screens/auth/SuccessScreen.tsx +201 -0
  224. package/template/src/screens/auth/WalcomeScreen.tsx +274 -0
  225. package/template/src/screens/auth/index.ts +8 -0
  226. package/template/src/screens/chat/ChatScreen.tsx +1819 -0
  227. package/template/src/screens/chat/ChatThreadsScreen.tsx +360 -0
  228. package/template/src/screens/chat/ReportMessageScreen.tsx +238 -0
  229. package/template/src/screens/clubs/Announcements.tsx +426 -0
  230. package/template/src/screens/clubs/BuyRaffleTicketsScreen.tsx +568 -0
  231. package/template/src/screens/clubs/ClubDeteils.tsx +497 -0
  232. package/template/src/screens/clubs/JoinClub.tsx +841 -0
  233. package/template/src/screens/events/EventScreen.tsx +460 -0
  234. package/template/src/screens/index.ts +42 -0
  235. package/template/src/screens/raffles/MyReferralMembersScreen.tsx +758 -0
  236. package/template/src/screens/raffles/RaffleDetailsScreen.tsx +762 -0
  237. package/template/src/screens/raffles/RafflesScreen.tsx +495 -0
  238. package/template/src/screens/raffles/SetRaffleReminderScreen.tsx +390 -0
  239. package/template/src/screens/teams/JoinTeamScreen.tsx +464 -0
  240. package/template/src/screens/teams/MyTeamDetailsScreen.tsx +979 -0
  241. package/template/src/screens/teams/MyTeamScreen.tsx +568 -0
  242. package/template/src/screens/teams/PendingRequestsScreen.tsx +426 -0
  243. package/template/src/screens/volunteerOpportunities/SetReminderScreen.tsx +631 -0
  244. package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesDetailsScreen.tsx +1049 -0
  245. package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesScreen.tsx +608 -0
  246. package/template/src/services/api.ts +167 -0
  247. package/template/src/services/authService.ts +422 -0
  248. package/template/src/services/index.ts +5 -0
  249. package/template/src/services/mainServices.ts +1963 -0
  250. package/template/src/stores/authStore.ts +159 -0
  251. package/template/src/stores/index.ts +2 -0
  252. package/template/src/types/index.ts +85 -0
  253. package/template/src/types/navigation.ts +206 -0
  254. package/template/src/utils/AzureUploaderService.ts +371 -0
  255. package/template/src/utils/ClubSearchManager.ts +222 -0
  256. package/template/src/utils/NotificationManager.ts +503 -0
  257. package/template/src/utils/UploadDebugUtil.ts +107 -0
  258. package/template/src/utils/imagePicker.ts +321 -0
  259. package/template/src/utils/index.ts +111 -0
  260. package/template/src/utils/permissions.ts +277 -0
  261. package/template/src/utils/scaling.ts +14 -0
  262. package/template/src/utils/storage.ts +247 -0
  263. package/template/src/utils/usePermissions.ts +275 -0
  264. package/template/src/utils/validation.ts +340 -0
  265. package/template/tsconfig.json +50 -0
  266. package/template/types/svg.d.ts +6 -0
@@ -0,0 +1,736 @@
1
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { View, Text, StyleSheet, FlatList, StatusBar, Platform, TouchableOpacity, Image, RefreshControl, ActivityIndicator, Modal, Animated, Dimensions, PanResponder, Alert } from 'react-native';
3
+ import { useQueryClient } from '@tanstack/react-query';
4
+ import { useFocusEffect } from '@react-navigation/native';
5
+ import { SafeAreaView } from 'react-native-safe-area-context';
6
+ import { HomeScreenProps } from '@navigation';
7
+ import { theme } from '@constants';
8
+ import { Strings } from '@constants/strings';
9
+ import { moderateScale } from '@utils/scaling';
10
+ import { Fonts } from '@constants/Fonts';
11
+ import { useAuthStore } from '@stores/authStore';
12
+ import { getApiErrorInfo, useLogout } from '@services/authService';
13
+ import { Button, ClubCard } from '@components/common';
14
+ import { mainService, useInfiniteMyClubs } from '@services/mainServices';
15
+ import ToastManager from '@components/common/ToastManager';
16
+ import SVG from '@assets/icons';
17
+
18
+ type MemberListItemProps = {
19
+ item: any;
20
+ isSelected: boolean;
21
+ onSelect: (member: any) => void;
22
+ };
23
+
24
+
25
+ const MemberListItem: React.FC<MemberListItemProps> = ({ item, isSelected, onSelect }) => {
26
+ const [memberImageError, setMemberImageError] = useState(false);
27
+ return (
28
+ <TouchableOpacity style={styles.memberListItem} onPress={() => onSelect(item)}>
29
+ {item?.profileImage && !memberImageError ? (
30
+ <Image
31
+ source={{ uri: item?.profileImage }}
32
+ onError={() => {
33
+ setMemberImageError(true);
34
+ }}
35
+ style={styles.profileImage}
36
+ />
37
+ ) : (
38
+ <SVG.emptyUser style={{ marginRight: moderateScale(10) }} width={moderateScale(50)} height={moderateScale(50)} />
39
+ )}
40
+ <View style={styles.memberNameContainer}>
41
+ <Text style={styles.memberName}>{item?.name}</Text>
42
+ {item?.isOwner && (
43
+ <Text style={styles.memberNameSubtitle}>Yourself</Text>
44
+ )}
45
+ </View>
46
+ <View>
47
+ {isSelected ? (
48
+ <SVG.checkRadio width={moderateScale(20)} height={moderateScale(20)} />
49
+ ) : (
50
+ <SVG.uncheckRadio width={moderateScale(20)} height={moderateScale(20)} />
51
+ )}
52
+ </View>
53
+ </TouchableOpacity>
54
+ );
55
+ };
56
+
57
+ export const HomeScreen: React.FC<HomeScreenProps> = ({ navigation }) => {
58
+ const user = useAuthStore((state) => state.user);
59
+ const [isLoading, setIsLoading] = useState(false);
60
+ const [isRefreshing, setIsRefreshing] = useState(false);
61
+ const [membersModal, setMembersModal] = useState<boolean>(false);
62
+ const [selectedMember, setSelectedMember] = useState<any>(null);
63
+ const [members, setMembers] = useState<any[]>([]);
64
+ const translateY = useRef(new Animated.Value(0)).current;
65
+ const screenHeight = Dimensions.get('window').height;
66
+ const bottomSheetHeight = screenHeight * 0.45; // 45% of screen height
67
+ const PAGE_SIZE = 10;
68
+ const queryClient = useQueryClient();
69
+ const [selectedClub, setSelectedClub] = useState<any>(null);
70
+ const logout = useAuthStore((state) => state.logout);
71
+ const [joinClubMemberImageUrls, setJoinClubMemberImageUrls] = useState<Record<number, string>>({});
72
+ const [listError, setListError] = useState<string | null>(null);
73
+
74
+
75
+ const keyExtractor = (item: any, index: number) => {
76
+ const id = item?.id?.toString() || '';
77
+ const name = item?.name || item?.clubName || '';
78
+ const clubCode = item?.clubCode || item?.uniqueCode || '';
79
+ const timestamp = item?.createdAt || item?.timestamp || '';
80
+ const userId = item?.userId || '';
81
+ const keyParts = [id, name, clubCode, timestamp, userId].filter(part => part && part.toString().trim() !== '');
82
+ if (keyParts.length > 0) {
83
+ return `club-${keyParts.join('-')}-${index}`;
84
+ }
85
+ return `club-unknown-${index}`;
86
+ };
87
+
88
+
89
+
90
+ const {
91
+ data,
92
+ fetchNextPage,
93
+ hasNextPage,
94
+ isFetchingNextPage,
95
+ isLoading: isLoadingMyClubs,
96
+ isError: isErrorMyClubs,
97
+ error: myClubsError,
98
+ refetch
99
+ } = useInfiniteMyClubs("", PAGE_SIZE);
100
+ const logoutMutation = useLogout();
101
+
102
+ const allMyClubs = data?.pages?.flatMap(page =>
103
+ (page?.data?.data || []).map((club: any) => ({
104
+ ...club,
105
+ }))
106
+ ) || [];
107
+
108
+
109
+ useEffect(() => {
110
+ if (isErrorMyClubs && myClubsError) {
111
+ const errorInfo = getApiErrorInfo(myClubsError);
112
+ setListError(errorInfo?.message || 'Unable to load clubs right now.');
113
+ ToastManager.error(errorInfo?.message);
114
+ } else {
115
+ setListError(null);
116
+ }
117
+ }, [isErrorMyClubs, myClubsError]);
118
+
119
+ useFocusEffect(useCallback(() => {
120
+ refetch();
121
+ }, [refetch]));
122
+
123
+
124
+
125
+ const onRefresh = useCallback(async () => {
126
+ try {
127
+ setIsRefreshing(true);
128
+ queryClient.removeQueries({
129
+ queryKey: ['infiniteMyClubs', "", PAGE_SIZE]
130
+ });
131
+ await refetch();
132
+ } catch (error) {
133
+ console.error('Refresh failed:', error);
134
+ } finally {
135
+ setIsRefreshing(false);
136
+ }
137
+ }, [refetch, queryClient, PAGE_SIZE]);
138
+
139
+
140
+
141
+ // Load more data
142
+ const onEndReached = useCallback(() => {
143
+ if (isFetchingNextPage || !hasNextPage) return;
144
+ fetchNextPage();
145
+ }, [isFetchingNextPage, hasNextPage, fetchNextPage, allMyClubs.length]);
146
+
147
+
148
+ const handleRetryLoad = useCallback(() => {
149
+ queryClient.invalidateQueries({
150
+ queryKey: ['infiniteMyClubs', "", PAGE_SIZE]
151
+ });
152
+ refetch();
153
+ }, [queryClient, refetch, PAGE_SIZE]);
154
+
155
+ // Render loading footer
156
+ const renderMyClubsFooter = () => {
157
+ if (!isFetchingNextPage) return null;
158
+ return (
159
+ <View style={styles.footerLoader}>
160
+ <ActivityIndicator size="small" color={theme.colors.primary} />
161
+ <Text style={styles.footerText}>Loading more clubs...</Text>
162
+ </View>
163
+ );
164
+ };
165
+
166
+ // Render empty state
167
+ const renderEmptyComponent = () => {
168
+ if (isLoadingMyClubs && !isRefreshing) {
169
+ return (
170
+ <View style={styles.emptyContainer}>
171
+ <ActivityIndicator size="large" color={theme.colors.primary} />
172
+ <Text style={styles.emptyListText}>Loading clubs...</Text>
173
+ </View>
174
+ );
175
+ }
176
+
177
+ if (listError) {
178
+ return (
179
+ <View style={styles.emptyContainer}>
180
+ <Text style={styles.errorText}>{listError}</Text>
181
+ <Button title="Retry" onPress={handleRetryLoad} variant="primary" size="small" />
182
+ </View>
183
+ );
184
+ }
185
+
186
+ return (
187
+ <View style={styles.emptyContainer}>
188
+ <Text style={styles.emptyListText}>No clubs found</Text>
189
+ <Text style={styles.emptySubText}>Try refreshing or check back later</Text>
190
+ </View>
191
+ );
192
+ };
193
+
194
+
195
+ const handleLogout = () => {
196
+ Alert.alert(
197
+ 'Logout',
198
+ 'Are you sure you want to logout?',
199
+ [
200
+ {
201
+ text: 'Cancel',
202
+ style: 'cancel',
203
+ },
204
+ {
205
+ text: 'Logout',
206
+ style: 'destructive',
207
+ onPress: async () => {
208
+ try {
209
+ // Call logout API
210
+ const response = await logoutMutation.mutateAsync(undefined);
211
+
212
+ // API call successful - logout locally
213
+ logout();
214
+
215
+ // Show success message if available
216
+ if (response?.data?.message) {
217
+ ToastManager.success('Logout', response.data.message);
218
+ }
219
+ } catch (error) {
220
+ // Handle error scenarios
221
+ const errorInfo = getApiErrorInfo(error);
222
+
223
+ // If it's a network error or server error, still logout locally
224
+ // This ensures user can logout even if API is down
225
+ if (errorInfo.isNetworkError || errorInfo.isServerError || errorInfo.statusCode === 0) {
226
+ logout();
227
+ ToastManager.error('Logout', 'Logged out locally. There was an issue connecting to the server.');
228
+ }
229
+ // If it's a 401 Unauthorized, token might be invalid - logout locally anyway
230
+ else if (errorInfo.isUnauthorized) {
231
+ logout();
232
+ ToastManager.error('Logout', 'Session expired. You have been logged out.');
233
+ }
234
+ // For other errors (4xx client errors), show error but still allow logout
235
+ else if (errorInfo.isClientError) {
236
+ // Still logout locally but show error
237
+ logout();
238
+ ToastManager.error('Logout', errorInfo.message || 'Logged out with some issues.');
239
+ }
240
+ // For any other unexpected error, logout locally as fallback
241
+ else {
242
+ logout();
243
+ ToastManager.error('Logout', errorInfo.message || 'Failed to logout from server. Logged out locally.');
244
+ }
245
+ }
246
+ },
247
+ },
248
+ ]
249
+ );
250
+ };
251
+
252
+
253
+
254
+
255
+ const handleMemberSelection = (member: any) => {
256
+ setSelectedMember(member);
257
+ console.log('Selected member:', member);
258
+ };
259
+
260
+ // Bottom sheet animation functions
261
+ const showBottomSheet = () => {
262
+ setMembersModal(true);
263
+ Animated.spring(translateY, {
264
+ toValue: 0,
265
+ useNativeDriver: true,
266
+ tension: 100,
267
+ friction: 8,
268
+ }).start();
269
+ };
270
+
271
+ const hideBottomSheet = () => {
272
+ Animated.timing(translateY, {
273
+ toValue: bottomSheetHeight,
274
+ duration: 300,
275
+ useNativeDriver: true,
276
+ }).start(() => {
277
+ setMembersModal(false);
278
+ translateY.setValue(bottomSheetHeight);
279
+ });
280
+ };
281
+
282
+ // PanResponder for swipe gestures
283
+ const panResponder = useRef(
284
+ PanResponder.create({
285
+ onMoveShouldSetPanResponder: (_, gestureState) => {
286
+ return Math.abs(gestureState.dy) > 5;
287
+ },
288
+ onPanResponderGrant: () => {
289
+ translateY.setOffset((translateY as any)._value);
290
+ translateY.setValue(0);
291
+ },
292
+ onPanResponderMove: (_, gestureState) => {
293
+ // Only allow downward movement
294
+ if (gestureState.dy > 0) {
295
+ translateY.setValue(gestureState.dy);
296
+ }
297
+ },
298
+ onPanResponderRelease: (_, gestureState) => {
299
+ translateY.flattenOffset();
300
+
301
+ // If swiped down more than 100px or with high velocity, close the modal
302
+ if (gestureState.dy > 100 || gestureState.vy > 0.5) {
303
+ hideBottomSheet();
304
+ } else {
305
+ // Snap back to original position
306
+ Animated.spring(translateY, {
307
+ toValue: 0,
308
+ useNativeDriver: true,
309
+ tension: 100,
310
+ friction: 8,
311
+ }).start();
312
+ }
313
+ },
314
+ })
315
+ ).current;
316
+
317
+ const renderMemberListItem = ({ item }: { item: any }) => (
318
+ <MemberListItem
319
+ item={item}
320
+ isSelected={selectedMember?.id === item?.id}
321
+ onSelect={handleMemberSelection}
322
+ />
323
+ );
324
+
325
+
326
+ const renderClubItem = ({ item }: any) => {
327
+ return (
328
+ <ClubCard
329
+ club={item}
330
+ onPress={async () => {
331
+ const response = await mainService.getClubMembers({ clubId: item?.id });
332
+ const members = response.data.data;
333
+ const sortedMembers = members.sort((a: any, b: any) => {
334
+ if (a?.isOwner) return -1;
335
+ if (b?.isOwner) return 1;
336
+ return 0;
337
+ });
338
+ setSelectedClub(item);
339
+ setMembers(sortedMembers.map((member: any) => ({
340
+ ...member,
341
+ profileImageUrl: joinClubMemberImageUrls[member?.id] || undefined
342
+ })));
343
+
344
+ if (sortedMembers.length === 1) {
345
+ setSelectedMember(sortedMembers[0]);
346
+ navigation.navigate('ClubDetails', { ...item, selectedMember: sortedMembers[0] });
347
+ } else {
348
+ setSelectedMember(null);
349
+ showBottomSheet();
350
+ }
351
+ }}
352
+ />
353
+ );
354
+ };
355
+
356
+
357
+ return (
358
+ <View style={styles.container}>
359
+ <StatusBar
360
+ barStyle="light-content"
361
+ backgroundColor={theme.colors.blue}
362
+ translucent={Platform.OS === 'android' ? true : false}
363
+ />
364
+ {Platform.OS === 'ios' && <View style={styles.statusBarBackground} />}
365
+ <SafeAreaView style={styles.header} edges={['top']}>
366
+ <View style={{ flexDirection: 'row', }}>
367
+ <View style={{ width: '50%', justifyContent: 'center' }}>
368
+ <Text style={styles.helloTitle}>Hello,</Text>
369
+ <Text style={styles.headerTitle}>{user?.Name}</Text>
370
+ </View>
371
+ <View style={{ flex: 1, width: '50%', flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end' }}>
372
+ <TouchableOpacity onPress={() => { Alert.alert('Alert', "Coming Soon") }}>
373
+ <SVG.NotificationIcon style={{ marginRight: moderateScale(10) }} width={moderateScale(40)} height={moderateScale(40)} />
374
+ </TouchableOpacity>
375
+ <TouchableOpacity style={styles.profileImageContainer} onPress={handleLogout}>
376
+ {isLoading && (
377
+ <ActivityIndicator style={styles.loadingIndicator} size="small" color={theme.colors.appleGreen} />
378
+ )}
379
+ {!!user?.profileImage ? (
380
+ <Image
381
+ onLoadStart={() => setIsLoading(true)}
382
+ onLoad={() => setIsLoading(false)}
383
+ onLoadEnd={() => setIsLoading(false)}
384
+ source={{ uri: user?.profileImage }}
385
+ style={{ width: moderateScale(40), height: moderateScale(40), borderRadius: moderateScale(20) }}
386
+ />
387
+ ) : (
388
+ <SVG.UsersIcon width={moderateScale(20)} height={moderateScale(20)} />
389
+ )}
390
+
391
+
392
+ </TouchableOpacity>
393
+ </View>
394
+
395
+ </View>
396
+ </SafeAreaView>
397
+
398
+ <View style={styles.addMemberContainer}>
399
+ <TouchableOpacity style={styles.addMemberButton} onPress={() => { navigation.navigate('JoinClub') }}>
400
+ <SVG.icAdd width={moderateScale(20)} height={moderateScale(20)} />
401
+ <Text style={styles.addMemberButtonText}>{Strings.AUTH.JOIN_CLUB}</Text>
402
+ </TouchableOpacity>
403
+ </View>
404
+
405
+ <View style={styles.content}>
406
+ <Text style={styles.sectionTitle}>{Strings.AUTH.MY_CLUBS_SECTION_TITLE}</Text>
407
+ <FlatList
408
+ data={allMyClubs}
409
+ renderItem={renderClubItem}
410
+ keyExtractor={keyExtractor}
411
+ style={styles.clubsList}
412
+ showsVerticalScrollIndicator={false}
413
+ contentContainerStyle={styles.flatListContent}
414
+ removeClippedSubviews={false}
415
+ initialNumToRender={10}
416
+ onEndReached={onEndReached}
417
+ onEndReachedThreshold={0.9}
418
+ ListFooterComponent={renderMyClubsFooter}
419
+ ListEmptyComponent={renderEmptyComponent}
420
+ refreshControl={
421
+ <RefreshControl
422
+ refreshing={isRefreshing}
423
+ onRefresh={onRefresh}
424
+ colors={[theme.colors.primary]}
425
+ tintColor={theme.colors.primary}
426
+ />
427
+ }
428
+ />
429
+ </View>
430
+
431
+
432
+ <Modal
433
+ visible={membersModal}
434
+ animationType="fade"
435
+ transparent={true}
436
+ onRequestClose={hideBottomSheet}
437
+ >
438
+ <View style={styles.modalOverlay}>
439
+ <TouchableOpacity
440
+ style={styles.modalBackdrop}
441
+ activeOpacity={1}
442
+ onPress={hideBottomSheet}
443
+ />
444
+ <Animated.View
445
+ style={[
446
+ styles.modalContainer,
447
+ {
448
+ transform: [{ translateY }],
449
+ },
450
+ ]}
451
+
452
+ >
453
+ <View style={styles.dragHandle} {...panResponder.panHandlers} />
454
+ <FlatList
455
+ data={members}
456
+ renderItem={renderMemberListItem}
457
+ style={styles.membersList}
458
+ showsVerticalScrollIndicator={false}
459
+ ListHeaderComponent={() => {
460
+ return (
461
+ <View>
462
+ <View style={styles.headerContainer}>
463
+ <Text style={styles.mamberHeaderTitle}>Continue as</Text>
464
+ </View>
465
+ </View>
466
+ )
467
+ }}
468
+
469
+ keyExtractor={(item, index) => {
470
+ // Create a more robust key for members
471
+ const id = item?.id?.toString() || '';
472
+ const name = item?.name || '';
473
+ const email = item?.email || '';
474
+ const memberCode = item?.membershipCode || '';
475
+ const createdAt = item?.createdAt || '';
476
+ // Create a unique composite key using multiple attributes
477
+ const keyParts = [id, name, email, memberCode, createdAt].filter(part => part && part.toString().trim() !== '');
478
+
479
+ if (keyParts.length > 0) {
480
+ return `member-${keyParts.join('-')}`;
481
+ }
482
+ // Ultimate fallback with index to ensure uniqueness
483
+ return `member-unknown-${index}`;
484
+ }}
485
+
486
+ />
487
+
488
+ <Button
489
+ title="Continue"
490
+ onPress={() => {
491
+ navigation.navigate('ClubDetails', {
492
+ ...selectedClub,
493
+ selectedMember: selectedMember
494
+ });
495
+ hideBottomSheet();
496
+ }}
497
+ variant="primary"
498
+ size="medium"
499
+ style={styles.nextButton}
500
+ disabled={!selectedMember}
501
+ />
502
+ </Animated.View>
503
+ </View>
504
+ </Modal>
505
+ </View>
506
+ );
507
+ };
508
+
509
+ const styles = StyleSheet.create({
510
+ container: {
511
+ flex: 1,
512
+ backgroundColor: theme.colors.blue,
513
+ },
514
+ statusBarBackground: {
515
+ position: 'absolute',
516
+ top: 0,
517
+ left: 0,
518
+ right: 0,
519
+ height: Platform.OS === 'ios' ? 44 : 0, // Status bar height for iOS
520
+ backgroundColor: theme.colors.blue,
521
+ zIndex: 1000,
522
+ },
523
+ header: {
524
+ backgroundColor: theme.colors.blue,
525
+ paddingHorizontal: theme.spacing.lg,
526
+ paddingVertical: theme.spacing.md,
527
+ justifyContent: 'space-between',
528
+ alignItems: 'center',
529
+ },
530
+ helloTitle: {
531
+ fontFamily: Fonts.outfitRegular,
532
+ fontSize: moderateScale(18),
533
+ color: theme.colors.white,
534
+ },
535
+ headerTitle: {
536
+ fontFamily: Fonts.outfitSemiBold,
537
+ fontSize: moderateScale(24),
538
+ color: theme.colors.white,
539
+ },
540
+ skipButton: {
541
+ alignSelf: 'flex-end',
542
+ paddingHorizontal: theme.spacing.sm,
543
+ paddingVertical: theme.spacing.xs,
544
+ },
545
+ skipButtonText: {
546
+ fontFamily: Fonts.outfitSemiBold,
547
+ fontSize: theme.typography.fontSize.md,
548
+ color: theme.colors.white,
549
+ },
550
+ addMemberContainer: {
551
+ backgroundColor: theme.colors.blue,
552
+ paddingHorizontal: theme.spacing.lg,
553
+ paddingBottom: theme.spacing.md,
554
+ },
555
+ addMemberButton: {
556
+ flexDirection: 'row',
557
+ alignItems: 'center',
558
+ justifyContent: 'center',
559
+ backgroundColor: 'transparent',
560
+ borderWidth: 1.5,
561
+ borderColor: theme.colors.appleGreen,
562
+ borderRadius: theme.borderRadius.xxl,
563
+ paddingVertical: theme.spacing.sm,
564
+ paddingHorizontal: theme.spacing.lg,
565
+ gap: theme.spacing.sm,
566
+ },
567
+ addMemberButtonText: {
568
+ fontFamily: Fonts.outfitMedium,
569
+ fontSize: theme.typography.fontSize.md,
570
+ color: theme.colors.appleGreen,
571
+ },
572
+ content: {
573
+ flex: 1,
574
+ backgroundColor: theme.colors.background,
575
+ paddingHorizontal: theme.spacing.lg,
576
+ paddingTop: theme.spacing.md,
577
+ borderTopLeftRadius: moderateScale(30),
578
+ borderTopRightRadius: moderateScale(30),
579
+ },
580
+ sectionTitle: {
581
+ fontFamily: Fonts.outfitMedium,
582
+ fontSize: theme.typography.fontSize.xs,
583
+ color: theme.colors.blue,
584
+ marginBottom: theme.spacing.md,
585
+ letterSpacing: 0.5,
586
+ },
587
+ clubsList: {
588
+ flex: 1,
589
+ },
590
+ flatListContent: {
591
+ paddingBottom: theme.spacing.xl,
592
+ marginBottom: theme.spacing.md,
593
+ },
594
+ emptyContainer: {
595
+ flex: 1,
596
+ justifyContent: 'center',
597
+ alignItems: 'center',
598
+ paddingVertical: theme.spacing.xl,
599
+ },
600
+ bottomContainer: {
601
+ height: moderateScale(120),
602
+ paddingHorizontal: theme.spacing.lg,
603
+ paddingVertical: theme.spacing.md,
604
+ backgroundColor: theme.colors.white,
605
+ },
606
+ nextButton: {
607
+ width: '100%',
608
+ marginBottom: theme.spacing.md,
609
+ },
610
+ emptyListText: {
611
+ fontFamily: Fonts.outfitMedium,
612
+ fontSize: theme.typography.fontSize.md,
613
+ color: theme.colors.text,
614
+ textAlign: 'center',
615
+ marginBottom: theme.spacing.xs,
616
+ },
617
+ emptySubText: {
618
+ fontFamily: Fonts.outfitRegular,
619
+ fontSize: theme.typography.fontSize.sm,
620
+ color: theme.colors.textSecondary,
621
+ textAlign: 'center',
622
+ },
623
+ footerLoader: {
624
+ flexDirection: 'row',
625
+ justifyContent: 'center',
626
+ alignItems: 'center',
627
+ paddingVertical: theme.spacing.md,
628
+ gap: theme.spacing.sm,
629
+ },
630
+ footerText: {
631
+ fontFamily: Fonts.outfitMedium,
632
+ fontSize: theme.typography.fontSize.sm,
633
+ color: theme.colors.textSecondary,
634
+ },
635
+ errorText: {
636
+ fontFamily: Fonts.outfitMedium,
637
+ fontSize: theme.typography.fontSize.md,
638
+ color: theme.colors.error,
639
+ textAlign: 'center',
640
+ marginBottom: theme.spacing.md,
641
+ },
642
+ retryButton: {
643
+ backgroundColor: theme.colors.primary,
644
+ paddingHorizontal: theme.spacing.lg,
645
+ paddingVertical: theme.spacing.sm,
646
+ borderRadius: theme.borderRadius.md,
647
+ },
648
+ retryButtonText: {
649
+ fontFamily: Fonts.outfitMedium,
650
+ fontSize: theme.typography.fontSize.md,
651
+ color: theme.colors.white,
652
+ textAlign: 'center',
653
+ },
654
+ modalOverlay: {
655
+ flex: 1,
656
+ justifyContent: 'flex-end',
657
+ },
658
+ modalBackdrop: {
659
+ position: 'absolute',
660
+ top: 0,
661
+ left: 0,
662
+ right: 0,
663
+ bottom: 0,
664
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
665
+ },
666
+ modalContainer: {
667
+ backgroundColor: theme.colors.white,
668
+ borderTopLeftRadius: moderateScale(20),
669
+ borderTopRightRadius: moderateScale(20),
670
+ paddingHorizontal: theme.spacing.lg,
671
+ paddingTop: theme.spacing.sm,
672
+ paddingBottom: theme.spacing.xl,
673
+ height: '45%',
674
+ maxHeight: '45%',
675
+ },
676
+ dragHandle: {
677
+ width: moderateScale(40),
678
+ height: moderateScale(4),
679
+ backgroundColor: theme.colors.border,
680
+ borderRadius: moderateScale(2),
681
+ alignSelf: 'center',
682
+ marginBottom: theme.spacing.sm,
683
+ },
684
+ membersList: {
685
+ flex: 1,
686
+ },
687
+ memberListItem: {
688
+ height: moderateScale(70),
689
+ alignItems: 'center',
690
+ paddingVertical: theme.spacing.md,
691
+ flexDirection: 'row',
692
+ },
693
+ headerContainer: {
694
+ paddingVertical: theme.spacing.xs,
695
+ },
696
+ mamberHeaderTitle: {
697
+ fontFamily: Fonts.outfitSemiBold,
698
+ fontSize: moderateScale(14),
699
+ color: theme.colors.text,
700
+ },
701
+ profileImage: {
702
+ width: moderateScale(40),
703
+ height: moderateScale(40),
704
+ borderRadius: moderateScale(20),
705
+ marginRight: moderateScale(10),
706
+ },
707
+ memberName: {
708
+ fontFamily: Fonts.outfitSemiBold,
709
+ fontSize: moderateScale(16),
710
+ color: theme.colors.text,
711
+ },
712
+ memberNameContainer: {
713
+ flex: 1,
714
+ justifyContent: 'center',
715
+ },
716
+ memberNameSubtitle: {
717
+ fontFamily: Fonts.outfitMedium,
718
+ fontSize: moderateScale(12),
719
+ color: theme.colors.border,
720
+ },
721
+ loadingIndicator: {
722
+ position: 'absolute',
723
+ top: 0,
724
+ left: 0,
725
+ right: 0,
726
+ bottom: 0,
727
+ },
728
+ profileImageContainer: {
729
+ backgroundColor: theme.colors.white,
730
+ width: moderateScale(40),
731
+ height: moderateScale(40),
732
+ borderRadius: moderateScale(20),
733
+ alignItems: 'center',
734
+ justifyContent: 'center',
735
+ },
736
+ });