diff --git a/src/components/JoinedGroupCard.tsx b/src/components/JoinedGroupCard.tsx index 5543c46..b1048f7 100644 --- a/src/components/JoinedGroupCard.tsx +++ b/src/components/JoinedGroupCard.tsx @@ -1,7 +1,9 @@ import React, { FC } from 'react'; -import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import { View, Text, StyleSheet, TouchableOpacity, ToastAndroid } from 'react-native'; import colors from './colors'; import { useNavigation } from '@react-navigation/native'; +import { BASE_URL } from '../utils/ApiUrls'; +import { apiCall } from '../utils/ApiHelper'; interface JoinedGroup { id: string; @@ -34,37 +36,55 @@ const formatDate = (dateArray: [number, number, number] | null): string => { }; 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', - }; - } + 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 = ({ item, onViewPress, onPayPress, hasPaid }) => { +export const JoinedGroupCard: FC = ({ item, onViewPress, hasPaid }) => { const progress = (item.currentParticipants / item.maxParticipants) * 100; const statusStyle = getStatusStyle(item.status); const navigation = useNavigation(); + const [loading, setLoading] = React.useState(false); + 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=${item?.contributionAmount}`, '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); + } + }; return ( - navigation.navigate("GroupDetails", {item, color:colors.black})}> + navigation.navigate("GroupDetails", { item, color: colors.black })}> {/* Header */} {item.groupName} @@ -87,7 +107,7 @@ export const JoinedGroupCard: FC = ({ item, onViewPress, o {/* Footer with Dates & Buttons */} - {formatDate(item.startDate)} - {formatDate(item.endDate)} + {formatDate(item.startDate)} - {formatDate(item.endDate)} {/* Add Pay / Paid button here */} {hasPaid ? ( @@ -96,7 +116,7 @@ export const JoinedGroupCard: FC = ({ item, onViewPress, o ) : ( - Pay + {loading ? 'Loading...' : 'Pay'} )} @@ -106,7 +126,7 @@ export const JoinedGroupCard: FC = ({ item, onViewPress, o const styles = StyleSheet.create({ card: { - backgroundColor:colors.pastelOrange, + backgroundColor: colors.pastelOrange, borderRadius: 16, padding: 20, marginBottom: 16, @@ -115,7 +135,7 @@ const styles = StyleSheet.create({ shadowRadius: 10, shadowOffset: { width: 0, height: 4 }, elevation: 5, - marginHorizontal:12 + marginHorizontal: 12 }, header: { flexDirection: 'row', @@ -153,12 +173,12 @@ const styles = StyleSheet.create({ borderColor: colors.pastelOrange, marginBottom: 16, backgroundColor: colors.white, - borderRadius:15, + borderRadius: 15, }, infoItem: { - padding:5, - paddingHorizontal:10, - backgroundColor:colors.white, + padding: 5, + paddingHorizontal: 10, + backgroundColor: colors.white, alignItems: 'flex-start', }, infoLabel: { @@ -197,7 +217,7 @@ const styles = StyleSheet.create({ }, progressBarFill: { height: '100%', - backgroundColor: colors.Settings, + backgroundColor: colors.pastelOrange, borderRadius: 4, }, footer: { @@ -212,7 +232,7 @@ const styles = StyleSheet.create({ }, // Pay button payButton: { - elevation:5, + elevation: 5, backgroundColor: colors.white, paddingHorizontal: 25, paddingVertical: 10, diff --git a/src/components/common/AnimatedCard.tsx b/src/components/common/AnimatedCard.tsx index 3878505..678803c 100644 --- a/src/components/common/AnimatedCard.tsx +++ b/src/components/common/AnimatedCard.tsx @@ -1,86 +1,41 @@ import React, { useRef } from "react"; -import { - View, - Text, - StyleSheet, - TouchableWithoutFeedback, - Animated, - ViewStyle, - StyleProp, -} from "react-native"; +import { View, Text, StyleSheet, Animated, TouchableWithoutFeedback } from "react-native"; import LinearGradient from "react-native-linear-gradient"; -import { Ionicons, IoniconsIconName } from "@react-native-vector-icons/ionicons"; -import colors from "../colors"; // Assuming your pastel colors are defined here +import { Ionicons } from "@react-native-vector-icons/ionicons"; +import colors from "../colors"; type CardProps = { title: string; subtitle?: string; - iconName?: IoniconsIconName; + iconName?: string; onPress?: () => void; backOne?: string; backTwo?: string; - style?: StyleProp; }; const AnimatedCard: React.FC = ({ title, subtitle, - iconName = "ellipse", + iconName = "ellipse-outline", onPress, - backOne = colors.pastelPink, // Defaulting to pastelPink from your preferences - backTwo = colors.primary, - style, + backOne = colors.primary, + backTwo = colors.pastelPink, }) => { const scale = useRef(new Animated.Value(1)).current; - const handlePressIn = () => { - Animated.spring(scale, { - toValue: 0.96, - useNativeDriver: true, - friction: 4, - }).start(); - }; - - const handlePressOut = () => { - Animated.spring(scale, { - toValue: 1, - useNativeDriver: true, - friction: 4, - }).start(() => { - if (onPress) { - onPress(); - } - }); + const handlePress = () => { + Animated.sequence([ + Animated.spring(scale, { toValue: 0.95, useNativeDriver: true }), + ]).start(() => onPress && onPress()); }; return ( - - - - {/* Decorative Graphics */} - - - - {/* Content */} - - {iconName && ( - - )} - - {title} - {subtitle && {subtitle}} - - + + + + + {title} + {subtitle && {subtitle}} @@ -89,77 +44,21 @@ const AnimatedCard: React.FC = ({ const styles = StyleSheet.create({ card: { - // minWidth: 170, flex: 1, - height: 170, - marginHorizontal: 8, - marginVertical: 10, - borderRadius: 24, + height: 160, + margin: 8, + borderRadius: 20, overflow: "hidden", - shadowColor: "#000", - shadowOffset: { width: 0, height: 5 }, - shadowOpacity: 0.15, - shadowRadius: 12, elevation: 5, }, gradient: { - position: "relative", - flex: 1, - // justifyContent: "center", - // alignItems: "center", - justifyContent: "space-between", - borderRadius: 24, - overflow: "hidden", - gap: 10, - }, - graphicCircleLarge: { - position: "absolute", - top: -30, - right: -40, - width: 100, - height: 100, - borderRadius: 50, - backgroundColor: "rgba(255, 255, 255, 0.1)", - }, - graphicCircleSmall: { - position: "absolute", - bottom: -20, - left: -25, - width: 70, - height: 70, - borderRadius: 35, - backgroundColor: "rgba(255, 255, 255, 0.08)", - }, - content: { - padding: 20, - position: "relative", - zIndex: 1, flex: 1, justifyContent: "center", alignItems: "center", }, - icon: { - marginBottom: 12, - }, - textContainer: { - alignItems: "center", - }, - title: { - fontSize: 18, - fontWeight: "bold", - color: "#fff", - textTransform: "uppercase", - letterSpacing: 1, - textAlign: "center", - }, - subtitle: { - fontSize: 13, - color: colors.white, - opacity: 0.9, - fontWeight: "500", - textAlign: "center", - marginTop: 4, - }, + icon: { marginBottom: 10 }, + title: { fontSize: 18, fontWeight: "bold", color: "#fff" }, + subtitle: { fontSize: 13, color: "#fff", opacity: 0.9 }, }); export default AnimatedCard; diff --git a/src/screens/GroupScreen.tsx b/src/screens/GroupScreen.tsx index 70bfb14..83c83d2 100644 --- a/src/screens/GroupScreen.tsx +++ b/src/screens/GroupScreen.tsx @@ -22,7 +22,7 @@ import { StackNavigationProp } from "@react-navigation/stack"; import { RootStackParamList } from "../navigation/AppNavigator"; import AppView from "../components/AppView"; import { apiCall } from "../utils/ApiHelper"; -import { BASE_URL, GET_JOINED_GROUP_URL, JOIN_GROUP_URL, PUBLIC_GROUPS_URL, PUT_USER_STATUS } from "../utils/ApiUrls"; +import { BASE_URL, GET_JOINED_GROUP_URL, JOIN_GROUP_URL, PUBLIC_GROUPS_URL, USER_STATUS } from "../utils/ApiUrls"; import colors from "../components/colors"; import { ApiResponse } from "./home/UserProfileStatus"; import * as Keychain from "react-native-keychain"; @@ -214,7 +214,7 @@ export default function GroupScreen() { setLoading(true); setError(null); - const response = await fetch(PUT_USER_STATUS, { + const response = await fetch(USER_STATUS, { method: "GET", headers: { Accept: "*/*", diff --git a/src/screens/home/Home.tsx b/src/screens/home/Home.tsx index f898c50..f508743 100644 --- a/src/screens/home/Home.tsx +++ b/src/screens/home/Home.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useCallback } from "react"; import { StyleSheet, Text, @@ -6,23 +6,21 @@ import { TouchableOpacity, UIManager, Platform, - Image, - Alert, FlatList, ListRenderItem, - ToastAndroid, - ScrollView, + Alert, + RefreshControl, } from "react-native"; import AppView from "../../components/AppView"; import colors from "../../components/colors"; import { useDispatch, useSelector } from "react-redux"; import { useNavigation } from "@react-navigation/native"; -import { PROFILE_URL } from "../../utils/ApiUrls"; +import { DASHBOARD_URL, PROFILE_URL, USER_STATUS } from "../../utils/ApiUrls"; import { setUserData } from "../../redux/userSlice"; import { TabNavigationProp } from "../../navigation/BottomTabs"; import UserProfileStatus from "./UserProfileStatus"; import AnimatedCard from "../../components/common/AnimatedCard"; -import { Ionicons, IoniconsIconName } from "@react-native-vector-icons/ionicons"; +import { Ionicons } from "@react-native-vector-icons/ionicons"; import { apiCall } from "../../utils/ApiHelper"; import CustomAlert, { CustomAlertProps } from "../../components/CostomAlart"; @@ -38,6 +36,7 @@ type UserProfile = { imageBlob: string | null; role?: string; organizerStatus?: string; + profileStatus?: boolean; }; interface ActionItem { @@ -45,174 +44,206 @@ interface ActionItem { title: string; action: string; tab?: "My Groups" | "Joined Groups" | "Requests" | "Your Requests"; - iconName?: IoniconsIconName; + iconName?: any; backOne?: string; backTwo?: string; subtitle?: string; } -// Updated action items with a prettier pastel palette const organizerActions: ActionItem[] = [ - { id: "1", title: "My Groups", subtitle: "Manage your groups", action: "NAVIGATE_MANAGEMENT", tab: "My Groups", iconName: "people-circle-outline", backOne: '#decbfcff', backTwo: '#B799FF' }, - { id: "2", title: "Joined Groups", subtitle: "View member groups", action: "NAVIGATE_MANAGEMENT", tab: "Joined Groups", iconName: "people-outline", backOne: '#D7E9F7', backTwo: '#82AAE3' }, - { id: "3", title: "Requests", subtitle: "Check new requests", action: "NAVIGATE_MANAGEMENT", tab: "Requests", iconName: "notifications-outline", backOne: '#DFF6DD', backTwo: '#88D18A' }, - { id: "4", title: "Create Group", subtitle: "Create a new group", action: "NAVIGATE_CREATE_GROUP", tab: "Requests", iconName: "people-outline", backOne: '#DFF6DD', backTwo: '#88D18A' }, + { id: "1", title: "My Groups", subtitle: "Manage groups", action: "NAVIGATE_MANAGEMENT", tab: "My Groups", iconName: "people-circle-outline", backOne: "#decbfcff", backTwo: "#B799FF" }, + { id: "2", title: "Joined Groups", subtitle: "View groups", action: "NAVIGATE_MANAGEMENT", tab: "Joined Groups", iconName: "people-outline", backOne: "#D7E9F7", backTwo: "#82AAE3" }, + { id: "3", title: "Requests", subtitle: "Check requests", action: "NAVIGATE_MANAGEMENT", tab: "Requests", iconName: "notifications-outline", backOne: "#DFF6DD", backTwo: "#88D18A" }, + { id: "4", title: "Create Group", subtitle: "Start new group", action: "NAVIGATE_CREATE_GROUP", iconName: "add-circle-outline", backOne: "#FFF4D2", backTwo: "#FFB344" }, + { id: "5", title: "Your Requests", subtitle: "Track requests", action: "NAVIGATE_MANAGEMENT", tab: "Your Requests", iconName: "mail-open-outline", backOne: "#FFF4D2", backTwo: "#FFB344" }, + { id: "6", title: "Transactions", subtitle: "View history", action: "TRANSACTION", iconName: "receipt-outline", backOne: "#decbfcff", backTwo: "#B799FF" }, ]; const userActions: ActionItem[] = [ - { id: "1", title: "Joined Groups", subtitle: "See your groups", action: "NAVIGATE_MANAGEMENT", tab: "Joined Groups", iconName: "person-outline", backOne: '#D7E9F7', backTwo: '#82AAE3' }, - { id: "2", title: "Your Requests", subtitle: "Track your requests", action: "NAVIGATE_MANAGEMENT", tab: "Your Requests", iconName: "mail-open-outline", backOne: '#FFF4D2', backTwo: '#FFB344' }, - { id: "3", title: "Transactions", subtitle: "View your history", action: "TRANSACTION", iconName: "receipt-outline", backOne: '#decbfcff', backTwo: '#B799FF' }, - { id: "4", title: "Create Group", subtitle: "Create a new group", action: "NAVIGATE_CREATE_GROUP", tab: "Requests", iconName: "people-outline", backOne: '#DFF6DD', backTwo: '#88D18A' }, + { id: "1", title: "Joined Groups", subtitle: "See your groups", action: "NAVIGATE_MANAGEMENT", tab: "Joined Groups", iconName: "people-outline", backOne: "#D7E9F7", backTwo: "#82AAE3" }, + { id: "2", title: "Your Requests", subtitle: "Track requests", action: "NAVIGATE_MANAGEMENT", tab: "Your Requests", iconName: "mail-open-outline", backOne: "#FFF4D2", backTwo: "#FFB344" }, + { id: "3", title: "Transactions", subtitle: "View history", action: "TRANSACTION", iconName: "receipt-outline", backOne: "#decbfcff", backTwo: "#B799FF" }, + { id: "4", title: "Create Group", subtitle: "Start new group", action: "NAVIGATE_CREATE_GROUP", iconName: "add-circle-outline", backOne: "#DFF6DD", backTwo: "#88D18A" }, ]; + const Home: React.FC = () => { const user = useSelector((state: any) => state.user); - const notifications = useSelector( - (state: any) => state.notifications.notifications - ); + const notifications = useSelector((state: any) => state.notifications.notifications); const navigation = useNavigation(); const dispatch = useDispatch(); - const actions = user.role === 'ORGANIZER' ? organizerActions : userActions; + const actions = user.role === "ORGANIZER" ? organizerActions : userActions; const [apiUser, setApiUser] = useState(null); - const [alartVisible, setalartVisible] = useState(false) - const [alartAction, setAlartAction] = useState(null) + const [alertVisible, setAlertVisible] = useState(false); + const [alertAction, setAlertAction] = useState(null); + const [userDashboard, setUserDashboard] = useState(null); + const [refreshing, setRefreshing] = useState(false); + const [statusData, setStatusData] = useState(null); + const getDashboardData = async () => { + try { + const result = (await apiCall(DASHBOARD_URL, "GET")) as any; + if (result.status === "success") { + console.log("dashboard -----",result) + setUserDashboard(result.data); + } + } catch (error) { + console.error("❌ Error fetching dashboard data:", error); + } + }; + + const getUserStatus = async () => { + const result = await apiCall(USER_STATUS, "GET") as any; + setStatusData(result.data); + } + + const fetchUserProfile = async () => { + try { + const result = (await apiCall(PROFILE_URL, "GET")) as any; + console.log("profile data", result); + if (result.status === "success") { + setApiUser(result.data); + dispatch(setUserData({ ...result.data, profileStatus: true })); + } + } catch (error) { + console.error("❌ Error fetching profile:", error); + } + }; + + const fetchData = useCallback(async () => { + await Promise.all([fetchUserProfile(), getDashboardData(), getUserStatus()]); + }, []); + + const onRefresh = useCallback(async () => { + setRefreshing(true); + await fetchData(); + setRefreshing(false); + }, [fetchData]); useEffect(() => { - const fetchUserProfile = async () => { - try { - const result = await apiCall(PROFILE_URL, "GET") as any; - console.log(PROFILE_URL, result) - if (result.status === "success") { - setApiUser(result.data); - dispatch(setUserData({ ...result.data, profileStatus: true })); - } - } catch (error) { - console.error("❌ Error fetching profile:", error); - } - }; - const unsubscribe = navigation.addListener('focus', fetchUserProfile); - fetchUserProfile(); + const unsubscribe = navigation.addListener("focus", fetchData); + fetchData(); return unsubscribe; - }, [navigation, dispatch]); + }, [navigation, fetchData]); const handleActionPress = (item: ActionItem) => { if (item.action === "NAVIGATE_MANAGEMENT" && item.tab) { - navigation.navigate('Management', { actionTab: item.tab }); + navigation.navigate("Management", { actionTab: item.tab }); } else if (item.action === "TRANSACTION") { navigation.navigate("TransactionList" as never); } else if (item.action === "NAVIGATE_CREATE_GROUP") { switch (apiUser?.organizerStatus) { - case 'NOT_APPLIED': - setalartVisible(true); - setAlartAction({ + case "NOT_APPLIED": + setAlertAction({ visible: true, - title: "Become organizer", - message: "You have to become organizer for creating groups", + title: "Become an Organizer", + message: "You need to be an organizer to create groups.", buttons: [ { text: "Continue", onPress: () => { - setalartVisible(false); + setAlertVisible(false); navigation.navigate("BecomeOrg" as never); }, - style: "default", - }, - { - text: "Close", - onPress: () => { - setalartVisible(false); - }, - style: "cancel", }, + { text: "Close", onPress: () => setAlertVisible(false), style: "cancel" }, ], - alertType: 'error', - onClose: () => setAlartAction(null), + alertType: "error", + onClose: () => setAlertAction(null), }); + setAlertVisible(true); break; - - case 'PENDING': - setalartVisible(true); - setAlartAction({ - visible: true, - title: "Organizer Application Pending", - message: "Your application to become an organizer is pending approval. You cannot create groups until it's approved.", - buttons: [ - { - text: "Close", - onPress: () => { - setalartVisible(false); - }, - style: "cancel", - }, - ], - alertType: 'info', - onClose: () => setAlartAction(null), - }); + case "PENDING": + Alert.alert("Pending Approval", "Your organizer request is still pending."); break; - case 'APPROVED': + case "APPROVED": navigation.navigate("GroupCreation" as never); break; default: - Alert.alert("Unknown action", item.title); + Alert.alert("Unknown Status", "Could not determine your organizer status."); break; } } }; - + const unreadCount = notifications.filter((item: any) => !item.read).length; const renderGridItem: ListRenderItem = ({ item }) => ( - - handleActionPress(item)} - /> - + handleActionPress(item)} + /> ); const getProfileImageUri = () => { const blob = user.imageBlob || apiUser?.imageBlob; - return blob ? { uri: `data:image/jpeg;base64,${blob}` } : require('../../assets/images/placeholder.png'); + return blob ? { uri: `data:image/jpeg;base64,${blob}` } : undefined; }; return ( - // - - - - Hello, - {user.firstName || apiUser?.firstName || 'User'} - - navigation.navigate("Notification" as never)}> - {unreadCount > 0 ? unreadCount : ''} - - - - - - - - Quick Actions - Alert.alert('Info', 'This will allow you to pay all pending contributions in your groups.')}> - Pay All - - - + item.id} - numColumns={2} - contentContainerStyle={{ paddingHorizontal: 8 }} + ListHeaderComponent={ + <> + + + + Hello, {user.firstName || apiUser?.firstName || "User"} + + + navigation.navigate("Notification" as never)}> + {unreadCount > 0 && ( + + {unreadCount} + + )} + + + + + + + + Quick Actions + + + + } + contentContainerStyle={{ paddingBottom: 100 }} + refreshControl={ + + } /> - + + {alertAction && ( + setAlertVisible(false)} + /> + )} + ); }; @@ -221,9 +252,7 @@ export default Home; const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: colors.white, // Set background to white - marginBottom: 100, - padding:10 + backgroundColor: colors.white, }, header: { flexDirection: "row", @@ -233,25 +262,34 @@ const styles = StyleSheet.create({ paddingTop: 24, paddingBottom: 16, }, - greeting: { - fontSize: 22, - color: colors.textDark, + greeting: { fontSize: 22, color: colors.textDark }, + sectionTitle: { fontSize: 20, fontWeight: "600", color: colors.textDark, flex: 1 }, + actionHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginHorizontal: 20, + marginBottom: 10, + marginTop: 16 }, - userName: { - fontSize: 28, - fontWeight: 'bold', - color: colors.textDark, + payAllButton: { + paddingHorizontal: 14, + paddingVertical: 6, + backgroundColor: colors.primary, + borderRadius: 8, }, - sectionTitle: { - fontSize: 20, - fontWeight: '600', - color: colors.textDark, - paddingHorizontal: 20, - marginBottom: 8, + payAllText: { color: "#fff", fontWeight: "bold", fontSize: 14 }, + badge: { + position: "absolute", + right: -6, + top: -4, + backgroundColor: colors.danger, + borderRadius: 8, + width: 16, + height: 16, + justifyContent: "center", + alignItems: "center", + zIndex: 1, }, - cardWrapper: { - flex: 1, - // marginHorizontal: 8, - // backgroundColor:'red' - }, -}); + badgeText: { color: colors.white, fontSize: 10, fontWeight: "bold" }, +}); \ No newline at end of file diff --git a/src/screens/home/UserProfileStatus.tsx b/src/screens/home/UserProfileStatus.tsx index bc5755f..6b8b342 100644 --- a/src/screens/home/UserProfileStatus.tsx +++ b/src/screens/home/UserProfileStatus.tsx @@ -1,4 +1,4 @@ -import React, { use, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import React, { useMemo, useState, useEffect } from "react"; import { StyleSheet, Text, @@ -7,395 +7,339 @@ import { Modal, ToastAndroid, Image, - Animated, + ScrollView, + ActivityIndicator, } from "react-native"; -import * as Keychain from "react-native-keychain"; -import { useFocusEffect, useNavigation } from "@react-navigation/native"; -import { DASHBOARD_URL, PUT_USER_STATUS } from "../../utils/ApiUrls"; +import { useNavigation } from "@react-navigation/native"; import colors from "../../components/colors"; import LinearGradient from "react-native-linear-gradient"; -import { Ionicons, IoniconsIconName } from "@react-native-vector-icons/ionicons"; +import { Ionicons } from "@react-native-vector-icons/ionicons"; import { apiCall } from "../../utils/ApiHelper"; +import { PAY_ALL_URL } from "../../utils/ApiUrls"; -export type ApiResponse = { - message: string; - status: string; - statusCode: number; - data: { - role: string; - organizerStatus: string; - profileStatus: boolean; - }; +type StatusData = { + role?: string; + organizerStatus?: string; + profileStatus?: boolean; }; -type DashboardResponse = { - message: string; - status: string; - statusCode: number; - data: { - totalPaidAmountMonth: number; - multiplePayAmount: number; - completeStatusMyGroupCount: number; - activeStatusMyGroupCount: number; - myCreatedGroups: any[]; - myJoinedGroups: Array<{ - groupId: string; - groupName: string; - contributionAmount: number; - currentCycle: number; - createdDate: string; - status: string; - }>; - requestList: any[]; - myRequests: Array<{ - requestId: string; - groupName: string; - type: string; - status: string; - requestDate: 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 DashboardData = { + totalPaidAmountMonth?: number; + multiplePayAmount?: number; + completeStatusMyGroupCount?: number; + activeStatusMyGroupCount?: number; + createdGroupActiveCount?: number; + createdGroupCompleteCount?: number; + myCreatedGroups?: any[]; + myJoinedGroups?: Array; + 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 = ({ profileImage }) => { +const UserProfileStatus: React.FC = ({ + profileImage, + statusData, + userDashboard, +}) => { const navigation = useNavigation(); - const [loading, setLoading] = useState(true); - const [statusData, setStatusData] = useState(null); const [showModal, setShowModal] = useState(false); - const [moreItem, setMoreItem] = useState(false); - const [userDashboard, setUserDashboard] = useState(null); - // Animation refs to prevent re-creation on re-render - const fadeAnim = useRef(new Animated.Value(0)).current; - const scaleAnim = useRef(new Animated.Value(0.9)).current; + // Payment UI states + const [showPayDialog, setShowPayDialog] = useState(false); + const [selectedGroupIds, setSelectedGroupIds] = useState([]); + const [submitting, setSubmitting] = useState(false); - // Memoized icons list - const icons: IoniconsIconName[] = useMemo( - () => ["person", "people", "person-add", "create", "card", "checkmark-done"], - [] - ); - - // Fetch user status - const userStatus = useCallback(async () => { - try { - const credentials = await Keychain.getGenericPassword(); - if (!credentials) { - ToastAndroid.show("You are not logged in", ToastAndroid.SHORT); - return; - } - - setLoading(true); - const token = credentials.password; - - const response = await fetch(PUT_USER_STATUS, { - method: "GET", - headers: { - Accept: "*/*", - Authorization: `Bearer ${token}`, - }, - }); - - const json: ApiResponse = await response.json(); - - console.log("profile json : ", json) - if (json.status === "success") { - setStatusData(json.data); - if (!json.data.profileStatus) setShowModal(true); - } else { - ToastAndroid.show("Failed to fetch status", ToastAndroid.SHORT); - } - } catch { - ToastAndroid.show("Something went wrong", ToastAndroid.SHORT); - } finally { - setLoading(false); - } - }, []); - - useFocusEffect( - useCallback(() => { - userStatus(); - }, [userStatus]) - ); - - // Animation start + // Preselect all due groups at mount and on change useEffect(() => { - Animated.parallel([ - Animated.timing(fadeAnim, { toValue: 1, duration: 600, useNativeDriver: true }), - Animated.timing(scaleAnim, { toValue: 1, duration: 600, useNativeDriver: true }), - ]).start(); - }, [fadeAnim, scaleAnim]); + if (userDashboard?.dueGroupDetails) { + setSelectedGroupIds(userDashboard.dueGroupDetails.map((g) => g.groupId)); + } + }, [userDashboard?.dueGroupDetails]); - const handleGoToProfile = useCallback(() => { - setShowModal(false); - navigation.navigate("Profile" as never); - }, [navigation]); + // Show profile completion modal only if profile is incomplete + useEffect(() => { + if (statusData && !statusData.profileStatus) setShowModal(true); + }, [statusData]); - const handlePress = useCallback((label: string) => { - ToastAndroid.show(`${label} clicked`, ToastAndroid.SHORT); - }, []); + // Top grid info items + const infoItems = useMemo( + () => [ + { label: "Role", value: statusData?.role || "-" }, + { label: "Active Groups", value: userDashboard?.activeStatusMyGroupCount?.toString() || "0" }, + { label: "Joined Groups", value: userDashboard?.myJoinedGroups?.length?.toString() || "0" }, + { label: "My Groups", value: userDashboard?.myCreatedGroups?.length?.toString() || "0" }, + { label: "Total Due", value: `₹${userDashboard?.multiplePayAmount || 0}` }, + { 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"; }; - // Dynamic info items based on dashboard data - const infoItems = useMemo(() => { - if (!userDashboard) { - return [ - { label: "Role", value: statusData?.role || "-" }, - { label: "Active", value: "0" }, - { label: "Joined", value: "0" }, - { label: "Created", value: "0" }, - { label: "Due Pay", value: "₹0" }, - { label: "Completed", value: "0" }, - ]; + // 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; } - - return [ - { - label: "Role", - value: statusData?.role || "-" - }, - { - label: "Active", - value: userDashboard.activeStatusMyGroupCount.toString() - }, - { - label: "Joined", - value: userDashboard.myJoinedGroups.length.toString() - }, - { - label: "Created", - value: userDashboard.myCreatedGroups.length.toString() - }, - { - label: "Due Pay", - value: `₹${userDashboard.multiplePayAmount}` - }, - { - label: "Completed", - value: userDashboard.completeStatusMyGroupCount.toString() - }, - ]; - }, [userDashboard, statusData]); - - // Calculate total due amount - const getTotalDueAmount = useCallback(() => { - if (!userDashboard?.dueGroupDetails) return 0; - return userDashboard.dueGroupDetails.reduce((total, due) => total + due.totalDue, 0); - }, [userDashboard]); - - // Calculate pending requests count - const getPendingRequestsCount = useCallback(() => { - if (!userDashboard?.myRequests) return 0; - return userDashboard.myRequests.filter(request => request.status === "PENDING").length; - }, [userDashboard]); - - const getDashboardData = async () => { + setSubmitting(true); try { - const result = (await apiCall(DASHBOARD_URL, "GET")) as DashboardResponse; - console.log("DASHBOARD_URL-------", result) - if (result.status === "success") { - setUserDashboard(result.data); + const response = await apiCall(PAY_ALL_URL, 'POST',{ groupIds: selectedGroupIds, payAllDue: true }, undefined, "Payment failed. Try again.") as any; + console.log("-=-=-=-=-=-=-=-=-=-=-=",response) + + // const response = await fetch( + // "https://api.godemo.in/FundCollect/api/payments/payments/bulk", + // { + // method: "POST", + // headers: { "Content-Type": "application/json" }, + // body: JSON.stringify({ groupIds: selectedGroupIds, payAllDue: true }), + // } + // ); + if (response.status) { + ToastAndroid.show("Payment initiated!", ToastAndroid.LONG); + setShowPayDialog(false); + } else { + ToastAndroid.show("Payment failed. Try again.", ToastAndroid.SHORT); } - } catch (error) { - console.error("❌ Error fetching dashboard data:", error); + } catch { + ToastAndroid.show("Network error. Try again.", ToastAndroid.SHORT); } + setSubmitting(false); }; - useEffect(() => { - getDashboardData(); - }, []); + // Bottom bar + const renderDueBar = () => ( + + + Total Due: ₹{totalDue} + + setShowPayDialog(true)} + disabled={dueGroups.length === 0} + activeOpacity={0.8} + > + + Pay All + + + ); - // Format currency - const formatCurrency = (amount: number) => { - return `₹${amount.toLocaleString('en-IN')}`; - }; + // Payment dialog modal + const renderPayDialog = () => ( + setShowPayDialog(false)}> + + + + + Select Groups to Pay + setShowPayDialog(false)}> + + + + + {dueGroups.map((group) => ( + handleToggleGroup(group.groupId)} + activeOpacity={0.85} + > + + + {group.groupName} + + ₹{group.baseAmount} + {group.lateFee > 0 ? ` (Late: ₹${group.lateFee})` : ""} + + + ₹{group.totalDue} + + ))} + + + + Total: ₹{totalDueSelected} + + + {submitting ? ( + + ) : ( + <> + + Pay Selected + + )} + + + + + + ); return ( - - {/* Decorative Background */} - - {["circle1", "circle2", "circle3"].map((c, i) => ( - - ))} - - + - {/* Header */} - - - - Profile Overview - - - - - {statusData?.profileStatus ? "Verified" : "Pending"} - - + + + Profile Overview + + + + + {statusData?.profileStatus ? "Verified" : "Pending"} + - {/* Content Section */} - {/* LEFT SIDE */} - {infoItems.map((item, index) => ( - handlePress(item.label)} - > + {infoItems.map((item, idx) => ( + - + - {item.label} + + {item.label} + - + {item.value} - + ))} - {/* RIGHT SIDE */} + - navigation.navigate("Profile" as never)} activeOpacity={0.8}> - + navigation.navigate("Profile" as never)} + activeOpacity={0.8} + > + {profileImage ? ( ) : ( - + )} - + - View Profile - - - - setMoreItem(!moreItem)}> - + - - {moreItem && ( - <> - - - - Status Overview - - - - - Organizer: {statusData?.organizerStatus || "-"} - - - Profile: {statusData?.profileStatus ? "Complete ✅" : "Incomplete ❌"} - - - Pending Requests: {getPendingRequestsCount()} - - - Total Due: {formatCurrency(getTotalDueAmount())} - - {userDashboard?.dueGroupDetails?.map((due, index) => ( - - • {due.groupName}: {formatCurrency(due.totalDue)} (Due: {due.dueDate}) - - ))} - - - - - - Profile Completion - - {statusData?.profileStatus ? "100%" : "60%"} - - - - - - - - )} + {renderDueBar()} - {/* Modal */} + {renderPayDialog()} + {showModal && ( - + Complete Your Profile Your profile is incomplete. Update your details to unlock all features. - - + { + setShowModal(false); + navigation.navigate("Profile" as never); + }} + > Complete Profile - - setShowModal(false)} > Remind Later - + )} - + ); }; @@ -403,66 +347,37 @@ export default UserProfileStatus; const styles = StyleSheet.create({ container: { - // margin: 16, - }, - backgroundGraphics: { - position: 'absolute', - width: '100%', - height: '100%', - zIndex: -1, - }, - floatingCircle: { - position: 'absolute', - borderRadius: 50, - backgroundColor: 'rgba(255,255,255,0.1)', - }, - circle1: { - width: 80, - height: 80, - top: -20, - left: -20, - }, - circle2: { - width: 60, - height: 60, - bottom: -10, - right: -10, - }, - circle3: { - width: 40, - height: 40, - top: '40%', - right: '10%', + marginHorizontal: 8, + marginBottom: 16, }, card: { borderRadius: 24, - padding: 20, + overflow: "hidden", elevation: 8, shadowColor: "#000", shadowOpacity: 0.3, shadowRadius: 10, shadowOffset: { width: 0, height: 4 }, - overflow: 'hidden', }, headerSection: { - marginBottom: 16, - }, - headerContent: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', + paddingTop: 16, + paddingHorizontal: 18, + marginBottom: 16, }, - titleWithIcon: { - flexDirection: 'row', - alignItems: 'center', + titleWithIcon: { + flexDirection: 'row', + alignItems: 'center' }, - headerIcon: { - marginRight: 8, + headerIcon: { + marginRight: 8 }, - header: { - fontSize: 22, - fontWeight: "700", - color: "#fff", + header: { + fontSize: 20, + fontWeight: "700", + color: "#fff" }, statusBadge: { flexDirection: 'row', @@ -472,92 +387,70 @@ const styles = StyleSheet.create({ paddingVertical: 6, borderRadius: 20, }, - statusBadgeText: { - color: "#fff", - fontSize: 12, - fontWeight: '600', - marginLeft: 4, + statusBadgeText: { + color: "#fff", + fontSize: 13, + fontWeight: '600', + marginLeft: 4 }, - contentSection: { - flexDirection: 'row', - justifyContent: 'space-between', + contentSection: { + flexDirection: 'row', + alignItems: 'flex-start', + paddingHorizontal: 16, + paddingBottom: 16, }, - leftSection: { - flex: 1, - marginRight: 16, + leftSection: { + flex: 1, + marginRight: 16 }, - infoGrid: { - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'space-between', + infoGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 10 }, infoCard: { - width: '48%', backgroundColor: 'rgba(255,255,255,0.15)', - borderRadius: 12, - padding: 5, - marginBottom: 8, + borderRadius: 14, + padding: 12, borderWidth: 1, borderColor: 'rgba(255,255,255,0.2)', + flex: 1, + minWidth: '45%', + marginBottom: 8, }, - infoCardHeader: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 6, + infoCardHeader: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 6 }, iconContainer: { - width: 24, - height: 24, + width: 28, + height: 28, backgroundColor: '#fff', - borderRadius: 6, + borderRadius: 8, justifyContent: 'center', alignItems: 'center', - marginRight: 6, + marginRight: 8, }, infoLabel: { - color: "rgba(255,255,255,0.8)", + color: "rgba(255,255,255,0.9)", fontSize: 12, fontWeight: '500', + flexShrink: 1, }, - infoValue: { - fontWeight: "700", - fontSize: 16, - }, - statusOverview: { - backgroundColor: 'rgba(255,255,255,0.15)', - padding: 16, - borderRadius: 12, - borderWidth: 1, - borderColor: 'rgba(255,255,255,0.2)', - marginTop: 16, - }, - statusHeader: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 12, - }, - statusOverviewTitle: { - color: "#fff", - fontSize: 16, - fontWeight: '600', - marginLeft: 6, - }, - statusItems: { - gap: 8, - }, - statusItemText: { - color: "#fff", - fontSize: 14, + infoValue: { + fontWeight: "700", + fontSize: 16, + textAlign: 'left', + color: "#fff" }, profileSection: { + width: 90, alignItems: 'center', + justifyContent: 'center', }, - profileContainer: { - alignItems: 'center', - padding: 16, - borderRadius: 20, - borderWidth: 2, - borderColor: 'rgba(255,255,255,0.3)', + profileContainer: { + alignItems: 'center' }, avatar: { width: 80, @@ -578,52 +471,134 @@ const styles = StyleSheet.create({ }, profileBadge: { position: 'absolute', - top: 12, - right: 12, - width: 24, - height: 24, + bottom: 0, + right: 0, + width: 26, + height: 26, backgroundColor: '#fff', - borderRadius: 12, + borderRadius: 13, justifyContent: 'center', alignItems: 'center', }, - viewProfileText: { - color: "#fff", - fontSize: 12, - fontWeight: '600', - marginTop: 8, - }, - progressSection: { - marginTop: 16, - paddingTop: 16, + dueBar: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + padding: 16, borderTopWidth: 1, - borderTopColor: 'rgba(255,255,255,0.3)', + borderColor: "rgba(255,255,255,0.15)", + backgroundColor: "rgba(0,0,0,0.15)", }, - progressHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', + dueBarText: { + color: "#fff", + fontSize: 16, + fontWeight: "600" + }, + payAllButton: { + backgroundColor: "#ffffffff", + flexDirection: "row", + alignItems: "center", + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 20, + gap: 6, + }, + payAllButtonText: { + color: "#000000ff", + fontWeight: "bold", + fontSize: 16 + }, + 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: 18, + fontWeight: "700" + }, + payGroupOption: { + flexDirection: "row", + alignItems: "center", + gap: 16, + paddingVertical: 12, + paddingHorizontal: 14, + borderColor: colors.primary, + borderWidth: 1, + borderRadius: 12, marginBottom: 8, }, - progressText: { - color: "#fff", - fontSize: 14, - fontWeight: '600', + payOptionSelected: { + backgroundColor: "rgba(166, 49, 255, 0.2)", + borderColor: colors.primary, + borderWidth: 1, }, - progressPercent: { - color: "#fff", - fontSize: 14, - fontWeight: '700', + payGroupName: { + color: "#000000ff", + fontWeight: "700", + fontSize: 16 }, - progressBar: { - height: 6, - backgroundColor: 'rgba(255,255,255,0.3)', - borderRadius: 3, - overflow: 'hidden', + payDueText: { + color: "rgba(0, 0, 0, 0.8)", + fontSize: 13, + marginTop: 2 }, - progressFill: { - height: '100%', - borderRadius: 3, + payAmount: { + fontWeight: "bold", + fontSize: 16, + color: "#4CAF50" + }, + payDialogFooter: { + flexDirection: "row", + alignItems: "center", + width: "100%", + justifyContent: "space-between", + paddingVertical: 16, + paddingHorizontal: 18, + }, + payDialogTotal: { + color: "#000000ff", + fontWeight: "600", + fontSize: 17 + }, + 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: 16 }, modalOverlay: { flex: 1, @@ -638,10 +613,6 @@ const styles = StyleSheet.create({ padding: 24, alignItems: "center", elevation: 12, - shadowColor: "#000", - shadowOpacity: 0.3, - shadowRadius: 15, - shadowOffset: { width: 0, height: 6 }, }, modalTitle: { fontSize: 22, @@ -659,14 +630,13 @@ const styles = StyleSheet.create({ }, modalButtons: { width: '100%', - gap: 8, + gap: 10, }, button: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - paddingVertical: 12, - paddingHorizontal: 24, + paddingVertical: 14, borderRadius: 12, gap: 8, }, @@ -681,7 +651,7 @@ const styles = StyleSheet.create({ buttonText: { color: "#fff", fontWeight: "600", - fontSize: 14, + fontSize: 16, }, secondaryText: { color: "#666", diff --git a/src/screens/home/user-home/UserHome.tsx b/src/screens/home/user-home/UserHome.tsx index beded4a..d9e40b8 100644 --- a/src/screens/home/user-home/UserHome.tsx +++ b/src/screens/home/user-home/UserHome.tsx @@ -1,256 +1,3 @@ -// import React, { useEffect, useState } from "react"; -// import { -// StyleSheet, -// Text, -// View, -// FlatList, -// TouchableOpacity, -// Alert, -// RefreshControl, -// ScrollView, -// Button, -// ToastAndroid, -// } from "react-native"; -// import { useNavigation } from "@react-navigation/native"; -// import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; -// import type { RootStackParamList } from "../../../navigation/AppNavigator"; -// import colors from "../../../components/colors"; -// import { Ionicons } from "@react-native-vector-icons/ionicons"; -// import { GET_JOINED_GROUP_URL } from "../../../utils/ApiUrls"; -// import * as Keychain from "react-native-keychain"; -// import AnimatedCard from "../../../components/common/AnimatedCard"; - -// type NavigationProp = NativeStackNavigationProp< -// RootStackParamList, -// "GroupDetails" -// >; - -// type Group = { -// id: string; -// groupName: string; -// description: string; -// currentParticipants: number; -// maxParticipants: number; -// contributionAmount: number; -// status: string; -// }; - -// const UserHome = () => { -// const navigation = useNavigation(); -// const [groups, setGroups] = useState([]); -// const [loading, setLoading] = useState(false) -// const [refreshing, setRefreshing] = useState(false) - - -// const activeGroups = groups.filter((g) => g.status === "ACTIVE"); - -// const totalGroups = activeGroups.length; - -// const totalContribution = activeGroups.reduce( -// (sum, g) => sum + g.contributionAmount, -// 0 -// ); - -// const onRefresh = async () => { -// setRefreshing(true) -// await handleGroup() -// setRefreshing(false) -// } - -// const handleGroup = async () => { -// try { -// setLoading(true) -// const credentials = await Keychain.getGenericPassword(); -// if (!credentials) { -// ToastAndroid.show('You are not logged in', ToastAndroid.SHORT); -// return; -// } -// const token = credentials.password; - -// const response = await fetch(GET_JOINED_GROUP_URL, { -// method: "GET", -// headers: { -// Accept: "*/*", -// Authorization: `Bearer ${token}`, -// }, -// }); - -// const result = await response.json(); -// if (result.status === "success") { -// console.log("GET_JOINED_GROUP_URL res : ", result); -// setGroups(result.data); // save API data -// setLoading(false) -// } else { -// ToastAndroid.show("Failed to fetch groups", ToastAndroid.SHORT); -// setLoading(false) -// } -// } catch (error) { -// console.error("❌ Error fetching groups:", error); -// } finally { -// setLoading(false) -// } -// }; - -// useEffect(() => { -// handleGroup(); -// }, []); - -// const renderItem = ({ item }: { item: Group }) => { -// return ( -// navigation.navigate("GroupDetails", { item })} -// > -// {item.groupName} -// {item.description} -// -// -// -// {item.currentParticipants}/{item.maxParticipants} members -// -// - -// -// Contribution: ₹{item.contributionAmount} -// -// Status: {item.status} -// -// ); -// }; - -// return ( -// -// {/* Stats */} - -// {/*