Files
circle-collect/src/screens/home/UserProfileStatus.tsx
T

671 lines
19 KiB
TypeScript

import React, { useMemo, useState, useEffect } from "react";
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Modal,
ToastAndroid,
Image,
ScrollView,
ActivityIndicator,
} from "react-native";
import { useNavigation } from "@react-navigation/native";
import colors from "../../components/colors";
import LinearGradient from "react-native-linear-gradient";
import { Ionicons } from "@react-native-vector-icons/ionicons";
import { apiCall } from "../../utils/ApiHelper";
import { PAY_ALL_URL } from "../../utils/ApiUrls";
import {
responsiveHeight,
responsiveWidth,
responsiveFontSize
} from "react-native-responsive-dimensions";
type StatusData = {
role?: string;
organizerStatus?: string;
profileStatus?: boolean;
};
type DashboardData = {
totalPaidAmountMonth?: number;
multiplePayAmount?: number;
completeStatusMyGroupCount?: number;
activeStatusMyGroupCount?: number;
createdGroupActiveCount?: number;
createdGroupCompleteCount?: number;
myCreatedGroups?: any[];
myJoinedGroups?: Array<any>;
requestList?: any[];
myRequests?: Array<{ status: string }>;
dueGroupDetails?: Array<{
groupId: string;
groupName: string;
baseAmount: number;
lateFee: number;
waivedAmount: number;
totalDue: number;
dueDate: string;
isLate: boolean;
currentCycle: number;
lateFeeMode: string;
lateFeeValue: number;
hasPaid: boolean;
}>;
};
type UserProfileStatusProps = {
profileImage?: { uri: string };
statusData: StatusData | null;
userDashboard: DashboardData | null;
};
const UserProfileStatus: React.FC<UserProfileStatusProps> = ({
profileImage,
statusData,
userDashboard,
}) => {
const navigation = useNavigation();
const [showModal, setShowModal] = useState(false);
// Payment UI states
const [showPayDialog, setShowPayDialog] = useState(false);
const [selectedGroupIds, setSelectedGroupIds] = useState<string[]>([]);
const [submitting, setSubmitting] = useState(false);
// console.log("user me : ", userDashboard)
// Preselect all due groups at mount and on change
useEffect(() => {
if (userDashboard?.dueGroupDetails) {
setSelectedGroupIds(userDashboard.dueGroupDetails.map((g) => g.groupId));
}
}, [userDashboard?.dueGroupDetails]);
// Show profile completion modal only if profile is incomplete
console.log("userDashboard : ", userDashboard)
useEffect(() => {
if (statusData && !statusData.profileStatus) setShowModal(true);
}, [statusData]);
// Top grid info items
const infoItems = useMemo(
() => [
{ icon: <Ionicons name="person-outline" size={20} color={colors.primary} />, label: "Role", value: statusData?.role || "-" },
{ icon: <Ionicons name="pie-chart-outline" size={20} color={colors.primary} />, label: "Active Groups", value: userDashboard?.activeStatusMyGroupCount?.toString() || "0" },
{ icon: <Ionicons name="book-outline" size={20} color={colors.primary} />, label: "Joined Groups", value: userDashboard?.myJoinedGroups?.length?.toString() || "0" },
{ icon: <Ionicons name="people-outline" size={20} color={colors.primary} />, label: "My Groups", value: userDashboard?.myCreatedGroups?.length?.toString() || "0" },
{ icon: <Ionicons name="document-text-outline" size={20} color={colors.primary} />, label: "Total Due", value: `₹${userDashboard?.multiplePayAmount || 0}` },
{ icon: <Ionicons name="checkmark-done-outline" size={20} color={colors.primary} />, label: "Completed", value: userDashboard?.completeStatusMyGroupCount?.toString() || "0" },
],
[userDashboard, statusData]
);
// Utility for info color
const getValueColor = (value: string) => {
if (value.includes("₹") || value === "Active" || value === "Yes ✅") return "#4CAF50";
if (value === "Inactive" || value === "No ❌") return "#FF6B6B";
return "#fff";
};
// Dues and payment logic
const dueGroups = userDashboard?.dueGroupDetails || [];
const totalDue = dueGroups.reduce((sum, item) => sum + (item.totalDue || 0), 0);
const totalDueSelected = useMemo(
() =>
dueGroups
.filter((g) => selectedGroupIds.includes(g.groupId))
.reduce((sum, item) => sum + (item.totalDue || 0), 0),
[dueGroups, selectedGroupIds]
);
const handleToggleGroup = (groupId: string) => {
setSelectedGroupIds((prev) =>
prev.includes(groupId)
? prev.filter((id) => id !== groupId)
: [...prev, groupId]
);
};
const handlePayAll = async () => {
if (selectedGroupIds.length === 0) {
ToastAndroid.show("Select at least one group", ToastAndroid.SHORT);
return;
}
setSubmitting(true);
try {
const response = await apiCall(PAY_ALL_URL, 'POST', { groupIds: selectedGroupIds }, undefined, "Payment failed. Try again.") as any;
console.log("-=-=-=-=-=-=-=-=-=-=-=", response)
if (response.status) {
ToastAndroid.show("Payment initiated!", ToastAndroid.LONG);
setShowPayDialog(false);
} else {
ToastAndroid.show("Payment failed. Try again.", ToastAndroid.SHORT);
}
} catch {
ToastAndroid.show("Network error. Try again.", ToastAndroid.SHORT);
}
setSubmitting(false);
};
// Bottom bar
const renderDueBar = () => (
<View style={styles.dueBar}>
<Text style={styles.dueBarText}>
Total Due: <Text style={{ color: "#FFDE03", fontWeight: "bold" }}>₹{totalDue}</Text>
</Text>
<TouchableOpacity
style={styles.payAllButton}
onPress={() => setShowPayDialog(true)}
disabled={dueGroups.length === 0}
activeOpacity={0.8}
>
<Ionicons name="wallet-outline" size={20} color="#000000ff" />
<Text style={styles.payAllButtonText}>Pay All</Text>
</TouchableOpacity>
</View>
);
// Payment dialog modal
const renderPayDialog = () => (
<Modal visible={showPayDialog} transparent animationType="fade" onRequestClose={() => setShowPayDialog(false)}>
<View style={styles.payModalOverlay}>
<LinearGradient
colors={[colors.white, colors.white]}
start={{ x: 0.2, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.payModalBox}
>
<View style={styles.payDialogHeader}>
<Ionicons name="card-outline" color="#000000ff" size={22} />
<Text style={styles.payDialogTitle}>Select Groups to Pay</Text>
<TouchableOpacity onPress={() => setShowPayDialog(false)}>
<Ionicons name="close-circle" color="#000000ff" size={24} />
</TouchableOpacity>
</View>
<ScrollView
style={{ maxHeight: 200, width: "100%" }}
contentContainerStyle={{ paddingVertical: 8, paddingHorizontal: 4 }}
>
{dueGroups.map((group) => (
<TouchableOpacity
key={group.groupId}
style={[
styles.payGroupOption,
selectedGroupIds.includes(group.groupId) && styles.payOptionSelected,
]}
onPress={() => handleToggleGroup(group.groupId)}
activeOpacity={0.85}
>
<Ionicons
name={selectedGroupIds.includes(group.groupId) ? "checkbox" : "square-outline"}
size={22}
color={selectedGroupIds.includes(group.groupId) ? colors.primary : "#000000ff"}
/>
<View style={{ flex: 1 }}>
<Text style={[styles.payGroupName, { color: "#000000ff" }]}>{group.groupName}</Text>
<Text style={styles.payDueText}>
₹{group.baseAmount}
{group.lateFee > 0 ? ` (Late: ₹${group.lateFee})` : ""}
</Text>
</View>
<Text style={[styles.payAmount, { color: "#4CAF50" }]}>₹{group.totalDue}</Text>
</TouchableOpacity>
))}
</ScrollView>
<View style={styles.payDialogFooter}>
<Text style={styles.payDialogTotal}>
Total: <Text style={{ fontWeight: "bold", color: "#ff5f03ff" }}>₹{totalDueSelected}</Text>
</Text>
<TouchableOpacity
style={[styles.payConfirmButton, { opacity: submitting ? 0.7 : 1 }]}
onPress={handlePayAll}
disabled={submitting || selectedGroupIds.length === 0}
activeOpacity={0.8}
>
{submitting ? (
<ActivityIndicator color="#fff" />
) : (
<>
<Ionicons name="checkmark-circle" size={22} color="#ffffffff" />
<Text style={styles.payButtonText}>Pay Selected</Text>
</>
)}
</TouchableOpacity>
</View>
</LinearGradient>
</View>
</Modal>
);
return (
<View style={styles.container}>
<LinearGradient
colors={[colors.primary, colors.pastelPink]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.card}
>
<View style={styles.headerSection}>
<View style={styles.titleWithIcon}>
<Ionicons name="stats-chart" size={24} color="#fff" style={styles.headerIcon} />
<Text style={styles.header}>Profile Overview</Text>
</View>
<View style={styles.statusBadge}>
<Ionicons name="shield-checkmark" size={16} color="#fff" />
<Text style={styles.statusBadgeText}>
{statusData?.profileStatus ? "Verified" : "Pending"}
</Text>
</View>
</View>
<View style={styles.contentSection}>
<View style={styles.leftSection}>
<View style={styles.infoGrid}>
{infoItems.map((item, idx) => (
<View key={item.label} style={styles.infoCard}>
<View style={styles.infoCardHeader}>
<View style={styles.iconContainer}>
{item.icon}
</View>
<Text style={styles.infoLabel} >
{item.label}
</Text>
</View>
<Text style={[styles.infoValue, { color: getValueColor(item.value) }]} numberOfLines={1}>
{item.value}
</Text>
</View>
))}
</View>
</View>
<View style={styles.profileSection}>
<TouchableOpacity
onPress={() => navigation.navigate("Profile" as never)}
activeOpacity={0.8}
>
<View style={styles.profileContainer}>
{profileImage !== undefined ? (
<Image source={{ uri: profileImage.uri }} style={styles.avatar} />
) : (
<View style={styles.avatarPlaceholder}>
<Ionicons name="person" size={45} color="#fff" />
</View>
)}
</View>
<View style={styles.infoCardOne}>
<Text style={{ color: "#fff", fontSize: responsiveWidth(2.3)}}>Total Paid Amount <Text style={{ fontWeight: "900"}}>{userDashboard?.totalPaidAmountMonth}</Text></Text>
{/* <Text style={{ color: "#fff", fontWeight: "400"}}>Total Paid Amount {userDashboard?.totalPaidAmountMonth}</Text> */}
</View>
</TouchableOpacity>
</View>
</View>
{renderDueBar()}
</LinearGradient>
{renderPayDialog()}
{showModal && (
<Modal visible transparent animationType="fade">
<View style={styles.modalOverlay}>
<View style={styles.modalBox}>
<Ionicons name="alert-circle" size={60} color={colors.primary} />
<Text style={styles.modalTitle}>Complete Your Profile</Text>
<Text style={styles.modalText}>
Your profile is incomplete. Update your details to unlock all features.
</Text>
<View style={styles.modalButtons}>
<TouchableOpacity
style={[styles.button, styles.primaryButton]}
onPress={() => {
setShowModal(false);
navigation.navigate("Profile" as never);
}}
>
<Ionicons name="person" size={18} color="#fff" />
<Text style={styles.buttonText}>Complete Profile</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.secondaryButton]}
onPress={() => setShowModal(false)}
>
<Ionicons name="time" size={18} color="#666" />
<Text style={[styles.buttonText, styles.secondaryText]}>Remind Later</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
)}
</View>
);
};
export default UserProfileStatus;
const styles = StyleSheet.create({
container: {
marginHorizontal: 8,
marginBottom: 16,
},
card: {
borderRadius: 24,
overflow: "hidden",
elevation: 8,
shadowColor: "#000",
shadowOpacity: 0.3,
shadowRadius: 10,
shadowOffset: { width: 0, height: 4 },
},
headerSection: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: 16,
paddingHorizontal: 18,
marginBottom: 16,
},
titleWithIcon: {
flexDirection: 'row',
alignItems: 'center'
},
headerIcon: {
marginRight: 8
},
header: {
fontSize: responsiveWidth(3.5),
fontWeight: "700",
color: "#fff"
},
statusBadge: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(255,255,255,0.2)',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 20,
},
statusBadgeText: {
color: "#fff",
fontSize: responsiveWidth(2),
fontWeight: '600',
marginLeft: 4
},
contentSection: {
flexDirection: 'row',
alignItems: 'flex-start',
paddingHorizontal: 16,
paddingBottom: 16,
},
leftSection: {
flex: 1,
marginRight: 16
},
infoGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 10
},
infoCard: {
backgroundColor: 'rgba(255,255,255,0.15)',
borderRadius: 14,
padding: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.2)',
flex: 1,
minWidth: '45%',
marginBottom: 8,
},
infoCardOne: {
backgroundColor: 'rgba(255,255,255,0.15)',
borderRadius: 14,
padding: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.2)',
// flex: 1,
minWidth: '45%',
marginTop: 21,
},
infoCardHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 6
},
iconContainer: {
width: 28,
height: 28,
backgroundColor: '#fff',
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
marginRight: 8,
},
infoLabel: {
color: "rgba(255,255,255,0.9)",
fontSize: responsiveWidth(2),
fontWeight: '500',
flexShrink: 1,
},
infoValue: {
fontWeight: "700",
fontSize: responsiveWidth(2.5),
textAlign: 'left',
color: "#fff"
},
profileSection: {
width: 90,
alignItems: 'center',
justifyContent: 'center',
},
profileContainer: {
alignItems: 'center'
},
avatar: {
width: 80,
height: 80,
borderRadius: 40,
borderWidth: 3,
borderColor: "#fff",
},
avatarPlaceholder: {
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: 'rgba(255,255,255,0.2)',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 3,
borderColor: "#fff",
},
profileBadge: {
position: 'absolute',
bottom: 0,
right: 0,
width: 26,
height: 26,
backgroundColor: '#fff',
borderRadius: 13,
justifyContent: 'center',
alignItems: 'center',
},
dueBar: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
padding: 16,
borderTopWidth: 1,
borderColor: "rgba(255,255,255,0.15)",
backgroundColor: "rgba(0,0,0,0.15)",
},
dueBarText: {
color: "#fff",
fontSize: responsiveWidth(2.5),
fontWeight: "600"
},
payAllButton: {
backgroundColor: "#ffffffff",
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 20,
gap: 6,
},
payAllButtonText: {
color: "#000000ff",
fontWeight: "bold",
fontSize: responsiveWidth(2.5)
},
payModalOverlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.3)",
justifyContent: "center",
alignItems: "center",
},
payModalBox: {
width: "92%",
borderRadius: 24,
overflow: "hidden",
alignItems: "center",
shadowColor: "#000",
shadowOpacity: 0.15,
shadowRadius: 24,
elevation: 12,
},
payDialogHeader: {
flexDirection: "row",
alignItems: "center",
width: "100%",
justifyContent: "space-between",
paddingVertical: 14,
paddingHorizontal: 18,
borderBottomWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
},
payDialogTitle: {
color: "#000000ff",
fontSize: responsiveWidth(1.8),
fontWeight: "700"
},
payGroupOption: {
flexDirection: "row",
alignItems: "center",
gap: 16,
paddingVertical: 12,
paddingHorizontal: 14,
borderColor: colors.primary,
borderWidth: 1,
borderRadius: 12,
marginBottom: 8,
},
payOptionSelected: {
backgroundColor: "rgba(166, 49, 255, 0.2)",
borderColor: colors.primary,
borderWidth: 1,
},
payGroupName: {
color: "#000000ff",
fontWeight: "700",
fontSize: responsiveWidth(2.5)
},
payDueText: {
color: "rgba(0, 0, 0, 0.8)",
fontSize: responsiveWidth(1.3),
marginTop: 2
},
payAmount: {
fontWeight: "bold",
fontSize: responsiveWidth(2.5),
color: "#4CAF50"
},
payDialogFooter: {
flexDirection: "row",
alignItems: "center",
width: "100%",
justifyContent: "space-between",
paddingVertical: 16,
paddingHorizontal: 18,
},
payDialogTotal: {
color: "#000000ff",
fontWeight: "600",
fontSize: responsiveWidth(1.7)
},
payConfirmButton: {
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.primary,
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 20,
elevation: 4,
borderColor: colors.primary,
borderWidth: 1,
gap: 8,
},
payButtonText: {
color: "#ffffffff",
fontWeight: "bold",
fontSize: responsiveWidth(2.5)
},
modalOverlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.6)",
justifyContent: "center",
alignItems: "center",
},
modalBox: {
width: "85%",
backgroundColor: "#fff",
borderRadius: 24,
padding: 24,
alignItems: "center",
elevation: 12,
},
modalTitle: {
fontSize: responsiveWidth(2.2),
fontWeight: "700",
color: colors.primary,
marginBottom: 8,
textAlign: 'center',
},
modalText: {
textAlign: "center",
color: "#666",
fontSize: responsiveWidth(1.4),
lineHeight: 20,
marginBottom: 20,
},
modalButtons: {
width: '100%',
gap: 10,
},
button: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 14,
borderRadius: 12,
gap: 8,
},
primaryButton: {
backgroundColor: colors.primary,
},
secondaryButton: {
backgroundColor: "#f5f5f5",
borderWidth: 1,
borderColor: '#e0e0e0',
},
buttonText: {
color: "#fff",
fontWeight: "600",
fontSize:responsiveWidth(2.5),
},
secondaryText: {
color: "#666",
},
});