theme update latest project

This commit is contained in:
Abin Toppo
2026-02-16 10:41:38 +05:30
parent bd7dbd864c
commit a98e0b6cdd
75 changed files with 10173 additions and 7915 deletions
+126 -209
View File
@@ -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;