theme update latest project
This commit is contained in:
+170
-200
@@ -1,10 +1,12 @@
|
||||
import React, { FC } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, ToastAndroid } from 'react-native';
|
||||
import colors from './colors';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { BASE_URL, CONTRIBUTE } from '../utils/ApiUrls';
|
||||
import { apiCall } from '../utils/ApiHelper';
|
||||
import { responsiveFontSize } from 'react-native-responsive-dimensions';
|
||||
import { useTheme } from '../hooks/ui/useTheme';
|
||||
import { useFetch } from '../hooks/core/useFetch';
|
||||
import { useCreate } from '../hooks/core/useCreate';
|
||||
import { CONTRIBUTE } from '../utils/ApiUrls';
|
||||
import FontsFamily from '../constents/FontsFamily';
|
||||
|
||||
interface JoinedGroup {
|
||||
id: string;
|
||||
@@ -13,8 +15,8 @@ interface JoinedGroup {
|
||||
visibility: 'PUBLIC' | 'PRIVATE';
|
||||
createdByUserId: number;
|
||||
createdByName: string;
|
||||
startDate: [number, number, number] | null;
|
||||
endDate: [number, number, number] | null;
|
||||
startDate: string | null;
|
||||
endDate: string | null;
|
||||
minParticipants: number;
|
||||
maxParticipants: number;
|
||||
currentParticipants: number;
|
||||
@@ -24,138 +26,141 @@ interface JoinedGroup {
|
||||
}
|
||||
|
||||
interface JoinedGroupCardProps {
|
||||
item: JoinedGroup | any;
|
||||
onViewPress: () => void;
|
||||
onPayPress: () => void; // function for Pay button
|
||||
hasPaid?: boolean; // whether user has already paid
|
||||
item: JoinedGroup;
|
||||
hasPaid?: boolean;
|
||||
}
|
||||
|
||||
const formatDate = (dateStr: string | null): string => {
|
||||
if (!dateStr) return 'N/A';
|
||||
|
||||
const [year, month, day] = dateStr.split('-');
|
||||
return `${day}-${month}-${year}`;
|
||||
const months = [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
||||
];
|
||||
const monthIndex = parseInt(month, 10) - 1;
|
||||
const dayNum = parseInt(day, 10);
|
||||
return `${dayNum} ${months[monthIndex]} ${year}`;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const getStatusStyle = (status: JoinedGroup['status']) => {
|
||||
switch (status) {
|
||||
case 'ACTIVE':
|
||||
return {
|
||||
backgroundColor: '#D1FAE5',
|
||||
textColor: '#065F46',
|
||||
};
|
||||
case 'PENDING':
|
||||
return {
|
||||
backgroundColor: '#FEF3C7',
|
||||
textColor: '#92400E',
|
||||
};
|
||||
case 'COMPLETED':
|
||||
return {
|
||||
backgroundColor: '#E5E7EB',
|
||||
textColor: '#4B5563',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
backgroundColor: '#E5E7EB',
|
||||
textColor: '#4B5563',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const JoinedGroupCard: FC<JoinedGroupCardProps> = ({ item, onViewPress, hasPaid }) => {
|
||||
const progress = (item.currentParticipants / item.maxParticipants) * 100;
|
||||
const statusStyle = getStatusStyle(item.status);
|
||||
export const JoinedGroupCard: FC<JoinedGroupCardProps> = ({ item }) => {
|
||||
const theme = useTheme();
|
||||
const navigation = useNavigation<any>();
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [payable, setPayable] = React.useState<any>();
|
||||
|
||||
const { data: payableRes } = useFetch<any>(`${CONTRIBUTE}${item.id}/calculate-contribution`);
|
||||
const payable = payableRes?.data;
|
||||
|
||||
const { mutate: contribute, isPending: loading } = useCreate<any, any>(
|
||||
`payments/contribute/${item?.id}?amount=${payable?.totalPayable}`,
|
||||
{
|
||||
onSuccess: () => {
|
||||
ToastAndroid.show('Payment successful!', ToastAndroid.SHORT);
|
||||
},
|
||||
onError: () => {
|
||||
ToastAndroid.show('Payment failed. Please try again.', ToastAndroid.SHORT);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
const onPayPress = async () => {
|
||||
// Navigate to payment screen or handle payment logic
|
||||
// navigation.navigate('PaymentScreen', { groupId: item.id, amount: item.contributionAmount });
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await apiCall(`${BASE_URL}payments/contribute/${item?.id}?amount=${payable?.totalPayable}`, 'POST') as any;
|
||||
console.log('payment res ', res)
|
||||
if (res.status === 200 || res.status === 201 || res.statusCode === 200) {
|
||||
ToastAndroid.show('Payment done!', ToastAndroid.SHORT);
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
ToastAndroid.show("An error occurred during payment.", ToastAndroid.SHORT);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
const getStatusColors = (status: JoinedGroup['status']) => {
|
||||
switch (status) {
|
||||
case 'ACTIVE':
|
||||
return { bg: theme.palette.greyExtraLight, text: theme.palette.black };
|
||||
case 'COMPLETED':
|
||||
return { bg: theme.palette.greyLight, text: theme.palette.greyDark };
|
||||
default:
|
||||
return { bg: theme.palette.greyExtraLight, text: theme.palette.greyDark };
|
||||
}
|
||||
};
|
||||
|
||||
const handleContribution = async () => {
|
||||
try {
|
||||
const res = await apiCall(`${CONTRIBUTE}${item.id}/calculate-contribution`, 'GET') as any;
|
||||
setPayable(res.data);
|
||||
} catch (error) {
|
||||
console.log("Error in handleContribution: ", error);
|
||||
}
|
||||
};
|
||||
React.useEffect(() => {
|
||||
handleContribution();
|
||||
}, []);
|
||||
const statusColors = getStatusColors(item.status);
|
||||
|
||||
return (
|
||||
<TouchableOpacity style={styles.card} onPress={() => navigation.navigate("GroupDetails", { item, color: colors.black })}>
|
||||
{/* Header */}
|
||||
<TouchableOpacity
|
||||
style={[styles.card, { backgroundColor: theme.colors.card, borderColor: theme.colors.border }]}
|
||||
onPress={() => navigation.navigate('GroupDetails', { item })}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.groupName}>{item.groupName}</Text>
|
||||
<View style={[styles.statusBadge, { backgroundColor: statusStyle.backgroundColor }]}>
|
||||
<Text style={[styles.statusText, { color: statusStyle.textColor }]}>{item.status}</Text>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={[styles.groupName, { color: theme.colors.textPrimary }]}>{item.groupName}</Text>
|
||||
<Text style={[styles.createdBy, { color: theme.colors.textSecondary }]}>
|
||||
by {item.createdByName}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.statusBadge, { backgroundColor: statusColors.bg }]}>
|
||||
<Text style={[styles.statusText, { color: statusColors.text }]}>{item.status}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={styles.createdBy}>Created by {item.createdByName}</Text>
|
||||
{/* Financial Info */}
|
||||
<View style={styles.infoRow}>
|
||||
|
||||
{/* Contribution Amount — always visible */}
|
||||
<View style={[styles.infoRow, { borderColor: theme.colors.border }]}>
|
||||
<View style={styles.infoItem}>
|
||||
<Text style={styles.infoLabel}>Contribution</Text>
|
||||
<Text style={styles.infoValue}>₹{item.contributionAmount.toLocaleString('en-IN')}</Text>
|
||||
<Text style={[styles.infoLabel, { color: theme.colors.textMuted }]}>Contribution</Text>
|
||||
<Text style={[styles.infoValue, { color: theme.colors.textPrimary }]}>
|
||||
UGX {item.contributionAmount.toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.infoItem}>
|
||||
<Text style={styles.infoLabel}>Cycle</Text>
|
||||
<Text style={styles.infoValue}>{item.cycleType}</Text>
|
||||
<Text style={[styles.infoLabel, { color: theme.colors.textMuted }]}>Type</Text>
|
||||
<Text style={[styles.infoValue, { color: theme.colors.textPrimary }]}>{item.cycleType}</Text>
|
||||
</View>
|
||||
</View>
|
||||
{payable?.effectiveLateFee && (
|
||||
<>
|
||||
<Text style={{ marginBottom: 8, fontSize: 14, color: '#050911ff' }}>Total Payable: ₹{payable?.totalPayable.toLocaleString('en-IN')}</Text>
|
||||
<Text style={{ marginBottom: 8, fontSize: 14, color: '#050911ff' }}>Effective Late Fee : ₹{payable?.effectiveLateFee.toLocaleString('en-IN')}</Text>
|
||||
</>
|
||||
|
||||
{/* Payment Details — shown when NOT paid and there's an amount due */}
|
||||
{payable && !payable.hasPaid && payable.totalPayable > 0 && (
|
||||
<View style={styles.payableSection}>
|
||||
<View style={styles.payableRow}>
|
||||
<Text style={[styles.payableLabel, { color: theme.colors.textSecondary }]}>Total Payable</Text>
|
||||
<Text style={[styles.payableValue, { color: theme.colors.textPrimary }]}>
|
||||
UGX {payable.totalPayable.toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
{payable.effectiveLateFee > 0 && (
|
||||
<View style={styles.payableRow}>
|
||||
<Text style={[styles.payableLabel, { color: theme.colors.warning }]}>Late Fee</Text>
|
||||
<Text style={[styles.payableValue, { color: theme.colors.warning }]}>
|
||||
+ UGX {payable.effectiveLateFee.toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{payable.alreadyPaid > 0 && (
|
||||
<View style={styles.payableRow}>
|
||||
<Text style={[styles.payableLabel, { color: theme.colors.success }]}>Already Paid</Text>
|
||||
<Text style={[styles.payableValue, { color: theme.colors.success }]}>
|
||||
UGX {payable.alreadyPaid.toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
{/* Footer with Dates & Buttons */}
|
||||
|
||||
<View style={styles.footer}>
|
||||
<Text style={styles.dateText}>
|
||||
{formatDate(item.startDate)} to {formatDate(item.endDate)}
|
||||
<Text style={[styles.dateText, { color: theme.colors.textMuted }]}>
|
||||
{formatDate(item.startDate)} — {formatDate(item.endDate)}
|
||||
</Text>
|
||||
{/* Add Pay / Paid button here */}
|
||||
{/* {hasPaid ? (
|
||||
<View style={styles.paidButton}>
|
||||
<Text style={styles.paidButtonText}>Paid</Text>
|
||||
|
||||
{payable?.hasPaid ? (
|
||||
<View style={[styles.paidBadge, { backgroundColor: theme.colors.success + '18' }]}>
|
||||
<Text style={[styles.paidText, { color: theme.colors.success }]}>✓ PAID</Text>
|
||||
</View>
|
||||
) : (
|
||||
|
||||
<TouchableOpacity style={styles.payButton} onPress={onPayPress}>
|
||||
<Text style={styles.payButtonText}>{loading ? 'Loading...' : ' Pay'}</Text>
|
||||
</TouchableOpacity>
|
||||
)} */}
|
||||
{
|
||||
hasPaid && <View style={styles.paidButton}>
|
||||
<Text style={styles.paidButtonText}>Paid</Text>
|
||||
</View>
|
||||
}
|
||||
{
|
||||
item.status !== 'COMPLETED' && <TouchableOpacity style={styles.payButton} onPress={onPayPress}>
|
||||
<Text style={styles.payButtonText}>{loading ? 'Loading...' : ' Pay'}</Text>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
item.status !== 'COMPLETED' && (
|
||||
<TouchableOpacity
|
||||
style={[styles.payButton, { backgroundColor: theme.colors.primary }]}
|
||||
onPress={() => {
|
||||
navigation.navigate('PaymentMode' as any, {
|
||||
groupId: item.id,
|
||||
routeFrom: 'GroupDetails'
|
||||
})
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
<Text style={[styles.payButtonText, { color: theme.colors.onPrimary }]}>
|
||||
{loading ? '...' : 'Pay'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
@@ -163,144 +168,109 @@ export const JoinedGroupCard: FC<JoinedGroupCardProps> = ({ item, onViewPress, h
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
backgroundColor: colors.pastelOrange,
|
||||
borderRadius: 16,
|
||||
padding: 20,
|
||||
marginBottom: 16,
|
||||
shadowColor: '#374151',
|
||||
shadowOpacity: 0.08,
|
||||
shadowRadius: 10,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
elevation: 5,
|
||||
marginHorizontal: 12
|
||||
padding: 14,
|
||||
marginBottom: 12,
|
||||
marginHorizontal: 12,
|
||||
borderWidth: 1,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 8,
|
||||
elevation: 2,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: 4,
|
||||
marginBottom: 10,
|
||||
},
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
groupName: {
|
||||
fontSize: responsiveFontSize(2),
|
||||
fontWeight: 'bold',
|
||||
color: '#1F2937',
|
||||
flexShrink: 1,
|
||||
fontFamily: FontsFamily.nunitoExtraBold,
|
||||
marginBottom: 2,
|
||||
},
|
||||
createdBy: {
|
||||
fontSize: responsiveFontSize(1.4),
|
||||
color: '#6B7280',
|
||||
marginBottom: 16,
|
||||
fontFamily: FontsFamily.nunitoRegular,
|
||||
},
|
||||
statusBadge: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 6,
|
||||
},
|
||||
statusText: {
|
||||
fontSize: responsiveFontSize(1.2),
|
||||
fontWeight: '600',
|
||||
textTransform: 'uppercase',
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
},
|
||||
infoRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: 16,
|
||||
paddingVertical: 10,
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: colors.pastelOrange,
|
||||
marginBottom: 16,
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 15,
|
||||
marginBottom: 10,
|
||||
},
|
||||
infoItem: {
|
||||
padding: 5,
|
||||
paddingHorizontal: 10,
|
||||
backgroundColor: colors.white,
|
||||
alignItems: 'flex-start',
|
||||
flex: 1,
|
||||
},
|
||||
infoLabel: {
|
||||
fontSize: responsiveFontSize(1.4),
|
||||
color: '#6B7280',
|
||||
marginBottom: 4,
|
||||
fontSize: responsiveFontSize(1.3),
|
||||
fontFamily: FontsFamily.nunitoRegular,
|
||||
marginBottom: 2,
|
||||
},
|
||||
infoValue: {
|
||||
fontSize: responsiveFontSize(1.6),
|
||||
fontWeight: '600',
|
||||
color: '#111827',
|
||||
textTransform: 'capitalize',
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
},
|
||||
progressContainer: {
|
||||
marginBottom: 16,
|
||||
payableSection: {
|
||||
marginBottom: 12,
|
||||
gap: 6,
|
||||
},
|
||||
progressLabels: {
|
||||
payableRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 8,
|
||||
alignItems: 'center',
|
||||
},
|
||||
participantText: {
|
||||
fontSize: responsiveFontSize(1.4),
|
||||
color: '#6B7280',
|
||||
payableLabel: {
|
||||
fontSize: responsiveFontSize(1.35),
|
||||
fontFamily: FontsFamily.nunitoSemiBold,
|
||||
},
|
||||
participantCount: {
|
||||
fontSize: responsiveFontSize(1.4),
|
||||
fontWeight: '600',
|
||||
color: '#111827',
|
||||
},
|
||||
progressBarBackground: {
|
||||
height: 8,
|
||||
backgroundColor: '#E5E7EB',
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
progressBarFill: {
|
||||
height: '100%',
|
||||
backgroundColor: colors.pastelOrange,
|
||||
borderRadius: 4,
|
||||
payableValue: {
|
||||
fontSize: responsiveFontSize(1.5),
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
},
|
||||
footer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginTop: 4,
|
||||
},
|
||||
dateText: {
|
||||
fontSize: responsiveFontSize(1.4),
|
||||
color: '#4B5563',
|
||||
fontWeight: '500',
|
||||
fontSize: responsiveFontSize(1.3),
|
||||
fontFamily: FontsFamily.nunitoMedium,
|
||||
},
|
||||
// Pay button
|
||||
payButton: {
|
||||
elevation: 5,
|
||||
backgroundColor: colors.white,
|
||||
paddingHorizontal: 25,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 8,
|
||||
minWidth: 80,
|
||||
alignItems: 'center',
|
||||
},
|
||||
payButtonText: {
|
||||
color: colors.black,
|
||||
fontSize: responsiveFontSize(1.4),
|
||||
fontWeight: '600',
|
||||
fontSize: responsiveFontSize(1.5),
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
},
|
||||
// Paid button
|
||||
paidButton: {
|
||||
backgroundColor: '#D1FAE5',
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 10,
|
||||
paidBadge: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 6,
|
||||
},
|
||||
paidButtonText: {
|
||||
color: '#065F46',
|
||||
fontSize: responsiveFontSize(1.4),
|
||||
fontWeight: '600',
|
||||
},
|
||||
viewButton: {
|
||||
backgroundColor: colors.pastelOrange,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 10,
|
||||
},
|
||||
viewButtonText: {
|
||||
color: '#000000ff',
|
||||
fontSize: responsiveFontSize(1.4),
|
||||
fontWeight: '600',
|
||||
paidText: {
|
||||
fontSize: responsiveFontSize(1.3),
|
||||
fontFamily: FontsFamily.nunitoBold,
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user