diff --git a/App.tsx b/App.tsx index ae0e5a4..0de681b 100644 --- a/App.tsx +++ b/App.tsx @@ -5,23 +5,23 @@ import { SafeAreaProvider } from 'react-native-safe-area-context'; import { PaperProvider } from 'react-native-paper'; import AppNavigator from './src/navigation/AppNavigator'; import notifee from '@notifee/react-native'; -import FirebaseService from './src/utils/firebase/firebaseServices'; +import { backgroundHandler, foregroundListener, requestPermission, tokenRefreshHandler } from './src/utils/firebase/firebaseServices'; function App() { const isDarkMode = useColorScheme() === 'dark'; useEffect(() => { // 🔹 Ask for permission - FirebaseService.requestPermission(); + requestPermission(); // 🔹 Handle background + quit state - FirebaseService.backgroundHandler(); + backgroundHandler(); // 🔹 Listen for foreground messages - const unsubscribe = FirebaseService.foregroundListener(); + const unsubscribe = foregroundListener(); // 🔹 Listen for token refresh - const unsubscribeRefresh = FirebaseService.tokenRefreshHandler(); + const unsubscribeRefresh = tokenRefreshHandler(); // 🔹 Create default notification channel (Android) notifee.createChannel({ diff --git a/src/components/JoinedGroupCard.tsx b/src/components/JoinedGroupCard.tsx index bcb2433..a742639 100644 --- a/src/components/JoinedGroupCard.tsx +++ b/src/components/JoinedGroupCard.tsx @@ -66,7 +66,7 @@ export const JoinedGroupCard: FC = ({ item, onViewPress }) const navigation = useNavigation() return ( - navigation.navigate("GroupDetails", {item, color:colors.Settings})}> + navigation.navigate("GroupDetails", {item, color:colors.black})}> {/* Header */} {item.groupName} diff --git a/src/components/MyGroupCard.tsx b/src/components/MyGroupCard.tsx index 3521c8b..25e42a2 100644 --- a/src/components/MyGroupCard.tsx +++ b/src/components/MyGroupCard.tsx @@ -94,6 +94,7 @@ const styles = StyleSheet.create({ shadowColor: "#000", shadowOpacity: 0.05, shadowRadius: 4, + marginHorizontal:12 }, cardHeader: { flexDirection: "row", diff --git a/src/components/RequestCard.tsx b/src/components/RequestCard.tsx index ff8a7a2..7d6e385 100644 --- a/src/components/RequestCard.tsx +++ b/src/components/RequestCard.tsx @@ -37,8 +37,8 @@ export const RequestCard: FC = ({ item, onApprove, onDecline } {/* Bottom section with action buttons */} onDecline(item)}> - - Decline + + Decline onApprove(item)}> {/* Corrected icon color and updated text color for better contrast */} @@ -54,10 +54,11 @@ export const RequestCard: FC = ({ item, onApprove, onDecline } const styles = StyleSheet.create({ card: { + marginHorizontal: 12, backgroundColor: colors.white, borderRadius: 16, marginVertical: 8, - elevation: 4, + elevation: 5, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 10, @@ -67,9 +68,7 @@ const styles = StyleSheet.create({ userInfoSection: { flexDirection: 'row', alignItems: 'center', - borderBottomWidth: 1, - borderBottomColor: colors.textLight, - paddingBottom: 12, + paddingBottom: 6, }, avatar: { width: 50, @@ -96,7 +95,6 @@ const styles = StyleSheet.create({ requestText: { fontSize: 15, color: colors.textDark, - lineHeight: 22, }, groupName: { fontWeight: 'bold', @@ -116,11 +114,13 @@ const styles = StyleSheet.create({ borderRadius: 12, }, declineButton: { - backgroundColor: colors.pastelPink, + elevation:5, + backgroundColor:colors.white }, approveButton: { // Set background color as requested backgroundColor: colors.pastelOrange, + elevation:5, }, actionButtonText: { fontSize: 16, diff --git a/src/components/common/CustomModal.tsx b/src/components/common/CustomModal.tsx index c6b2f52..8c9818c 100644 --- a/src/components/common/CustomModal.tsx +++ b/src/components/common/CustomModal.tsx @@ -21,6 +21,8 @@ type CustomModalProps = { cancelText?: string; onConfirm?: () => void; confirmButton?:string + confirmTextColor?:string + elevation?:number }; const CustomModal: React.FC = ({ @@ -31,6 +33,8 @@ const CustomModal: React.FC = ({ confirmText = "Confirm", cancelText = "Cancel", onConfirm, + confirmTextColor, + elevation, confirmButton = colors.primary }) => { const slideAnim = useRef(new Animated.Value(height)).current; // starts off screen @@ -89,17 +93,17 @@ const CustomModal: React.FC = ({ {/* Footer Actions */} {cancelText} - {confirmText} + {confirmText} diff --git a/src/screens/ContributionScreen.tsx b/src/screens/ContributionScreen.tsx index ead24ba..0fdeaf5 100644 --- a/src/screens/ContributionScreen.tsx +++ b/src/screens/ContributionScreen.tsx @@ -56,7 +56,7 @@ const ContributionDetails: React.FC = ({ }, [visible]); return ( - + {/* Header */} = ({ { opacity: fadeAnim, transform: [{ translateY: slideAnim }] }, ]} > - + Contribution Summary @@ -126,7 +126,6 @@ const styles = StyleSheet.create({ headerTitle: { fontSize: 22, fontWeight: "700", - color: colors.primary, marginLeft: 10, }, card: { @@ -154,7 +153,7 @@ const styles = StyleSheet.create({ valueHighlight: { fontSize: 18, fontWeight: "700", - color: colors.primary, + color: colors.black, marginBottom: 10, }, }); diff --git a/src/screens/InviteScreen.tsx b/src/screens/InviteScreen.tsx index 4e54a2a..d5735cf 100644 --- a/src/screens/InviteScreen.tsx +++ b/src/screens/InviteScreen.tsx @@ -52,7 +52,7 @@ const InviteScreen: FC = ({ route }) => { setLoading(true); setError(null); try { - const response = await apiCall(`https://api.godemo.in/FundCollect/api/fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string }; + const response = await apiCall(`http://192.168.1.5:9060/api/fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string }; if (response.status === 'success' && response.data) { setGroupDetails(response.data); } else { @@ -76,7 +76,7 @@ const InviteScreen: FC = ({ route }) => { try { // Replace with your actual API call to join the group // For now, it just shows an alert. - await apiCall(`https://api.godemo.in/FundCollect/api/fund/groups/join/invite/${code}`, 'POST'); + await apiCall(`http://192.168.1.5:9060/api/fund/groups/join/invite/${code}`, 'POST'); Alert.alert('Success!', `You have requested to join "${groupDetails.groupName}".`); } catch (e: any) { Alert.alert('Join Error', e.message || 'An unexpected error occurred.'); diff --git a/src/screens/LoginScreen.tsx b/src/screens/LoginScreen.tsx index 3a44f1b..6690701 100644 --- a/src/screens/LoginScreen.tsx +++ b/src/screens/LoginScreen.tsx @@ -64,6 +64,7 @@ export default function LoginScreen() { if (mobile.length === 10) { setLoading(true); try { + console.log("Login api call:"); // FIX: Used the imported LOGIN_URL constant instead of a string literal const res = await axios.get(LOGIN_URL, { params: { mobile } }); console.log("Login response:", res.data); @@ -76,6 +77,7 @@ export default function LoginScreen() { [{ text: "Try Again", onPress: hideAlert }], 'error' ); + } } catch (error: any) { console.error("Login error:", error); diff --git a/src/screens/OtpVarificationScreen.tsx b/src/screens/OtpVarificationScreen.tsx index 249abef..94c93be 100644 --- a/src/screens/OtpVarificationScreen.tsx +++ b/src/screens/OtpVarificationScreen.tsx @@ -20,7 +20,7 @@ import { LOGIN_URL, VARIFY_OTP_URL } from "../utils/ApiUrls"; import colors from "../components/colors"; import LinearGradient from "react-native-linear-gradient"; import CustomAlert from "../components/CostomAlart"; -import FirebaseService from "../utils/firebase/firebaseServices"; +import { getFcmToken } from "../utils/firebase/firebaseServices"; const CELL_COUNT = 6; type OtpVerifyRouteProp = RouteProp; @@ -128,7 +128,7 @@ export default function OtpVerificationScreen() { const { jwt } = res.data?.data || {}; console.log("✅ OTP Verified:", res.data); await saveToken(jwt); - await FirebaseService.getFcmToken(res.data?.data?.userId); + await getFcmToken(res.data?.data?.userId); let hasActionRun = false; diff --git a/src/screens/home/GroupList.tsx b/src/screens/home/GroupList.tsx index 9bd3476..ece029d 100644 --- a/src/screens/home/GroupList.tsx +++ b/src/screens/home/GroupList.tsx @@ -110,7 +110,7 @@ const GroupList = () => { } }, [search, groups]); - if (loading) return ; + if (loading) return ; return ( <> @@ -175,10 +175,10 @@ const styles = StyleSheet.create({ alignItems: "center", marginRight: 12, }, - avatarText: { fontSize: 18, fontWeight: "700", color: colors.Home }, + avatarText: { fontSize: 18, fontWeight: "700", color: colors.black }, groupName: { fontSize: 16, fontWeight: "600", color: colors.textDark }, groupDescription: { fontSize: 13, color: colors.textLight, marginTop: 2 }, - groupMeta: { fontSize: 12, color: colors.Home, marginTop: 2 }, + groupMeta: { fontSize: 12, color: colors.black, marginTop: 2 }, detailsBox: { marginTop: 10, padding: 10, diff --git a/src/screens/home/RequestList.tsx b/src/screens/home/RequestList.tsx index c18e2a0..7c1683b 100644 --- a/src/screens/home/RequestList.tsx +++ b/src/screens/home/RequestList.tsx @@ -58,7 +58,7 @@ const RequestList: React.FC = () => { setError(null); const response = await fetch( - "https://api.godemo.in/FundCollect/api/fund/group/pending/request", + "http://192.168.1.5:9060/api/fund/group/pending/request", { method: "GET", headers: { @@ -124,7 +124,7 @@ const RequestList: React.FC = () => { // Make API call to approve the request const response = await fetch( - `https://api.godemo.in/FundCollect/api/fund/requests/${requestId}/approve`, + `http://192.168.1.5:9060/api/fund/requests/${requestId}/approve`, { method: "PUT", headers: { @@ -170,7 +170,7 @@ const RequestList: React.FC = () => { // Make API call to decline the request const response = await fetch( - `https://api.godemo.in/FundCollect/api/fund/requests/${requestedId}/reject?reason=/${reason}`, + `http://192.168.1.5:9060/api/fund/requests/${requestedId}/reject?reason=/${reason}`, { method: "PUT", headers: { diff --git a/src/screens/management/EditGroupScreen.tsx b/src/screens/management/EditGroupScreen.tsx index fc6832a..cea8d12 100644 --- a/src/screens/management/EditGroupScreen.tsx +++ b/src/screens/management/EditGroupScreen.tsx @@ -28,7 +28,7 @@ import *as Keychain from 'react-native-keychain' import Ionicons from '@react-native-vector-icons/ionicons'; import { CustomButton } from '../../components/common/Custom'; import { StackNavigationProp } from '@react-navigation/stack'; -import CustomModal from '../../components/common/CustomModal'; +// import CustomModal from '../../components/common/CustomModal'; import WinnerUser from './WinnerUser'; @@ -346,7 +346,7 @@ const EditGroupScreen: React.FC = ({ route }) => { {title} {isEditable && !editingCard && ( setEditingCard(cardKey)} style={styles.editButton}> - ✎ + Update )} @@ -537,7 +537,7 @@ const EditGroupScreen: React.FC = ({ route }) => { onPress={handleUpdateImage} icon={imageUpdating ? undefined : "cloud-upload"} style={{ paddingHorizontal: 20 }} - buttonBackground={colors.pastelBlue} + buttonBackground={colors.pastelOrange} /> @@ -674,7 +674,7 @@ const styles = StyleSheet.create({ position: "absolute", // right: 0, bottom: 0, - backgroundColor: colors.pastelBlue, + backgroundColor: colors.pastelOrange, borderRadius: 20, width: 40, height: 40, @@ -694,8 +694,8 @@ const styles = StyleSheet.create({ marginBottom: 12, }, cardTitle: { fontSize: 18, fontWeight: 'bold', color: '#1F2937' }, - editButton: { padding: 8 }, - editButtonText: { color: colors.pastelPink, fontSize: 20, fontWeight: 'bold' }, + editButton: { padding: 8,backgroundColor:colors.pastelOrange, borderRadius: 15,elevation:5 }, + editButtonText: { color: colors.black, fontSize: 15, fontWeight: 'bold' }, fieldContainer: { borderBottomWidth: 1, borderBottomColor: '#F3F4F6', paddingBottom: 12, marginBottom: 12 }, infoLabel: { fontSize: 16, color: '#6B7280', marginBottom: 4 }, infoValue: { fontSize: 16, fontWeight: '600', color: '#111827', textTransform: 'capitalize' }, @@ -712,8 +712,8 @@ const styles = StyleSheet.create({ button: { borderRadius: 10, paddingVertical: 12, paddingHorizontal: 20, marginLeft: 10, minWidth: 80, alignItems: 'center', justifyContent: 'center' }, saveButton: { backgroundColor: colors.pastelOrange }, cancelButton: { backgroundColor: '#E5E7EB' }, - inviteButton: { backgroundColor: colors.primary }, - buttonText: { color: '#ffffffff', fontSize: 15, fontWeight: '600' }, + inviteButton: { backgroundColor: colors.pastelOrange }, + buttonText: { color: colors.black, fontSize: 15, fontWeight: '600' }, floatingButtonContainer: { position: 'absolute', bottom: 85, diff --git a/src/screens/management/ManagementScreen.tsx b/src/screens/management/ManagementScreen.tsx index e5634de..0bcad58 100644 --- a/src/screens/management/ManagementScreen.tsx +++ b/src/screens/management/ManagementScreen.tsx @@ -161,8 +161,8 @@ const ManagementScreen: FC = () => { const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f6fa' }, header: { paddingHorizontal: 20, paddingBottom: 15, paddingTop: 10 }, - title: { fontSize: 22, fontWeight: "600", color: colors.Settings }, - segmentedControl: { flexDirection: 'row',borderRadius: 10, marginHorizontal: 20, marginBottom: 20, padding: 3 }, + title: { fontSize: 22, fontWeight: "600"}, + segmentedControl: { flexDirection: 'row',borderRadius: 10,marginBottom: 20, padding: 3 }, segmentButton: { flex: 1, paddingVertical: 12, alignItems: 'center', borderRadius: 8 }, segmentActive: { backgroundColor: colors.pastelOrange, elevation: 5,borderRadius:15, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 5 }, segmentText: { fontSize: 14, fontWeight: '600', color: '#6c757d' }, diff --git a/src/screens/management/ManagementTabNavigation.tsx b/src/screens/management/ManagementTabNavigation.tsx index f961a8e..1703e63 100644 --- a/src/screens/management/ManagementTabNavigation.tsx +++ b/src/screens/management/ManagementTabNavigation.tsx @@ -1,22 +1,33 @@ import React from 'react'; -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -import { View, Text, StyleSheet, Platform, TouchableOpacity } from 'react-native'; +import { createBottomTabNavigator, BottomTabBarProps } from '@react-navigation/bottom-tabs'; +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import {Ionicons} from '@react-native-vector-icons/ionicons'; // Correct import for react-native-vector-icons - +import { Ionicons } from '@react-native-vector-icons/ionicons'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { RootStackParamList } from '../../navigation/AppNavigator'; // Adjust path -import EditGroupScreen from './EditGroupScreen'; // Your screen component +import colors from '../../components/colors'; + + +// --- Screen Imports --- +import EditGroupScreen from './EditGroupScreen'; import Payments from './Payments'; import Member from './Member'; -type Props = NativeStackScreenProps; +// --- Type Definitions --- +type Props = NativeStackScreenProps; const Tab = createBottomTabNavigator(); -// --- Custom Floating Tab Bar Component --- -import type { BottomTabBarProps } from '@react-navigation/bottom-tabs'; +// --- Icon Mapping --- +const iconMap: { [key: string]: string } = { + EditGroup: 'trail-sign-outline', + Payments: 'trending-up', + Member: 'people-outline', +}; + + +// --- Custom Floating Tab Bar Component --- const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) => { const insets = useSafeAreaInsets(); @@ -24,13 +35,10 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) = 0 ? insets.bottom : 16 }]}> {state.routes.map((route, index) => { const { options } = descriptors[route.key]; - const label = options.tabBarLabel ?? options.title ?? route.name; - - console.log("option : ", options) - // A custom option to pass the icon name - const iconName = label === "Settings" ? "trail-sign-outline" : label === "Payments" ? "trending-up" : label === "Members" ? "people-outline" : "flower"; - + const label = options.tabBarLabel?.toString() ?? options.title ?? route.name; const isFocused = state.index === index; + + const iconName = iconMap[route.name] || 'help-circle-outline'; const onPress = () => { const event = navigation.emit({ @@ -60,15 +68,16 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) = testID={options.tabBarButtonTestID} onPress={onPress} onLongPress={onLongPress} - style={styles.tabItem} + // The style for the tab item itself no longer needs elevation + style={[styles.tabItem, { backgroundColor: isFocused ? colors.pastelOrange : 'transparent' }]} > - - {label as string} + + {label} ); @@ -77,69 +86,61 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) = ); }; + // --- Main Tab Navigator Component --- const ManagementTabNavigation: React.FC = ({ route }) => { - const { groupId } = route.params + const { groupId } = route.params; return ( } screenOptions={{ - headerShown: false, // Hides the default header for all screens in the tab + headerShown: false, }} > - {/* You can easily add more tabs here in the future */} ); }; + // --- Styles --- const styles = StyleSheet.create({ tabBarContainer: { flexDirection: 'row', position: 'absolute', - left: 30, - right: 30, + left: 20, // Adjusted for better visual balance + right: 20, // Adjusted for better visual balance backgroundColor: 'white', - borderRadius: 60, - paddingVertical: 4, + borderRadius: 30, + paddingVertical: 8, + paddingHorizontal: 8, // Added padding alignItems: 'center', justifyContent: 'space-around', - + // iOS Shadow shadowColor: '#000', shadowOffset: { width: 0, @@ -147,14 +148,24 @@ const styles = StyleSheet.create({ }, shadowOpacity: 0.1, shadowRadius: 10, + // Android Shadow elevation: 8, }, tabItem: { - flex: 1, + flex: 1, // Allow items to fill the space alignItems: 'center', justifyContent: 'center', - paddingVertical: 8, + paddingVertical: 10, // Increased padding for a better touch area + borderRadius: 25, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 4, + }, + shadowOpacity: 0.1, + shadowRadius: 10, // This is for the background color highlight }, + // The 'activeTab' style is no longer needed as the logic is handled inline tabLabel: { fontSize: 12, marginTop: 4, diff --git a/src/screens/management/Member.tsx b/src/screens/management/Member.tsx index eb0a0bc..2b533db 100644 --- a/src/screens/management/Member.tsx +++ b/src/screens/management/Member.tsx @@ -56,7 +56,7 @@ const MemberScreen = ({ route }) => { setLoading(true); try { const response = await apiCall( - `https://api.godemo.in/FundCollect/api/fund/group/all/member?groupId=${groupId}`, + `http://192.168.1.5:9060/api/fund/group/all/member?groupId=${groupId}`, 'GET' ); @@ -118,12 +118,7 @@ const MemberScreen = ({ route }) => { if (error) { return ( - - Oops! - {error} - - Try Again - + no members found ); } @@ -170,9 +165,7 @@ const styles = StyleSheet.create({ paddingHorizontal: 20, paddingTop: 20, paddingBottom: 12, - backgroundColor: 'white', - borderBottomWidth: 1, - borderBottomColor: '#E5E7EB', + }, headerTitle: { fontSize: 28, diff --git a/src/screens/management/Payments.tsx b/src/screens/management/Payments.tsx index b259d3d..a241901 100644 --- a/src/screens/management/Payments.tsx +++ b/src/screens/management/Payments.tsx @@ -8,7 +8,7 @@ import { apiCall } from '../../utils/ApiHelper'; import { BASE_URL } from '../../utils/ApiUrls'; // --- CONSTANTS --- -const API_BASE_URL = 'https://api.godemo.in/FundCollect/api/payments'; +const API_BASE_URL = 'http://192.168.1.5:9060/api/payments'; // --- TYPE DEFINITIONS --- interface ApiResponse { @@ -43,54 +43,61 @@ interface PaymentDetails { type PaymentStackParamList = { Payments: { groupId: string } }; type PaymentsRouteProp = RouteProp; +// The component is a valid React Functional Component, which is required for using hooks. const Payments: React.FC = () => { + // --- HOOKS --- + // All hooks are called at the top level, which is correct. const route = useRoute(); const { groupId } = route.params; const [paymentDetails, setPaymentDetails] = useState(null); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); - - // State for the waiver modal + const [isGroupActive, setIsGroupActive] = useState(true); const [isModalVisible, setModalVisible] = useState(false); const [currentParticipant, setCurrentParticipant] = useState(null); const [waiverAmount, setWaiverAmount] = useState(''); const [cycleData, setCycleData] = useState(); + // --- FUNCTIONS --- + // These are regular functions and do not call hooks inside them, which is correct. const getCycleData = async () => { try { - - const response = await apiCall(`${API_BASE_URL}/groups/${groupId}/cycle-dashboard`, 'GET') as ApiResponse; + const response = await apiCall(`${API_BASE_URL}/groups/${groupId}/cycle-dashboard`, 'GET') as ApiResponse; if (response && response.statusCode === 200) { setCycleData(response.data); - } else { - Alert.alert("Error", response?.message || "Failed to fetch cycle data."); } } catch (error: any) { - Alert.alert("Error", error.message || "An unexpected error occurred."); + console.error("Failed to fetch cycle data:", error.message); } }; const fetchPaymentDetails = useCallback(async () => { if (!refreshing) setLoading(true); + setIsGroupActive(true); + try { const response = await apiCall(`${BASE_URL}payments/current/payment/details/${groupId}`, 'GET') as ApiResponse; - console.log("payment response :", response) if (response && response.statusCode === 200) { setPaymentDetails(response.data); + } else if (response && response.statusCode === 400) { + setIsGroupActive(false); + setPaymentDetails(null); } else { - Alert.alert("Error", response?.message || "Failed to fetch details."); + setIsGroupActive(false); } } catch (error: any) { - // Alert.alert("Error", error.message || "An unexpected error occurred."); - console.log("payment error : ", error) + console.log("payment error: ", error); + setIsGroupActive(false); } finally { setLoading(false); setRefreshing(false); } }, [groupId, refreshing]); + // --- EFFECT HOOK --- + // useEffect is correctly used to call functions when the component mounts or dependencies change. useEffect(() => { getCycleData(); fetchPaymentDetails(); @@ -104,18 +111,17 @@ const Payments: React.FC = () => { const openWaiverDialog = (participant: Participant) => { setCurrentParticipant(participant); - setWaiverAmount(''); + setWaiverAmount(participant.lateFee.toString()); setModalVisible(true); }; - // --- UPDATED: handleWaiveFee now uses the correct API endpoint --- const handleWaiveFee = async () => { if (!currentParticipant || !paymentDetails) return; const amountToWaive = parseFloat(waiverAmount); - if (isNaN(amountToWaive) || amountToWaive <= 0) { - Alert.alert("Invalid Input", "Please enter a waiver amount greater than zero."); + if (isNaN(amountToWaive) || amountToWaive < 0) { + Alert.alert("Invalid Input", "Please enter a valid waiver amount."); return; } @@ -125,15 +131,13 @@ const Payments: React.FC = () => { } try { - // Construct the exact URL as provided const url = `${BASE_URL}groups/${groupId}/cycles/${paymentDetails.currentCycle}/waive/${currentParticipant.userId}?amountToWaive=${amountToWaive}`; - - const response = await apiCall(url, 'PUT', {}) as any; // Body is empty as amount is in URL + const response = await apiCall(url, 'PUT', {}) as any; if (response && response.statusCode === 200) { Alert.alert("Success", `₹${amountToWaive} has been waived successfully.`); setModalVisible(false); - onRefresh(); // Refresh data to reflect the change + onRefresh(); } else { throw new Error(response?.message || "Failed to apply waiver."); } @@ -142,13 +146,14 @@ const Payments: React.FC = () => { } }; + // --- RENDER LOGIC --- const renderParticipant = (participant: Participant) => { const isPending = participant.status === 'PENDING'; - const canWaive = isPending && participant.lateFee > 0; + const canWaive = isPending && participant.lateFee > 0 && !participant.lateFeeWaived; return ( - + {participant.firstName} {participant.lastName} {participant.mobile} @@ -163,7 +168,7 @@ const Payments: React.FC = () => { {isPending && participant.lateFee > 0 && ( Late Fee: ₹{participant.lateFee} )} - {participant.lateFee === 0 && participant.lateFeeWaived && Fee Waived} + {participant.lateFeeWaived && Fee Waived} @@ -210,36 +215,48 @@ const Payments: React.FC = () => { - } - > - {loading && !refreshing ? ( - - ) : ( - <> - - {paymentDetails?.groupName} - - - - Cycle - {paymentDetails?.currentCycle} + {loading && !refreshing ? ( + + ) : !isGroupActive ? ( + + + + This group is not currently active. + + + Tap to Refresh + + + ) : ( + } + > + {paymentDetails && ( + <> + + {paymentDetails.groupName} - - Contribution - ₹{paymentDetails?.contributionAmount} + + + Cycle + {paymentDetails.currentCycle} + + + Contribution + ₹{paymentDetails.contributionAmount} + + + Total Due + ₹{paymentDetails.totalDueWithLateFee} + - - Total Due - ₹{paymentDetails?.totalDueWithLateFee} - - - Participants - {paymentDetails?.participants.map(renderParticipant)} - - )} - + Participants + {paymentDetails.participants.map(renderParticipant)} + + )} + + )} ); }; @@ -251,7 +268,7 @@ const styles = StyleSheet.create({ summaryContainer: { flexDirection: 'row', justifyContent: 'space-around', padding: 20, backgroundColor: '#fff', marginHorizontal: 15, borderRadius: 12, elevation: 2 }, summaryBox: { alignItems: 'center' }, summaryLabel: { fontSize: 14, color: '#6c757d', marginBottom: 4 }, - summaryValue: { fontSize: 18, fontWeight: '700', color: colors.primary }, + summaryValue: { fontSize: 18, fontWeight: '700', color: colors.black }, sectionTitle: { fontSize: 20, fontWeight: 'bold', marginLeft: 20, marginTop: 20, marginBottom: 10 }, participantCard: { backgroundColor: '#fff', marginHorizontal: 15, marginBottom: 10, borderRadius: 10, padding: 15, flexDirection: 'row', alignItems: 'center', elevation: 1 }, participantInfo: { flex: 1 }, @@ -274,6 +291,30 @@ const styles = StyleSheet.create({ cancelButtonText: { color: '#333', fontWeight: 'bold' }, saveButton: { backgroundColor: colors.primary }, saveButtonText: { color: 'white', fontWeight: 'bold' }, + fullScreenMessage: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#f4f6f8', + padding: 20, + }, + fullScreenMessageText: { + fontSize: 18, + color: '#333', + textAlign: 'center', + marginTop: 15, + fontWeight: 'bold', + }, + refreshButton: { + marginTop: 20, + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + }, + refreshButtonText: { + color: colors.black, + fontWeight: 'bold', + }, }); export default Payments; diff --git a/src/screens/profile/BecomeOrg.tsx b/src/screens/profile/BecomeOrg.tsx index 02d987c..ba9a94b 100644 --- a/src/screens/profile/BecomeOrg.tsx +++ b/src/screens/profile/BecomeOrg.tsx @@ -43,7 +43,7 @@ const BecomeOrg = () => { const token = credentials.password; const response = await fetch( - "https://api.godemo.in/FundCollect/api/organizer/registration/apply", + "http://192.168.1.5:9060/api/organizer/registration/apply", { method: "POST", headers: { diff --git a/src/screens/profile/Profile.tsx b/src/screens/profile/Profile.tsx index 3e44d59..3d57103 100644 --- a/src/screens/profile/Profile.tsx +++ b/src/screens/profile/Profile.tsx @@ -29,7 +29,7 @@ import { clearUser, setUserData } from "../../redux/userSlice"; import { StackNavigationProp } from "@react-navigation/stack"; import { RootStackParamList } from "../../navigation/AppNavigator"; import { firebase } from "@react-native-firebase/messaging"; -import FirebaseService from "../../utils/firebase/firebaseServices"; +import FirebaseService, { deleteFcmToken } from "../../utils/firebase/firebaseServices"; type UserProfile = { @@ -305,7 +305,7 @@ const Profile = () => { return; } - await FirebaseService.deleteFcmToken(); + await deleteFcmToken(); // Clear stored credentials await Keychain.resetGenericPassword(); @@ -377,7 +377,7 @@ const Profile = () => { User Profile confirmLogout(navigation)} style={styles.refreshButton}> - + {/* if (fetching || !user) { @@ -701,7 +701,7 @@ const styles = StyleSheet.create({ borderBottomRightRadius: 20, }, headerTitle: { - fontSize: 22, fontWeight: "600", color: colors.Profile + fontSize: 22, fontWeight: "600", color: colors.black }, refreshButton: { padding: 4, diff --git a/src/utils/ApiHelper.tsx b/src/utils/ApiHelper.tsx index 3306406..d16a1ab 100644 --- a/src/utils/ApiHelper.tsx +++ b/src/utils/ApiHelper.tsx @@ -36,32 +36,31 @@ export async function apiCall( }); // --- Response Logging --- - console.log(`--- API Response: [${response}] ${url} ---`); - // Uncomment the line below to see the full response data in logs - // console.log("Response Data:", JSON.stringify(response.data, null, 2)); - console.log("---------------------------------------",response); + console.log(`--- API Response:${url}///// [${response}] `); return response.data; } catch (error: any) { if (error.response) { // Server responded with a status other than 2xx - console.error( - "API Error:", - `Status: ${error.response.status}`, - `Data: ${JSON.stringify(error.response.data)}` - ); + // console.error( + // "API Error:", + // `Status: ${error.response.status}`, + // `Data: ${JSON.stringify(error.response.data)}` + // ); if(error.response.status === 400){ + console.log("API Error:9090", url); ToastAndroid.show(error.response.data.message || responseErrorMessage, ToastAndroid.SHORT) } + } else if (error.request) { // Request was made but no response received - console.error("API Error: No response received", error.request); + // console.error("API Error: No response received", error.request); } else { // Something else went wrong while setting up the request - console.error("API Error:", error); + // console.error("API Error:", error); } - console.error("-----------------"); - + console.log(`API : ${url} API Error:`, error); + return error.response.status // Re-throw the error so that the calling function can handle it if needed throw error; } diff --git a/src/utils/ApiUrls.tsx b/src/utils/ApiUrls.tsx index 57d94da..eb91849 100644 --- a/src/utils/ApiUrls.tsx +++ b/src/utils/ApiUrls.tsx @@ -1,6 +1,6 @@ -// export const BASE_URL = "https://api.godemo.in/FundCollect/api/" -export const BASE_URL = "https://api.godemo.in/FundCollect/api/" +// export const BASE_URL = "http://192.168.1.5:9060/api/" +export const BASE_URL = "http://192.168.1.5:9060/api/" export const LOGIN_URL = `${BASE_URL}user/generate/otp?mobiles=` export const VARIFY_OTP_URL = `${BASE_URL}user/otp/verify` export const ORGRAGISTRATION = `${BASE_URL}organizer/registration/apply` diff --git a/src/utils/firebase/firebaseServices.tsx b/src/utils/firebase/firebaseServices.tsx index e1a55b9..1d0790e 100644 --- a/src/utils/firebase/firebaseServices.tsx +++ b/src/utils/firebase/firebaseServices.tsx @@ -4,105 +4,118 @@ import axios from 'axios'; import { apiCall } from '../ApiHelper'; import { REMOVE_FCM_TOKEN, STORE_FCM_TOKEN } from '../ApiUrls'; -// replace with your backend +// 🔹 Request notification permission +export const requestPermission = async () => { + await messaging().requestPermission(); + await notifee.requestPermission(); +}; -class FirebaseService { - // 🔹 Request notification permission - static async requestPermission() { - await messaging().requestPermission(); - await notifee.requestPermission(); - } - - // 🔹 Display local notification - static async displayNotification(title: string, body: string) { - await notifee.displayNotification({ - title, - body, - android: { - channelId: 'default', - smallIcon: 'ic_launcher', // ensure you have this icon in android/app/src/main/res/mipmap - importance: AndroidImportance.HIGH, - pressAction: { - id: 'default', - }, +// 🔹 Display local notification +export const displayNotification = async (title: string, body: string) => { + await notifee.displayNotification({ + title, + body, + android: { + channelId: 'default', + smallIcon: 'ic_launcher', // ensure you have this icon in android/app/src/main/res/mipmap + importance: AndroidImportance.HIGH, + pressAction: { + id: 'default', }, - }); - } + }, + }); +}; - // 🔹 Get & send token to backend - static async getFcmToken(userId: string) { - console.log('FCM Token o'); - try { - console.log('FCM Token 1'); - const token = await messaging().getToken(); - console.log('FCM Token 2'); - console.log('FCM Token login: ', token); - await apiCall(STORE_FCM_TOKEN+token, 'POST'); - console.log('FCM Token sent to backend'); - return token; - } catch (error) { - console.error('Error getting FCM token:', error); - return null; +// 🔹 Get & send token to backend +export const getFcmToken = async (userId: string) => { + console.log('FCM Token o'); + try { + console.log('FCM Token 1'); + const token = await messaging().getToken(); + console.log('FCM Token 2'); + console.log('FCM Token login:', token); + await apiCall(STORE_FCM_TOKEN + token, 'POST'); + console.log('FCM Token sent to backend'); + return token; + } catch (error) { + console.error('Error getting FCM token:', error); + return null; + } +}; + +// 🔹 Delete token from backend +export const deleteFcmToken = async () => { + try { + console.log('FCM Token 1'); + const token = await messaging().getToken(); + console.log('FCM Token 2'); + console.log('fcm token logout -', token); + await apiCall(REMOVE_FCM_TOKEN + token, 'POST'); + console.log('FCM Token deleted from backend'); + } catch (error) { + console.error('Error deleting FCM token:', error); + } +}; + +// 🔹 Foreground notifications +export const foregroundListener = () => { + return messaging().onMessage(async remoteMessage => { + console.log('Foreground message:', remoteMessage); + const { messageId, notification, data } = remoteMessage; + if (!remoteMessage) return; + const { title, body } = data || {}; + if (remoteMessage) { + await displayNotification( + (title as string) ?? 'New Message', + (body as string) ?? '' + ); } - } + }); +}; - // 🔹 Delete token from backend - static async deleteFcmToken() { - try { - console.log('FCM Token 1'); - const token = await messaging().getToken(); - console.log('FCM Token 2'); - console.log("fcm token logout -",token) - await apiCall(REMOVE_FCM_TOKEN+token, 'POST'); - console.log('FCM Token deleted from backend'); - } catch (error) { - console.error('Error deleting FCM token:', error); +// 🔹 Background & Quit state +export const backgroundHandler = () => { + messaging().setBackgroundMessageHandler(async remoteMessage => { + console.log('Background message:', remoteMessage); + const { messageId, notification, data } = remoteMessage; + if (!remoteMessage) return; + const { title, body } = data || {}; + if (remoteMessage) { + await displayNotification( + (title as string) ?? 'New Message', + (body as string) ?? '' + ); } - } + }); - // 🔹 Foreground notifications - static foregroundListener() { - return messaging().onMessage(async remoteMessage => { - console.log('Foreground message:', remoteMessage); - if (remoteMessage.notification) { - await this.displayNotification( - remoteMessage.notification.title ?? 'New Message', - remoteMessage.notification.body ?? '' - ); - } - }); - } + messaging().onNotificationOpenedApp(remoteMessage => { + console.log('App opened from background:', remoteMessage); + }); - // 🔹 Background & Quit state - static backgroundHandler() { - messaging().setBackgroundMessageHandler(async remoteMessage => { - console.log('Background message:', remoteMessage); - if (remoteMessage.notification) { - await this.displayNotification( - remoteMessage.notification.title ?? 'New Message', - remoteMessage.notification.body ?? '' - ); - } - }); - - messaging().onNotificationOpenedApp(remoteMessage => { - console.log('App opened from background:', remoteMessage); - }); - - messaging().getInitialNotification().then(remoteMessage => { + messaging() + .getInitialNotification() + .then(remoteMessage => { if (remoteMessage) { console.log('App opened from quit state:', remoteMessage); } }); - } +}; - // 🔹 Token auto-refresh - static tokenRefreshHandler() { - return messaging().onTokenRefresh(async token => { - console.log('New refreshed FCM Token:', token); - // 👉 send to backend - }); - } -} +// 🔹 Token auto-refresh +export const tokenRefreshHandler = () => { + return messaging().onTokenRefresh(async token => { + console.log('New refreshed FCM Token:', token); + // 👉 send to backend + }); +}; -export default FirebaseService; +// Optional: Export all functions as a single object for easier imports +export default { + requestPermission, + displayNotification, + getFcmToken, + deleteFcmToken, + foregroundListener, + backgroundHandler, + tokenRefreshHandler, +};