create-gufran-expo-app 2.0.3 → 2.0.5

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 (42) hide show
  1. package/README.md +1 -2
  2. package/package.json +3 -3
  3. package/template/src/navigation/AuthStack.tsx +6 -25
  4. package/template/src/navigation/MainStack.tsx +0 -148
  5. package/template/src/navigation/RootNavigator.tsx +4 -26
  6. package/template/src/navigation/index.ts +0 -1
  7. package/template/src/navigation/navigationRef.ts +1 -1
  8. package/template/src/screens/HomeScreen.tsx +3 -215
  9. package/template/src/screens/auth/LoginScreen.tsx +13 -13
  10. package/template/src/screens/auth/index.ts +1 -6
  11. package/template/src/screens/index.ts +0 -35
  12. package/template/src/services/api.ts +5 -5
  13. package/template/src/services/authService.ts +3 -299
  14. package/template/src/services/mainServices.ts +19 -1914
  15. package/template/src/types/navigation.ts +5 -155
  16. package/template/src/utils/index.ts +5 -8
  17. package/template/src/navigation/MiddleStack.tsx +0 -35
  18. package/template/src/screens/auth/AddMamber.tsx +0 -428
  19. package/template/src/screens/auth/ForgotPasswordScreen.tsx +0 -176
  20. package/template/src/screens/auth/OTPVerifyScreen.tsx +0 -359
  21. package/template/src/screens/auth/RegisterScreen.tsx +0 -430
  22. package/template/src/screens/auth/SuccessScreen.tsx +0 -201
  23. package/template/src/screens/chat/ChatScreen.tsx +0 -1819
  24. package/template/src/screens/chat/ChatThreadsScreen.tsx +0 -360
  25. package/template/src/screens/chat/ReportMessageScreen.tsx +0 -238
  26. package/template/src/screens/clubs/Announcements.tsx +0 -426
  27. package/template/src/screens/clubs/BuyRaffleTicketsScreen.tsx +0 -568
  28. package/template/src/screens/clubs/ClubDeteils.tsx +0 -497
  29. package/template/src/screens/clubs/JoinClub.tsx +0 -841
  30. package/template/src/screens/events/EventScreen.tsx +0 -460
  31. package/template/src/screens/raffles/MyReferralMembersScreen.tsx +0 -758
  32. package/template/src/screens/raffles/RaffleDetailsScreen.tsx +0 -762
  33. package/template/src/screens/raffles/RafflesScreen.tsx +0 -495
  34. package/template/src/screens/raffles/SetRaffleReminderScreen.tsx +0 -390
  35. package/template/src/screens/teams/JoinTeamScreen.tsx +0 -464
  36. package/template/src/screens/teams/MyTeamDetailsScreen.tsx +0 -979
  37. package/template/src/screens/teams/MyTeamScreen.tsx +0 -568
  38. package/template/src/screens/teams/PendingRequestsScreen.tsx +0 -426
  39. package/template/src/screens/volunteerOpportunities/SetReminderScreen.tsx +0 -631
  40. package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesDetailsScreen.tsx +0 -1049
  41. package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesScreen.tsx +0 -608
  42. package/template/src/utils/ClubSearchManager.ts +0 -222
