create-gufran-expo-app 2.0.4 → 2.0.6

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 (43) hide show
  1. package/README.md +60 -321
  2. package/bin/cli.js +0 -1
  3. package/package.json +4 -3
  4. package/template/src/navigation/AuthStack.tsx +6 -25
  5. package/template/src/navigation/MainStack.tsx +0 -148
  6. package/template/src/navigation/RootNavigator.tsx +4 -26
  7. package/template/src/navigation/index.ts +0 -1
  8. package/template/src/navigation/navigationRef.ts +1 -1
  9. package/template/src/screens/HomeScreen.tsx +3 -215
  10. package/template/src/screens/auth/LoginScreen.tsx +13 -13
  11. package/template/src/screens/auth/index.ts +1 -6
  12. package/template/src/screens/index.ts +0 -35
  13. package/template/src/services/api.ts +5 -5
  14. package/template/src/services/authService.ts +3 -299
  15. package/template/src/services/mainServices.ts +19 -1914
  16. package/template/src/types/navigation.ts +5 -155
  17. package/template/src/utils/index.ts +5 -8
  18. package/template/src/navigation/MiddleStack.tsx +0 -35
  19. package/template/src/screens/auth/AddMamber.tsx +0 -428
  20. package/template/src/screens/auth/ForgotPasswordScreen.tsx +0 -176
  21. package/template/src/screens/auth/OTPVerifyScreen.tsx +0 -359
  22. package/template/src/screens/auth/RegisterScreen.tsx +0 -430
  23. package/template/src/screens/auth/SuccessScreen.tsx +0 -201
  24. package/template/src/screens/chat/ChatScreen.tsx +0 -1819
  25. package/template/src/screens/chat/ChatThreadsScreen.tsx +0 -360
  26. package/template/src/screens/chat/ReportMessageScreen.tsx +0 -238
  27. package/template/src/screens/clubs/Announcements.tsx +0 -426
  28. package/template/src/screens/clubs/BuyRaffleTicketsScreen.tsx +0 -568
  29. package/template/src/screens/clubs/ClubDeteils.tsx +0 -497
  30. package/template/src/screens/clubs/JoinClub.tsx +0 -841
  31. package/template/src/screens/events/EventScreen.tsx +0 -460
  32. package/template/src/screens/raffles/MyReferralMembersScreen.tsx +0 -758
  33. package/template/src/screens/raffles/RaffleDetailsScreen.tsx +0 -762
  34. package/template/src/screens/raffles/RafflesScreen.tsx +0 -495
  35. package/template/src/screens/raffles/SetRaffleReminderScreen.tsx +0 -390
  36. package/template/src/screens/teams/JoinTeamScreen.tsx +0 -464
  37. package/template/src/screens/teams/MyTeamDetailsScreen.tsx +0 -979
  38. package/template/src/screens/teams/MyTeamScreen.tsx +0 -568
  39. package/template/src/screens/teams/PendingRequestsScreen.tsx +0 -426
  40. package/template/src/screens/volunteerOpportunities/SetReminderScreen.tsx +0 -631
  41. package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesDetailsScreen.tsx +0 -1049
  42. package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesScreen.tsx +0 -608
  43. 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
- });