theme update latest project
This commit is contained in:
+126
-209
@@ -1,23 +1,20 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
SafeAreaView,
|
||||
Image,
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
RefreshControl,
|
||||
} from 'react-native';
|
||||
import { Ionicons } from "@react-native-vector-icons/ionicons"; // Assuming you use Expo or have vector icons installed
|
||||
import { apiCall } from '../../utils/ApiHelper'; // Adjust path to your apiCall utility
|
||||
import LucideIcon from '../../components/common/LucideIcon';
|
||||
import { responsiveFontSize } from 'react-native-responsive-dimensions';
|
||||
import AppView from '../../components/AppView';
|
||||
import colors from '../../components/colors';
|
||||
import { RouteProp, useRoute } from '@react-navigation/native';
|
||||
import { useTheme } from '../../hooks/ui/useTheme';
|
||||
import { useFetch } from '../../hooks/core/useFetch';
|
||||
import { BASE_URL } from '../../utils/ApiUrls';
|
||||
import FontsFamily from '../../constents/FontsFamily';
|
||||
import SkeletonLoader from '../../components/common/SkeletonLoader';
|
||||
|
||||
// --- Type Definitions ---
|
||||
interface Member {
|
||||
id: string;
|
||||
userId: number;
|
||||
@@ -31,270 +28,190 @@ interface Member {
|
||||
role: 'USER' | 'ORGANIZER';
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
message: string;
|
||||
status: string;
|
||||
statusCode: number;
|
||||
data: Member[];
|
||||
}
|
||||
type RootStackParamList = {
|
||||
Member: { groupId: string }; // or number, depending on your data type
|
||||
};
|
||||
const MemberScreen = ({ route }: any) => {
|
||||
const theme = useTheme();
|
||||
const { groupId } = route?.params || {};
|
||||
|
||||
type MemberScreenRouteProp = RouteProp<RootStackParamList, 'Member'>;
|
||||
const { data: membersRes, isLoading, refetch } = useFetch<any>(
|
||||
`${BASE_URL}fund/group/all/member?groupId=${groupId}`,
|
||||
{ enabled: !!groupId }
|
||||
);
|
||||
|
||||
interface Props {
|
||||
route: MemberScreenRouteProp;
|
||||
}
|
||||
const members: Member[] = membersRes?.data || [];
|
||||
|
||||
// --- Main Screen Component ---
|
||||
// This assumes the component receives `groupId` from a navigator (e.g., Tab Navigator)
|
||||
const MemberScreen = ({ route }: Props) => {
|
||||
|
||||
const { groupId } = route.params;
|
||||
const [members, setMembers] = useState<Member[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchMembers = useCallback(async () => {
|
||||
if (!groupId) {
|
||||
setError('Group ID is missing.');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await apiCall<ApiResponse>(
|
||||
`${BASE_URL}fund/group/all/member?groupId=${groupId}`,
|
||||
'GET'
|
||||
);
|
||||
|
||||
if (response && response.status === 'success') {
|
||||
setMembers(response.data);
|
||||
setError(null);
|
||||
} else {
|
||||
throw new Error(response?.message || 'Failed to fetch members.');
|
||||
}
|
||||
} catch (e: any) {
|
||||
setError(e.message || 'An unexpected network error occurred.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [groupId]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMembers();
|
||||
}, [fetchMembers]);
|
||||
|
||||
// --- Render Functions ---
|
||||
const renderItem = ({ item }: { item: Member }) => {
|
||||
const fullName = `${item.firstName} ${item.lastName || ''}`.trim();
|
||||
const initial = item.firstName ? item.firstName.charAt(0).toUpperCase() : '?';
|
||||
const isOrganizer = item.role === 'ORGANIZER';
|
||||
|
||||
return (
|
||||
|
||||
<TouchableOpacity style={styles.card}>
|
||||
<View style={styles.avatarPlaceholder}>
|
||||
<Text style={styles.avatarText}>{initial}</Text>
|
||||
<View style={[styles.card, { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }]}>
|
||||
<View style={[styles.avatarPlaceholder, { backgroundColor: theme.colors.background }]}>
|
||||
<Text style={[styles.avatarText, { color: theme.colors.textPrimary }]}>{initial}</Text>
|
||||
</View>
|
||||
<View style={styles.infoContainer}>
|
||||
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
||||
<Text style={styles.name}>{fullName}</Text>
|
||||
<View style={styles.nameRow}>
|
||||
<Text style={[styles.name, { color: theme.colors.textPrimary }]}>{fullName}</Text>
|
||||
{isOrganizer && (
|
||||
<View style={styles.organizerIcon}>
|
||||
<Text>Orginzer</Text>
|
||||
<View style={[styles.badge, { backgroundColor: theme.colors.primary }]}>
|
||||
<Text style={[styles.badgeText, { color: theme.colors.onPrimary }]}>Organizer</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.username}>@{item.username}</Text>
|
||||
|
||||
<Text style={[styles.username, { color: theme.colors.textSecondary }]}>@{item.username}</Text>
|
||||
<View style={styles.contactInfo}>
|
||||
<LucideIcon name="call-outline" size={12} color={theme.colors.textMuted} />
|
||||
<Text style={[styles.contactText, { color: theme.colors.textMuted }]}>{item.mobile}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (loading) {
|
||||
return (
|
||||
<View style={styles.centered}>
|
||||
<ActivityIndicator size="large" color="#4F46E5" />
|
||||
<Text style={styles.loadingText}>Loading Members...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<View style={styles.centered}>
|
||||
<Text style={styles.errorText}>no members found</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (members.length === 0) {
|
||||
return (
|
||||
<View style={styles.centered}>
|
||||
<Ionicons name="people-outline" size={60} color="#9CA3AF" />
|
||||
<Text style={styles.emptyTitle}>No Members Yet</Text>
|
||||
<Text style={styles.emptyText}>Invite people to join your group.</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={members}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={item => item.id}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const renderSkeletons = () => (
|
||||
<View style={styles.skeletonList}>
|
||||
{[1, 2, 3, 4, 5].map(i => (
|
||||
<SkeletonLoader key={i} width="100%" height={80} style={{ marginBottom: 12, borderRadius: 12 }} />
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppView>
|
||||
<AppView paddingHorizontal={0}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.headerTitle}>Group Members</Text>
|
||||
<Text style={styles.headerSubtitle}>{members.length} member(s)</Text>
|
||||
<Text style={[styles.headerTitle, { color: theme.colors.textPrimary }]}>Group Members</Text>
|
||||
<Text style={[styles.headerSubtitle, { color: theme.colors.textSecondary }]}>
|
||||
{members.length} member{members.length !== 1 ? 's' : ''} in this circle
|
||||
</Text>
|
||||
</View>
|
||||
{renderContent()}
|
||||
|
||||
{isLoading ? (
|
||||
renderSkeletons()
|
||||
) : (
|
||||
<FlatList
|
||||
data={members}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={item => item.id}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={isLoading} onRefresh={refetch} colors={[theme.colors.primary]} />
|
||||
}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyContainer}>
|
||||
<LucideIcon name="people-outline" size={64} color={theme.colors.textMuted} />
|
||||
<Text style={[styles.emptyTitle, { color: theme.colors.textPrimary }]}>No Members Yet</Text>
|
||||
<Text style={[styles.emptySubtitle, { color: theme.colors.textSecondary }]}>
|
||||
Invite people to join your group to get started.
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</AppView>
|
||||
);
|
||||
};
|
||||
|
||||
// --- Styles ---
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#F3F4F6',
|
||||
},
|
||||
header: {
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 20,
|
||||
paddingBottom: 12,
|
||||
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 28,
|
||||
fontWeight: 'bold',
|
||||
color: '#111827',
|
||||
fontSize: responsiveFontSize(2.8),
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
},
|
||||
headerSubtitle: {
|
||||
fontSize: 16,
|
||||
color: '#6B7280',
|
||||
fontSize: responsiveFontSize(1.6),
|
||||
fontFamily: FontsFamily.nunitoRegular,
|
||||
marginTop: 4,
|
||||
},
|
||||
listContainer: {
|
||||
padding: 16,
|
||||
paddingHorizontal: 14,
|
||||
paddingBottom: 86,
|
||||
},
|
||||
skeletonList: {
|
||||
paddingHorizontal: 14,
|
||||
},
|
||||
card: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#FFFFFF',
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
marginBottom: 16,
|
||||
borderRadius: 16,
|
||||
marginBottom: 12,
|
||||
alignItems: 'center',
|
||||
shadowColor: '#374151',
|
||||
shadowOpacity: 0.08,
|
||||
shadowRadius: 10,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
elevation: 3,
|
||||
borderWidth: 1,
|
||||
elevation: 2,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 8,
|
||||
},
|
||||
avatarPlaceholder: {
|
||||
width: 52,
|
||||
height: 52,
|
||||
borderRadius: 26,
|
||||
backgroundColor: '#D1D5DB',
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 24,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 16,
|
||||
},
|
||||
avatarText: {
|
||||
color: '#4B5563',
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
organizerIcon: {
|
||||
borderRadius: 10,
|
||||
padding: 2,
|
||||
borderWidth: 2,
|
||||
borderColor: colors.pastelOrange
|
||||
fontSize: responsiveFontSize(2),
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
},
|
||||
infoContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
nameRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
name: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
color: '#111827',
|
||||
fontSize: responsiveFontSize(1.8),
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
},
|
||||
badge: {
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 4,
|
||||
},
|
||||
badgeText: {
|
||||
fontSize: responsiveFontSize(1),
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
username: {
|
||||
fontSize: 14,
|
||||
color: '#6B7280',
|
||||
marginBottom: 8,
|
||||
fontSize: responsiveFontSize(1.4),
|
||||
fontFamily: FontsFamily.nunitoRegular,
|
||||
marginTop: 2,
|
||||
},
|
||||
contactRow: {
|
||||
contactInfo: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 4,
|
||||
marginTop: 6,
|
||||
gap: 4,
|
||||
},
|
||||
contactText: {
|
||||
marginLeft: 8,
|
||||
fontSize: 14,
|
||||
color: '#374151',
|
||||
fontSize: responsiveFontSize(1.3),
|
||||
fontFamily: FontsFamily.nunitoMedium,
|
||||
},
|
||||
menuButton: {
|
||||
padding: 8,
|
||||
marginLeft: 8,
|
||||
},
|
||||
centered: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
emptyContainer: {
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 10,
|
||||
fontSize: 16,
|
||||
color: '#6B7280',
|
||||
},
|
||||
errorTitle: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
color: '#EF4444',
|
||||
marginBottom: 8,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 16,
|
||||
color: '#374151',
|
||||
textAlign: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
retryButton: {
|
||||
backgroundColor: '#4F46E5',
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 30,
|
||||
borderRadius: 8,
|
||||
},
|
||||
retryButtonText: {
|
||||
color: 'white',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
marginTop: 80,
|
||||
paddingHorizontal: 40,
|
||||
},
|
||||
emptyTitle: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
color: '#374151',
|
||||
marginBottom: 8,
|
||||
fontSize: responsiveFontSize(2.2),
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
marginTop: 20,
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: 16,
|
||||
color: '#6B7280',
|
||||
emptySubtitle: {
|
||||
fontSize: responsiveFontSize(1.6),
|
||||
fontFamily: FontsFamily.nunitoRegular,
|
||||
textAlign: 'center',
|
||||
}
|
||||
marginTop: 8,
|
||||
lineHeight: 22,
|
||||
},
|
||||
});
|
||||
|
||||
export default MemberScreen;
|
||||
|
||||
Reference in New Issue
Block a user