@@ -1,1049 +0,0 @@
1
- import React, { useMemo, useRef, useState } from 'react';
2
- import {
3
- View,
4
- Text,
5
- StyleSheet,
6
- StatusBar,
7
- Platform,
8
- TouchableOpacity,
9
- Image,
10
- ScrollView,
11
- Dimensions,
12
- Animated,
13
- Modal,
14
- PanResponder,
15
- ActivityIndicator,
16
- FlatList,
17
- RefreshControl,
18
- } from 'react-native';
19
- import { SafeAreaView } from 'react-native-safe-area-context';
20
- import { VolunteerOpportunitiesDetailsScreenProps } from '../../types/navigation';
21
- import { theme } from '../../constants';
22
- import { moderateScale } from '../../utils/scaling';
23
- import { Fonts } from '../../constants/Fonts';
24
- import SVG from '../../assets/icons';
25
- import {
26
- useVolunteerOpportunityDetails,
27
- useVolunteerOpportunityVolunteers,
28
- useJoinVolunteerOpportunity,
29
- useVolunteerSlots,
30
- useCancelVolunteerRequest,
31
- VolunteerOpportunitySlot,
32
- } from '../../services/mainServices';
33
- import { Button } from '../../components/common';
34
- import ToastManager from '../../components/common/ToastManager';
35
- import { useFocusEffect } from '@react-navigation/native';
36
-
37
- // Helper: Extract nested arrays from various data structures
38
- const getFirstPopulatedArray = (...candidates: unknown[]): any[] => {
39
- for (const candidate of candidates) {
40
- if (Array.isArray(candidate) && candidate.length > 0) {
41
- return candidate;
42
- }
43
-
44
- if (candidate && typeof candidate === 'object') {
45
- const casted = candidate as Record<string, unknown>;
46
- const nestedSlots =
47
- casted.slots ??
48
- casted.lstSlots ??
49
- casted.data ??
50
- casted.items ??
51
- casted.list;
52
-
53
- if (Array.isArray(nestedSlots) && nestedSlots.length > 0) {
54
- return nestedSlots;
55
- }
56
- }
57
- }
58
-
59
- return [];
60
- };
61
-
62
- export const VolunteerOpportunitiesDetailsScreen: React.FC<
63
- VolunteerOpportunitiesDetailsScreenProps
64
- > = ({ navigation, route }) => {
65
- const { selectedClub, item: itemDetails } = route?.params ?? {};
66
-
67
- // Extract identifiers
68
- const teamId = selectedClub?.id;
69
- const team = selectedClub;
70
- const opportunityId = itemDetails?.id;
71
- const opportunityType = itemDetails?.opportunityType;
72
- const clubId = selectedClub?.id;
73
- const memberId = selectedClub?.selectedMember?.id;
74
-
75
- // Mutations
76
- const joinOpportunityMutation = useJoinVolunteerOpportunity();
77
- const cancelVolunteerRequestMutation = useCancelVolunteerRequest();
78
-
79
- // State
80
- const [selectedSlot, setSelectedSlot] = useState<VolunteerOpportunitySlot | null>(null);
81
- const [isSlotModalVisible, setIsSlotModalVisible] = useState(false);
82
- const [isRefetching, setIsRefetching] = useState(false);
83
- const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false);
84
-
85
- // Bottom sheet animation
86
- const screenHeight = Dimensions.get('window').height;
87
- const bottomSheetHeight = screenHeight * 0.45;
88
- const bottomSheetTranslateY = useRef(new Animated.Value(bottomSheetHeight)).current;
89
-
90
-
91
-
92
-
93
- // Queries
94
- const {
95
- data: volunteersResponse,
96
- isLoading: isVolunteersLoading,
97
- refetch: refetchVolunteers,
98
- } = useVolunteerOpportunityVolunteers(opportunityId, selectedSlot?.slotId);
99
- const {
100
- data: opportunityResponse,
101
- isLoading,
102
- error,
103
- refetch: refetchOpportunity,
104
- } = useVolunteerOpportunityDetails(opportunityId);
105
- const {
106
- data: slotsResponse,
107
- isLoading: isSlotsLoading,
108
- error: slotsError,
109
- refetch: refetchSlots,
110
- } = useVolunteerSlots(opportunityId, memberId);
111
-
112
- // Computed values
113
- const opportunityDetails = opportunityResponse?.data?.data;
114
- const details = useMemo(
115
- () => opportunityDetails ?? itemDetails ?? {},
116
- [opportunityDetails, itemDetails]
117
- );
118
-
119
-
120
- useFocusEffect(
121
- React.useCallback(() => {
122
- handleRefetchAllData();
123
- }, [])
124
- );
125
-
126
-
127
- // Process slots
128
- const slots = slotsResponse?.data?.data|| [];
129
-
130
- // Process volunteers
131
- const volunteersDetails = volunteersResponse?.data?.data || [];
132
-
133
-
134
- // Navigation helper
135
- const createNavigationData = () => ({
136
- selectedClub,
137
- selectedMember: selectedClub?.selectedMember,
138
- item: itemDetails,
139
- });
140
-
141
- // Handler: Join opportunity
142
- const handleJoinVolunteerOpportunity = async (volunteer: any) => {
143
- const slotId = Number(volunteer?.slotId ?? 0);
144
- const opType = Number(itemDetails?.opportunityType ?? 1);
145
-
146
- if (!opportunityId || Number.isNaN(opportunityId)) {
147
- ToastManager.error('Opportunity ID is missing');
148
- return;
149
- }
150
-
151
- try {
152
- const response = await joinOpportunityMutation.mutateAsync({
153
- opportunityId,
154
- slotId,
155
- opportunityType: opType,
156
- memberId,
157
- });
158
-
159
- ToastManager.success(
160
- response.data?.message || 'Successfully joined volunteer opportunity'
161
- );
162
- await handleRefreshAllData();
163
- } catch (error: any) {
164
- const errorMessage =
165
- error?.response?.data?.message ||
166
- error?.message ||
167
- 'Failed to join opportunity';
168
- ToastManager.error(errorMessage);
169
- }
170
- };
171
-
172
- // Handler: Cancel request
173
- const handleCancelVolunteerRequest = async (volunteer: any) => {
174
- const slotId = Number(volunteer?.slotId ?? 0);
175
-
176
- if (!opportunityId || Number.isNaN(opportunityId)) {
177
- ToastManager.error('Opportunity ID is missing');
178
- return;
179
- }
180
-
181
- if (!memberId || Number.isNaN(memberId)) {
182
- ToastManager.error('Member ID is missing');
183
- return;
184
- }
185
-
186
- if (!slotId || Number.isNaN(slotId)) {
187
- ToastManager.error('Slot ID is missing');
188
- return;
189
- }
190
-
191
- try {
192
- const response = await cancelVolunteerRequestMutation.mutateAsync({
193
- opportunityId,
194
- memberId,
195
- slotId,
196
- });
197
-
198
- ToastManager.success(
199
- response.data?.message || 'Volunteer request cancelled successfully'
200
- );
201
- await refetchSlots();
202
- } catch (error: any) {
203
- const errorMessage =
204
- error?.response?.data?.message ||
205
- error?.message ||
206
- 'Failed to cancel volunteer request';
207
- ToastManager.error(errorMessage);
208
- console.error('Cancel volunteer request error:', error);
209
- }
210
- };
211
-
212
- // Modal: Close sheet
213
- const closeSlotSelectionSheet = () => {
214
- Animated.timing(bottomSheetTranslateY, {
215
- toValue: bottomSheetHeight,
216
- duration: 300,
217
- useNativeDriver: true,
218
- }).start(() => {
219
- setIsSlotModalVisible(false);
220
- setSelectedSlot(null);
221
- bottomSheetTranslateY.setValue(bottomSheetHeight);
222
- });
223
- };
224
-
225
- // Modal: Open sheet
226
- const openSlotSelectionSheet = (volunteer: any) => {
227
- setSelectedSlot(volunteer ?? null);
228
- setIsSlotModalVisible(true);
229
- refetchVolunteers()
230
- bottomSheetTranslateY.setValue(bottomSheetHeight);
231
- Animated.timing(bottomSheetTranslateY, {
232
- toValue: 0,
233
- duration: 300,
234
- useNativeDriver: true,
235
- }).start();
236
- };
237
-
238
- // PanResponder: Swipe gestures
239
- const slotSelectionPanResponder = useRef(
240
- PanResponder.create({
241
- onMoveShouldSetPanResponder: (_, gestureState) =>
242
- Math.abs(gestureState.dy) > 5,
243
- onPanResponderGrant: () => {
244
- bottomSheetTranslateY.extractOffset();
245
- },
246
- onPanResponderMove: (_, gestureState) => {
247
- if (gestureState.dy > 0) {
248
- bottomSheetTranslateY.setValue(gestureState.dy);
249
- }
250
- },
251
- onPanResponderRelease: (_, gestureState) => {
252
- bottomSheetTranslateY.flattenOffset();
253
-
254
- if (gestureState.dy > 100 || gestureState.vy > 0.5) {
255
- closeSlotSelectionSheet();
256
- } else {
257
- Animated.spring(bottomSheetTranslateY, {
258
- toValue: 0,
259
- useNativeDriver: true,
260
- tension: 100,
261
- friction: 8,
262
- }).start();
263
- }
264
- },
265
- })
266
- ).current;
267
-
268
- // Render volunteer item
269
- const renderSlotItem = ({ item }: { item: any }) => (
270
- <View style={styles.volunteerItemContainer}>
271
- <View style={styles.volunteerAvatarSection}>
272
- {item?.profileImage ? (
273
- <Image
274
- source={{ uri: item?.profileImage }}
275
- style={styles.volunteerModalAvatar}
276
- resizeMode="cover"
277
- />
278
- ) : (
279
- <SVG.emptyUser style={{ marginBottom: theme.spacing.xs }} width={moderateScale(50)} height={moderateScale(50)} />
280
- )}
281
- </View>
282
- <Text style={styles.volunteerName}>{item?.memberName}</Text>
283
- </View>
284
- );
285
-
286
- // Refresh all data
287
- const handleRefreshAllData = async () => {
288
- setIsRefetching(true);
289
- try {
290
- await Promise.all([refetchSlots(), refetchOpportunity(), refetchVolunteers()]);
291
- } catch (error: any) {
292
- const message =
293
- error?.response?.data?.message ||
294
- error?.message ||
295
- 'Failed to refresh data';
296
- ToastManager.error('Refresh Error', message);
297
- console.error('Error refreshing all data:', error);
298
- } finally {
299
- setIsRefetching(false);
300
- }
301
- };
302
-
303
- const handleRefetchAllData = async () => {
304
- try {
305
- await Promise.all([refetchSlots(), refetchOpportunity(), refetchVolunteers()]);
306
- } catch (error: any) {
307
- const message =
308
- error?.response?.data?.message ||
309
- error?.message ||
310
- 'Failed to refresh data';
311
- ToastManager.error('Refresh Error', message);
312
- console.error('Error refreshing all data:', error);
313
- }
314
-
315
- };
316
-
317
- // Refresh slots only
318
- const handleRefreshSlots = async () => {
319
- setIsRefetching(true);
320
- try {
321
- await refetchSlots();
322
- } catch (error: any) {
323
- const message =
324
- error?.response?.data?.message ||
325
- error?.message ||
326
- 'Failed to refresh slots';
327
- ToastManager.error('Refresh Error', message);
328
- console.error('Error refreshing slots:', error);
329
- } finally {
330
- setIsRefetching(false);
331
- }
332
- };
333
-
334
- const descriptionText = opportunityDetails?.description || 'No description available for this opportunity yet.';
335
- const shouldShowReadMore = descriptionText.length > 200;
336
-
337
- return (
338
- <View style={styles.container}>
339
- <StatusBar
340
- barStyle="light-content"
341
- backgroundColor={theme.colors.blue}
342
- translucent={Platform.OS === 'android'}
343
- />
344
- {Platform.OS === 'ios' && <View style={styles.statusBarBackground} />}
345
- <SafeAreaView style={styles.safeArea}>
346
- <View style={styles.header}>
347
- <View style={styles.navRow}>
348
- <TouchableOpacity
349
- onPress={() => navigation.goBack()}
350
- hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
351
- >
352
- <SVG.arrowLeft_white
353
- width={moderateScale(25)}
354
- height={moderateScale(25)}
355
- />
356
- </TouchableOpacity>
357
- <View style={styles.clubInfoContainer}>
358
- <View style={styles.userConSty}>
359
- {selectedClub?.clubImage ? (
360
- <Image
361
- source={{ uri: selectedClub.clubImage }}
362
- style={styles.userDetailsSty}
363
- resizeMode="cover"
364
- />
365
- ) : (
366
- <View style={styles.placeholderLogoHeader}>
367
- <SVG.UsersIcon
368
- width={moderateScale(20)}
369
- height={moderateScale(20)}
370
- />
371
- </View>
372
- )}
373
- </View>
374
- <Text style={styles.userNameSty}>
375
- {details?.clubName ?? 'Unknown Team'}
376
- </Text>
377
- </View>
378
- </View>
379
- <View style={styles.headerMain}>
380
- <Text style={styles.headerTitle}>
381
- {details?.title ?? 'Volunteer Opportunity'}
382
- </Text>
383
- <View style={styles.opMetaRow}>
384
- <SVG.locationWhite
385
- width={moderateScale(18)}
386
- height={moderateScale(18)}
387
- />
388
- <Text style={styles.opMetaText}>{details?.address}</Text>
389
- </View>
390
- {itemDetails?.scheduleDate && (
391
- <View style={styles.opMetaRow}>
392
- <SVG.CalendarWhite
393
- width={moderateScale(18)}
394
- height={moderateScale(18)}
395
- />
396
- <Text style={styles.opMetaText}>
397
- {itemDetails.scheduleDate}
398
- </Text>
399
- </View>
400
- )}
401
- </View>
402
- </View>
403
-
404
- <View style={styles.content}>
405
- {isLoading ? (
406
- <View style={styles.loadingContainer}>
407
- <Text style={styles.emptyText}>
408
- Loading opportunity details...
409
- </Text>
410
- </View>
411
- ) : error ? (
412
- <View style={styles.loadingContainer}>
413
- <Text style={styles.errorText}>
414
- Error loading opportunity details.
415
- </Text>
416
- </View>
417
- ) : (
418
- <ScrollView
419
- style={styles.scrollView}
420
- contentContainerStyle={styles.scrollContent}
421
- showsVerticalScrollIndicator={false}
422
- refreshControl={
423
- <RefreshControl
424
- refreshing={isRefetching}
425
- onRefresh={handleRefreshAllData}
426
- tintColor={theme.colors.blue}
427
- progressBackgroundColor={theme.colors.background}
428
- />
429
- }
430
- >
431
- <View style={styles.section}>
432
- <Text style={styles.sectionTitle}>Description</Text>
433
- <Text style={styles.descriptionText}>
434
- {isDescriptionExpanded ? descriptionText : descriptionText.slice(0, 200) + (shouldShowReadMore ? '...' : '')}
435
- {shouldShowReadMore && (
436
- <Text
437
- onPress={() => setIsDescriptionExpanded(!isDescriptionExpanded)}
438
- style={{ color: theme.colors.blue, fontFamily: Fonts.outfitMedium }}
439
- >
440
- {isDescriptionExpanded ? ' Show Less' : ' Show More'}
441
- </Text>
442
- )}
443
- </Text>
444
- </View>
445
-
446
- {details?.isExpired ? null : (
447
-
448
-
449
- <View style={styles.section}>
450
- <Text style={styles.sectionTitle}>Quick Actions</Text>
451
- <View style={styles.quickActionsRow}>
452
- <Button
453
- title="Manage Reminders"
454
- onPress={() => {
455
- navigation.navigate('SetReminder', createNavigationData());
456
- }}
457
- variant="outline"
458
- textStyle={styles.setReminderButtonText}
459
- size="small"
460
- />
461
- </View>
462
- </View>
463
- )}
464
-
465
- <View style={styles.section}>
466
- <View style={styles.sectionHeaderRow}>
467
- <Text style={styles.listTitle}>Volunteers</Text>
468
- </View>
469
- <View style={styles.volunteerList}>
470
- {isSlotsLoading ? (
471
- <View style={styles.loadingContainer}>
472
- <Text style={styles.emptyStateText}>
473
- Loading slots...
474
- </Text>
475
- <ActivityIndicator
476
- size="small"
477
- color={theme.colors.primary}
478
- />
479
- </View>
480
- ) : slotsError ? (
481
- <View style={styles.loadingContainer}>
482
- <Text style={styles.errorText}>
483
- Failed to load slots. Please try again.
484
- </Text>
485
- <TouchableOpacity
486
- onPress={handleRefreshSlots}
487
- style={styles.retryButtonContainer}
488
- >
489
- <Text style={styles.retryButtonText}>Retry</Text>
490
- </TouchableOpacity>
491
- </View>
492
- ) : slots.length > 0 ? (
493
- slots.map((volunteer: any) => (
494
- <View key={volunteer.id} style={styles.volunteerCard}>
495
- <View style={styles.slotHeaderRow}>
496
- <Text style={styles.volunteerSlot}>
497
- {volunteer?.slotStartTime} -{' '}
498
- {volunteer?.slotEndTime}
499
- </Text>
500
- {volunteer?.applicationStatus === 1 ? (
501
- <SVG.rightAppleGreen
502
- style={styles.statusIconMargin}
503
- width={moderateScale(25)}
504
- height={moderateScale(25)}
505
- />
506
- ) : volunteer?.applicationStatus === 2 ? (
507
- <SVG.PendingAppleGreen
508
- style={styles.statusIconMargin}
509
- width={moderateScale(20)}
510
- height={moderateScale(20)}
511
- />
512
- ) : null}
513
- </View>
514
- <View style={styles.volunteerActionRow}>
515
- {opportunityType === 1 ? (
516
- <>
517
- {volunteer?.applicationStatus === 3 ? (
518
- <>
519
- {!volunteer?.isExpired && (
520
- <TouchableOpacity
521
- disabled={
522
- volunteer?.isExpired
523
- }
524
- onPress={() =>
525
- handleJoinVolunteerOpportunity(volunteer)
526
- }
527
- style={styles.joinButton}
528
- >
529
- <Text style={styles.joinButtonText}>
530
- Join
531
- </Text>
532
- </TouchableOpacity>
533
- )}
534
- </>
535
- ) : (
536
- <TouchableOpacity
537
- onPress={() => {
538
- navigation.navigate('ChatScreen', {
539
- itemDetails: volunteer,
540
- memberId,
541
- teamId: volunteer?.opportunityId, //this is opportunityId
542
- team,
543
- userType: 'volunteer',
544
- });
545
- }}
546
- >
547
- {volunteer?.unreadMessageCount > 0 ? (
548
- <SVG.UNREAD_CHAT_BLUE_BG width={30} height={30} />
549
- ) : (
550
- <SVG.chatBlueBG width={30} height={30} />
551
- )}
552
- </TouchableOpacity>
553
- )}
554
- </>
555
- ) : (
556
- <>
557
- {volunteer?.applicationStatus === 3 ? (
558
- <>
559
- {!volunteer?.isExpired && (
560
- <TouchableOpacity
561
- disabled={
562
- volunteer?.isExpired
563
- }
564
- onPress={() =>
565
- handleJoinVolunteerOpportunity(volunteer)
566
- }
567
- style={styles.joinButton}
568
- >
569
- <Text style={styles.joinButtonText}>
570
- Join
571
- </Text>
572
- </TouchableOpacity>
573
- )}
574
- </>
575
- ) : volunteer?.applicationStatus === 2 ? (
576
- <>
577
- {volunteer?.isExpired ? null : (
578
- <TouchableOpacity
579
- onPress={() =>
580
- handleCancelVolunteerRequest(volunteer)
581
- }
582
- disabled={
583
- volunteer?.isExpired
584
- }
585
- style={[
586
- styles.cancelButton,
587
- cancelVolunteerRequestMutation.isPending &&
588
- styles.cancelButtonDisabled,
589
- ]}
590
- >
591
- <Text
592
- style={[
593
- styles.cancelButtonText,
594
- cancelVolunteerRequestMutation.isPending &&
595
- styles.cancelButtonTextDisabled,
596
- ]}
597
- >
598
- {cancelVolunteerRequestMutation.isPending
599
- ? 'Canceling...'
600
- : 'Cancel'}
601
- </Text>
602
- </TouchableOpacity>
603
- )}
604
- </>
605
- ) : (
606
- <TouchableOpacity
607
- onPress={() => {
608
- navigation.navigate('ChatScreen', {
609
- itemDetails: volunteer,
610
- memberId,
611
- teamId: volunteer?.opportunityId, //this is opportunityId
612
- team,
613
- userType: 'volunteer',
614
- });
615
- }}
616
- >
617
- {volunteer?.unreadMessageCount > 0 ? (
618
- <SVG.UNREAD_CHAT_BLUE_BG width={30} height={30} />
619
- ) : (
620
- <SVG.chatBlueBG width={30} height={30} />
621
- )}
622
- </TouchableOpacity>
623
- )}
624
- </>
625
- )}
626
- <TouchableOpacity
627
- onPress={() =>
628
- openSlotSelectionSheet(volunteer)
629
- }
630
- >
631
- <SVG.volunteerMember
632
- style={styles.volunteerMemberIconMargin}
633
- width={moderateScale(30)}
634
- height={moderateScale(30)}
635
- />
636
- </TouchableOpacity>
637
- </View>
638
- </View>
639
- ))
640
- ) : (
641
- <View style={styles.emptyState}>
642
- <Text style={styles.emptyStateText}>
643
- No slots available for this opportunity yet.
644
- </Text>
645
- </View>
646
- )}
647
- </View>
648
- </View>
649
- </ScrollView>
650
- )}
651
- </View>
652
-
653
- {/* Volunteer Members Modal */}
654
- <Modal
655
- visible={isSlotModalVisible}
656
- animationType="fade"
657
- transparent
658
- onRequestClose={closeSlotSelectionSheet}
659
- >
660
- <View style={styles.modalOverlay}>
661
- <TouchableOpacity
662
- style={styles.modalBackdrop}
663
- activeOpacity={1}
664
- onPress={closeSlotSelectionSheet}
665
- />
666
- <Animated.View
667
- style={[
668
- styles.modalContainer,
669
- {
670
- transform: [{ translateY: bottomSheetTranslateY }],
671
- },
672
- ]}
673
- >
674
- <View
675
- style={styles.dragHandle}
676
- {...slotSelectionPanResponder.panHandlers}
677
- />
678
- {isVolunteersLoading ? (
679
- <View style={styles.slotLoadingContainer}>
680
- <ActivityIndicator
681
- size="small"
682
- color={theme.colors.primary}
683
- />
684
- </View>
685
- ) : (
686
- <FlatList
687
- data={volunteersDetails}
688
- renderItem={renderSlotItem}
689
- style={styles.membersList}
690
- showsVerticalScrollIndicator={false}
691
- ListHeaderComponent={() => (
692
- <View style={styles.headerContainer}>
693
- <Text style={styles.mamberHeaderTitle}>
694
- Joined Volunteers
695
- </Text>
696
- <View style={styles.slotTimeRow}>
697
- <SVG.Clock_Blue
698
- width={moderateScale(28)}
699
- height={moderateScale(28)}
700
- />
701
- <Text style={styles.slotTimeHeaderText}>
702
- {selectedSlot?.slotStartTime} -{' '}
703
- {selectedSlot?.slotEndTime}
704
- </Text>
705
- </View>
706
- </View>
707
- )}
708
- ListEmptyComponent={() => (
709
- <View style={styles.emptyContainer}>
710
- <Text style={styles.emptyText}>
711
- No volunteers available
712
- </Text>
713
- </View>
714
- )}
715
- keyExtractor={(item) => `volunteer-${item.id}`}
716
- />
717
- )}
718
- </Animated.View>
719
- </View>
720
- </Modal>
721
- </SafeAreaView>
722
- </View>
723
- );
724
- };
725
-
726
- const styles = StyleSheet.create({
727
- container: {
728
- flex: 1,
729
- backgroundColor: theme.colors.blue,
730
- },
731
- statusBarBackground: {
732
- position: 'absolute',
733
- top: 0,
734
- left: 0,
735
- right: 0,
736
- height: Platform.OS === 'ios' ? 44 : 0,
737
- backgroundColor: theme.colors.blue,
738
- zIndex: 1000,
739
- },
740
- safeArea: {
741
- flex: 1,
742
- backgroundColor: theme.colors.blue,
743
- },
744
- header: {
745
- paddingBottom: theme.spacing.lg,
746
- backgroundColor: theme.colors.blue,
747
- },
748
- navRow: {
749
- flexDirection: 'row',
750
- alignItems: 'center',
751
- paddingHorizontal: theme.spacing.lg,
752
- },
753
- clubInfoContainer: {
754
- flexDirection: 'row',
755
- alignItems: 'center',
756
- },
757
- userConSty: {
758
- marginHorizontal: moderateScale(10),
759
- width: moderateScale(30),
760
- height: moderateScale(30),
761
- borderRadius: moderateScale(15),
762
- borderWidth: 1.5,
763
- borderColor: theme.colors.imageBorder,
764
- backgroundColor: theme.colors.background,
765
- alignItems: 'center',
766
- justifyContent: 'center',
767
- },
768
- placeholderLogoHeader: {
769
- width: moderateScale(24),
770
- height: moderateScale(24),
771
- borderRadius: moderateScale(12),
772
- alignItems: 'center',
773
- justifyContent: 'center',
774
- },
775
- userDetailsSty: {
776
- width: moderateScale(30),
777
- height: moderateScale(30),
778
- borderRadius: moderateScale(15),
779
- resizeMode: 'cover',
780
- },
781
- userNameSty: {
782
- color: theme.colors.white,
783
- fontFamily: Fonts.outfitMedium,
784
- fontSize: moderateScale(15),
785
- },
786
- headerMain: {
787
- paddingHorizontal: theme.spacing.lg,
788
- marginTop: theme.spacing.sm,
789
- },
790
- headerTitle: {
791
- fontFamily: Fonts.outfitSemiBold,
792
- fontSize: moderateScale(24),
793
- color: theme.colors.white,
794
- marginBottom: theme.spacing.sm,
795
- },
796
- opMetaRow: {
797
- flexDirection: 'row',
798
- alignItems: 'center',
799
- marginBottom: theme.spacing.xs,
800
- },
801
- opMetaText: {
802
- marginLeft: theme.spacing.xs,
803
- fontFamily: Fonts.outfitRegular,
804
- fontSize: theme.typography.fontSize.xs,
805
- color: 'rgba(255, 255, 255, 0.85)',
806
- },
807
- content: {
808
- flex: 1,
809
- backgroundColor: theme.colors.background,
810
- borderTopLeftRadius: moderateScale(28),
811
- borderTopRightRadius: moderateScale(28),
812
- paddingTop: theme.spacing.lg,
813
- },
814
- scrollView: {
815
- flex: 1,
816
- },
817
- scrollContent: {
818
- paddingHorizontal: theme.spacing.lg,
819
- paddingBottom: theme.spacing.xl,
820
- },
821
- section: {
822
- marginBottom: theme.spacing.lg,
823
- },
824
- sectionHeaderRow: {
825
- flexDirection: 'row',
826
- alignItems: 'center',
827
- justifyContent: 'space-between',
828
- marginBottom: theme.spacing.sm,
829
- },
830
- sectionTitle: {
831
- fontFamily: Fonts.outfitSemiBold,
832
- fontSize: theme.typography.fontSize.sm,
833
- color: theme.colors.black,
834
- },
835
- listTitle: {
836
- fontFamily: Fonts.outfitSemiBold,
837
- fontSize: theme.typography.fontSize.sm,
838
- color: theme.colors.blue,
839
- },
840
- descriptionText: {
841
- marginTop: theme.spacing.xs,
842
- fontFamily: Fonts.outfitRegular,
843
- fontSize: theme.typography.fontSize.sm,
844
- color: theme.colors.paraText,
845
- lineHeight: moderateScale(22),
846
- },
847
- quickActionsRow: {
848
- marginTop: theme.spacing.sm,
849
- gap: theme.spacing.md,
850
- },
851
- setReminderButtonText: {
852
- color: theme.colors.appleGreen,
853
- fontWeight: theme.typography.fontWeight.semibold,
854
- },
855
- volunteerList: {},
856
- volunteerCard: {
857
- flexDirection: 'row',
858
- alignItems: 'center',
859
- justifyContent: 'space-between',
860
- borderRadius: moderateScale(18),
861
- paddingHorizontal: theme.spacing.sm,
862
- paddingVertical: theme.spacing.sm,
863
- marginBottom: theme.spacing.sm,
864
- backgroundColor: theme.colors.lightLavenderGray,
865
- },
866
- slotHeaderRow: {
867
- flexDirection: 'row',
868
- alignItems: 'center',
869
- },
870
- volunteerSlot: {
871
- fontFamily: Fonts.outfitMedium,
872
- fontSize: theme.typography.fontSize.sm,
873
- color: theme.colors.black,
874
- },
875
- statusIconMargin: {
876
- marginLeft: theme.spacing.xs,
877
- },
878
- volunteerActionRow: {
879
- alignItems: 'center',
880
- flexDirection: 'row',
881
- justifyContent: 'flex-end',
882
- },
883
- joinButton: {
884
- alignItems: 'center',
885
- justifyContent: 'center',
886
- borderWidth: 1,
887
- borderColor: theme.colors.appleGreen,
888
- borderRadius: theme.borderRadius.xxl,
889
- paddingHorizontal: theme.spacing.md,
890
- paddingVertical: theme.spacing.xs,
891
- },
892
- joinButtonText: {
893
- color: theme.colors.appleGreen,
894
- fontFamily: Fonts.outfitMedium,
895
- fontSize: theme.typography.fontSize.xs,
896
- },
897
- cancelButton: {
898
- alignItems: 'center',
899
- justifyContent: 'center',
900
- borderWidth: 1,
901
- borderColor: theme.colors.red,
902
- borderRadius: theme.borderRadius.xxl,
903
- paddingHorizontal: theme.spacing.md,
904
- paddingVertical: theme.spacing.xs,
905
- },
906
- cancelButtonDisabled: {
907
- borderColor: theme.colors.DarkGray,
908
- opacity: 0.6,
909
- },
910
- cancelButtonText: {
911
- color: theme.colors.red,
912
- fontFamily: Fonts.outfitMedium,
913
- fontSize: theme.typography.fontSize.xs,
914
- },
915
- cancelButtonTextDisabled: {
916
- color: theme.colors.DarkGray,
917
- },
918
- volunteerMemberIconMargin: {
919
- marginLeft: theme.spacing.xs,
920
- marginTop: moderateScale(2),
921
- },
922
- retryButtonContainer: {
923
- marginTop: theme.spacing.sm,
924
- },
925
- retryButtonText: {
926
- color: theme.colors.blue,
927
- textDecorationLine: 'underline',
928
- },
929
- emptyState: {
930
- paddingVertical: theme.spacing.lg,
931
- alignItems: 'center',
932
- justifyContent: 'center',
933
- },
934
- emptyStateText: {
935
- fontFamily: Fonts.outfitMedium,
936
- fontSize: theme.typography.fontSize.sm,
937
- color: theme.colors.DarkGray,
938
- marginBottom: theme.spacing.sm,
939
- },
940
- loadingContainer: {
941
- flex: 1,
942
- justifyContent: 'center',
943
- alignItems: 'center',
944
- paddingHorizontal: theme.spacing.lg,
945
- },
946
- emptyText: {
947
- fontFamily: Fonts.outfitMedium,
948
- fontSize: theme.typography.fontSize.md,
949
- color: theme.colors.DarkGray,
950
- textAlign: 'center',
951
- },
952
- errorText: {
953
- fontFamily: Fonts.outfitMedium,
954
- fontSize: theme.typography.fontSize.md,
955
- color: theme.colors.cancelButton,
956
- marginTop: theme.spacing.md,
957
- textAlign: 'center',
958
- },
959
- modalOverlay: {
960
- flex: 1,
961
- justifyContent: 'flex-end',
962
- },
963
- modalBackdrop: {
964
- position: 'absolute',
965
- top: 0,
966
- left: 0,
967
- right: 0,
968
- bottom: 0,
969
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
970
- },
971
- modalContainer: {
972
- backgroundColor: theme.colors.white,
973
- borderTopLeftRadius: moderateScale(20),
974
- borderTopRightRadius: moderateScale(20),
975
- paddingHorizontal: theme.spacing.lg,
976
- paddingTop: theme.spacing.sm,
977
- paddingBottom: theme.spacing.xl,
978
- height: '45%',
979
- maxHeight: '45%',
980
- },
981
- dragHandle: {
982
- width: moderateScale(40),
983
- height: moderateScale(4),
984
- backgroundColor: theme.colors.border,
985
- borderRadius: moderateScale(2),
986
- alignSelf: 'center',
987
- marginBottom: theme.spacing.sm,
988
- },
989
- slotLoadingContainer: {
990
- flex: 1,
991
- justifyContent: 'center',
992
- alignItems: 'center',
993
- paddingVertical: theme.spacing.lg,
994
- },
995
- membersList: {
996
- flex: 1,
997
- },
998
- headerContainer: {
999
- paddingVertical: theme.spacing.xs,
1000
- borderBottomColor: theme.colors.lightLavenderGray,
1001
- marginBottom: theme.spacing.sm,
1002
- },
1003
- mamberHeaderTitle: {
1004
- fontFamily: Fonts.outfitBold,
1005
- fontSize: moderateScale(18),
1006
- color: theme.colors.text,
1007
- marginBottom: theme.spacing.xs,
1008
- },
1009
- slotTimeRow: {
1010
- flexDirection: 'row',
1011
- alignItems: 'center',
1012
- },
1013
- slotTimeHeaderText: {
1014
- color: theme.colors.black,
1015
- marginLeft: theme.spacing.sm,
1016
- fontFamily: Fonts.outfitMedium,
1017
- fontSize: theme.typography.fontSize.md,
1018
- },
1019
- volunteerItemContainer: {
1020
- backgroundColor: theme.colors.white,
1021
- flexDirection: 'row',
1022
- alignItems: 'center',
1023
- borderBottomWidth: 1,
1024
- borderBottomColor: theme.colors.border,
1025
- paddingBottom: theme.spacing.xs,
1026
- paddingTop: theme.spacing.sm,
1027
- },
1028
- volunteerAvatarSection: {
1029
- alignItems: 'center',
1030
- marginRight: theme.spacing.md,
1031
- },
1032
- volunteerModalAvatar: {
1033
- width: moderateScale(50),
1034
- height: moderateScale(50),
1035
- borderRadius: moderateScale(25),
1036
- marginBottom: theme.spacing.xs,
1037
- },
1038
- volunteerName: {
1039
- fontFamily: Fonts.outfitSemiBold,
1040
- fontSize: theme.typography.fontSize.md,
1041
- color: theme.colors.text,
1042
- flex: 1,
1043
- },
1044
- emptyContainer: {
1045
- paddingVertical: theme.spacing.xl,
1046
- alignItems: 'center',
1047
- marginTop: theme.spacing.xl,
1048
- },
1049
- });