testing
This commit is contained in:
@@ -61,7 +61,7 @@ export interface GroupDetails {
|
||||
organizerId: string;
|
||||
totalParticipants: number;
|
||||
participants: Participant[];
|
||||
imageBlob:string
|
||||
imageBlob: string
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string };
|
||||
const response = await apiCall(`http://192.168.1.9:9060/api/fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string };
|
||||
console.log("groupDetails", response)
|
||||
if (response.status === 'success' && response.data) {
|
||||
setGroupDetails(response.data);
|
||||
@@ -154,7 +154,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
}
|
||||
}, [groupId]);
|
||||
const getPaymentStatus = async()=>{
|
||||
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/payments/group/${groupId}/payment/paid`, 'GET') as { status: string; data?: { currentCycle: number ,allPaid: boolean }; message?: string };
|
||||
const response = await apiCall(`http://192.168.1.9:9060/api/payments/group/${groupId}/payment/paid`, 'GET') as { status: string; data?: { currentCycle: number ,allPaid: boolean }; message?: string };
|
||||
console.log("paymentStatus",response)
|
||||
if (response.status === 'success') {
|
||||
setPaymentStatus(response.data?.allPaid || false);
|
||||
@@ -188,7 +188,7 @@ const getPaymentStatus = async()=>{
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/groups/${groupDetails.id}/edit`, 'PUT', editableData);
|
||||
await apiCall(`http://192.168.1.9:9060/api/fund/groups/${groupDetails.id}/edit`, 'PUT', editableData);
|
||||
Alert.alert('Success', 'Group has been updated.');
|
||||
setEditingCard(null);
|
||||
await fetchGroupDetails();
|
||||
@@ -199,7 +199,7 @@ const getPaymentStatus = async()=>{
|
||||
}
|
||||
};
|
||||
const handleDistribute = async () => {
|
||||
navigation.navigate('RandomSelectUserScreen', { groupId: groupDetails?.id || '', cycleNumber: currentCycle || 0 });
|
||||
navigation.navigate('RandomSelectUserScreen', { groupId: groupDetails?.id || '', cycleNumber: currentCycle || 0 });
|
||||
};
|
||||
|
||||
const handleStartGroup = async () => {
|
||||
@@ -216,7 +216,7 @@ const getPaymentStatus = async()=>{
|
||||
onPress: async () => {
|
||||
setStarting(true);
|
||||
try {
|
||||
await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/group/start?groupId=${groupDetails.id}`, 'GET');
|
||||
await apiCall(`http://192.168.1.9:9060/api/fund/group/start?groupId=${groupDetails.id}`, 'GET');
|
||||
Alert.alert('Success', 'Group has been started successfully.');
|
||||
await fetchGroupDetails();
|
||||
} catch (e: any) {
|
||||
@@ -235,7 +235,7 @@ const getPaymentStatus = async()=>{
|
||||
if (!groupDetails) return;
|
||||
setInviting(true);
|
||||
try {
|
||||
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/invite/generate?groupId=${groupDetails.id}`, 'GET') as { data?: string };
|
||||
const response = await apiCall(`http://192.168.1.9:9060/api/invite/generate?groupId=${groupDetails.id}`, 'GET') as { data?: string };
|
||||
|
||||
|
||||
const inviteLink = response.data;
|
||||
@@ -354,82 +354,127 @@ const getPaymentStatus = async()=>{
|
||||
const isInvitable = groupDetails.status === 'APPROVED';
|
||||
const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants;
|
||||
|
||||
// const pickImage = () => {
|
||||
// launchImageLibrary({ mediaType: "photo", quality: 0.5 }, (response) => {
|
||||
// if (response.didCancel) return;
|
||||
// if (response.errorCode) {
|
||||
// Alert.alert("Error", response.errorMessage || "Something went wrong");
|
||||
// } else if (response.assets && response.assets.length > 0) {
|
||||
// const selectedImage = response.assets[0];
|
||||
// console.log(selectedImage?.uri)
|
||||
// setSelectedImage(selectedImage?.uri)
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
const pickImage = () => {
|
||||
launchImageLibrary({ mediaType: "photo", quality: 0.5 }, (response) => {
|
||||
if (response.didCancel) return;
|
||||
|
||||
if (response.errorCode) {
|
||||
Alert.alert("Error", response.errorMessage || "Something went wrong");
|
||||
} else if (response.assets && response.assets.length > 0) {
|
||||
const selectedImage = response.assets[0];
|
||||
console.log(selectedImage?.uri)
|
||||
setSelectedImage(selectedImage?.uri)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.assets || response.assets.length === 0) {
|
||||
Alert.alert("Error", "No image selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedImage = response.assets[0];
|
||||
const maxSizeKB = 1000;
|
||||
const maxSizeBytes = maxSizeKB * 1024;
|
||||
|
||||
// Size validation
|
||||
if (selectedImage.fileSize && selectedImage.fileSize > maxSizeBytes) {
|
||||
const currentSizeKB = (selectedImage.fileSize / 1024).toFixed(2);
|
||||
Alert.alert(
|
||||
"File Too Large",
|
||||
`Selected image is ${currentSizeKB}KB. Please select an image smaller than ${maxSizeKB}KB.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Additional validation for required fields
|
||||
if (!selectedImage.uri) {
|
||||
Alert.alert("Error", "Invalid image file");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("✅ Image selected:", {
|
||||
uri: selectedImage.uri,
|
||||
size: (selectedImage.fileSize / 1024).toFixed(2) + "KB",
|
||||
width: selectedImage.width,
|
||||
height: selectedImage.height
|
||||
});
|
||||
|
||||
setSelectedImage(selectedImage.uri);
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateImage = async () => {
|
||||
try {
|
||||
const credentials = await Keychain.getGenericPassword();
|
||||
if (!credentials) {
|
||||
Alert.alert("Error", "You are not logged in");
|
||||
return;
|
||||
}
|
||||
const token = credentials.password;
|
||||
|
||||
if (!selectedImage) {
|
||||
Alert.alert("Error", "Please select an image first");
|
||||
return;
|
||||
try {
|
||||
const credentials = await Keychain.getGenericPassword();
|
||||
if (!credentials) {
|
||||
Alert.alert("Error", "You are not logged in");
|
||||
return;
|
||||
}
|
||||
const token = credentials.password;
|
||||
|
||||
if (!selectedImage) {
|
||||
Alert.alert("Error", "Please select an image first");
|
||||
return;
|
||||
}
|
||||
|
||||
setImageUpdating(true);
|
||||
|
||||
// Get file extension and mime type dynamically
|
||||
const fileExtension = selectedImage.split('.').pop();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", {
|
||||
uri: selectedImage,
|
||||
type: "image/jpeg",
|
||||
name: `group_${groupId}_profile.${fileExtension || "jpg"}`,
|
||||
});
|
||||
|
||||
const url = `${UPLOAD_GROUP_IMAGE}${groupId}/upload-image`;
|
||||
|
||||
console.log("url : ", url)
|
||||
console.log("formdata : ", formData)
|
||||
|
||||
// return
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
console.log("image response : ", response)
|
||||
if (response?.status === 200) {
|
||||
fetchGroupDetails()
|
||||
ToastAndroid.show('Group uploaded successfully', ToastAndroid.SHORT)
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("❌ Error updating group image:", error);
|
||||
Alert.alert("Error", error.message || "Could not update group image");
|
||||
} finally {
|
||||
setImageUpdating(false);
|
||||
}
|
||||
};
|
||||
|
||||
setImageUpdating(true);
|
||||
|
||||
// Get file extension and mime type dynamically
|
||||
const fileExtension = selectedImage.split('.').pop();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", {
|
||||
uri: selectedImage,
|
||||
type: "image/jpeg",
|
||||
name: `group_${groupId}_profile.${fileExtension || "jpg"}`,
|
||||
});
|
||||
|
||||
const url = `${UPLOAD_GROUP_IMAGE}${groupId}/upload-image`;
|
||||
|
||||
console.log("url : ", url)
|
||||
console.log("formdata : ", formData)
|
||||
|
||||
// return
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
console.log("image response : ", response)
|
||||
if(response?.status === 200){
|
||||
fetchGroupDetails()
|
||||
ToastAndroid.show('Group uploaded successfully', ToastAndroid.SHORT)
|
||||
}
|
||||
|
||||
} catch (error:any) {
|
||||
console.error("❌ Error updating group image:", error);
|
||||
Alert.alert("Error", error.message || "Could not update group image");
|
||||
} finally {
|
||||
setImageUpdating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getProfileImage = () => {
|
||||
return selectedImage
|
||||
? selectedImage
|
||||
: groupDetails?.imageBlob
|
||||
? `data:image/jpeg;base64,${groupDetails.imageBlob}`
|
||||
: "https://img.freepik.com/premium-vector/people-group-frame-icon_791764-2649.jpg";
|
||||
};
|
||||
// Helper function to get MIME type
|
||||
const getProfileImage = () => {
|
||||
return selectedImage
|
||||
? selectedImage
|
||||
: groupDetails?.imageBlob
|
||||
? `data:image/jpeg;base64,${groupDetails.imageBlob}`
|
||||
: "https://img.freepik.com/premium-vector/people-group-frame-icon_791764-2649.jpg";
|
||||
};
|
||||
// Helper function to get MIME type
|
||||
|
||||
|
||||
return (
|
||||
@@ -461,12 +506,12 @@ const getProfileImage = () => {
|
||||
<Ionicons name="camera" size={18} color="#000" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={{justifyContent:'center'}}>
|
||||
<View style={{ justifyContent: 'center' }}>
|
||||
<CustomButton
|
||||
title={imageUpdating ? "Updating..." : "Update Image"}
|
||||
onPress={handleUpdateImage}
|
||||
icon={imageUpdating ? undefined : "cloud-upload"}
|
||||
style={{paddingHorizontal:20}}
|
||||
style={{ paddingHorizontal: 20 }}
|
||||
buttonBackground={colors.pastelBlue}
|
||||
/>
|
||||
</View>
|
||||
@@ -477,19 +522,19 @@ const getProfileImage = () => {
|
||||
], 'groupInfo')}
|
||||
|
||||
|
||||
{groupDetails.status === 'ACTIVE' ?
|
||||
renderCard('Financial Rules', [
|
||||
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
|
||||
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
|
||||
{ field: 'maxParticipants', label: 'Current Participants', displayValue: () => `${groupDetails.totalParticipants}` },
|
||||
], 'financialRules')
|
||||
:
|
||||
renderCard('Financial Rules', [
|
||||
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
|
||||
{ field: 'maxParticipants', label: 'Max Participants', displayValue: () => `${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}` },
|
||||
{ field: 'minParticipants', label: 'Min Participants', displayValue: () => `${groupDetails.minParticipants}` },
|
||||
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
|
||||
], 'financialRules')
|
||||
{groupDetails.status === 'ACTIVE' ?
|
||||
renderCard('Financial Rules', [
|
||||
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
|
||||
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
|
||||
{ field: 'maxParticipants', label: 'Current Participants', displayValue: () => `${groupDetails.totalParticipants}` },
|
||||
], 'financialRules')
|
||||
:
|
||||
renderCard('Financial Rules', [
|
||||
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
|
||||
{ field: 'maxParticipants', label: 'Max Participants', displayValue: () => `${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}` },
|
||||
{ field: 'minParticipants', label: 'Min Participants', displayValue: () => `${groupDetails.minParticipants}` },
|
||||
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
|
||||
], 'financialRules')
|
||||
}
|
||||
|
||||
|
||||
@@ -535,7 +580,7 @@ const getProfileImage = () => {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
{paymentStatus && (
|
||||
{paymentStatus && (
|
||||
<View style={styles.floatingButtonContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.activateButton}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// src/screens/ManagementScreen.tsx
|
||||
|
||||
import React, { useState, useCallback, FC, useEffect } from "react";
|
||||
import {
|
||||
View,
|
||||
@@ -16,13 +15,13 @@ import { APPROVE_REQUEST_URL, DECLINE_REQUEST_URL, GET_JOINED_GROUP_URL, MY_GROU
|
||||
import colors from "../../components/colors";
|
||||
import AppView from "../../components/AppView";
|
||||
import { apiCall } from "../../utils/ApiHelper";
|
||||
import { MyGroup, JoinedGroup, JoinRequest, ApiResponse, UserJoinRequest } from '../../types'; // Adjust import path
|
||||
import { MyGroup, JoinedGroup, JoinRequest, ApiResponse, UserJoinRequest } from '../../types';
|
||||
import { MyGroupCard } from "../../components/MyGroupCard";
|
||||
import { JoinedGroupCard } from "../../components/JoinedGroupCard";
|
||||
import { RequestCard } from "../../components/RequestCard";
|
||||
import { useSelector } from "react-redux";
|
||||
import { UserRequested } from "../../components/UserRequested";
|
||||
import { RootTabParamList } from "../../navigation/BottomTabs"; // Adjust import path
|
||||
import { RootTabParamList } from "../../navigation/BottomTabs";
|
||||
import { RootStackParamList } from "../../navigation/AppNavigator";
|
||||
|
||||
// --- TYPE DEFINITIONS ---
|
||||
@@ -34,7 +33,6 @@ const ManagementScreen: FC = () => {
|
||||
const user = useSelector((state: any) => state.user);
|
||||
const navigation = useNavigation<GroupManagementNavProp>();
|
||||
const route = useRoute<ManagementRouteProp>();
|
||||
|
||||
const [activeTab, setActiveTab] = useState<ActiveTab>(user.role === "USER" ? "Joined Groups" : "My Groups");
|
||||
const [myGroups, setMyGroups] = useState<MyGroup[]>([]);
|
||||
const [joinedGroups, setJoinedGroups] = useState<JoinedGroup[]>([]);
|
||||
@@ -43,9 +41,7 @@ const ManagementScreen: FC = () => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// --- HOOKS ---
|
||||
|
||||
// Effect to set the initial tab from route params
|
||||
useEffect(() => {
|
||||
const passedTab = route.params?.actionTab;
|
||||
if (passedTab) {
|
||||
@@ -56,9 +52,9 @@ const ManagementScreen: FC = () => {
|
||||
}
|
||||
}, [route.params?.actionTab, user.role]);
|
||||
|
||||
// --- FIXED: fetchData dependency array is corrected ---
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
// Check for loading state at the start to prevent multiple simultaneous fetches
|
||||
|
||||
if (loading) return;
|
||||
|
||||
setLoading(true);
|
||||
@@ -87,20 +83,18 @@ const ManagementScreen: FC = () => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
// The only external dependency that should trigger a re-fetch is the user's role.
|
||||
}, [user.role]); // **FIXED**: Removed `loading` from the dependency array
|
||||
|
||||
// --- FIXED: useFocusEffect now has a stable dependency ---
|
||||
}, [user.role]);
|
||||
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
fetchData();
|
||||
}, [fetchData]) // This will now only re-run if fetchData (and thus user.role) changes.
|
||||
}, [fetchData])
|
||||
);
|
||||
|
||||
// --- HANDLERS (No changes needed here) ---
|
||||
const handleViewGroup = (item: JoinedGroup) => {
|
||||
// Assuming you have an 'Invite' screen in your RootTabParamList or another navigator
|
||||
// navigation.navigate('Invite' as any, { groupId: item.id });
|
||||
|
||||
};
|
||||
|
||||
const handleAction = async (url: string, successMessage: string, failureMessage: string) => {
|
||||
@@ -122,10 +116,9 @@ const ManagementScreen: FC = () => {
|
||||
const handleDecline = (req: JoinRequest) => handleAction(DECLINE_REQUEST_URL + req.requestId + "/reject?reason=/", `Request from ${req.userName} has been declined.`, "Failed to decline request.");
|
||||
const handleManageGroup = (group: MyGroup) => navigation.navigate('ManagementTabs', { groupId: group.id });
|
||||
|
||||
// --- RENDER LOGIC ---
|
||||
const renderContent = () => {
|
||||
if (error) return <Text style={styles.emptyText}>{error}</Text>;
|
||||
// Improved loading logic to avoid hiding existing data during a refresh
|
||||
|
||||
if (loading && myGroups.length === 0 && joinedGroups.length === 0 && requests.length === 0) {
|
||||
return <ActivityIndicator size="large" color={colors.primary} style={styles.loader} />;
|
||||
}
|
||||
@@ -168,12 +161,12 @@ 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', backgroundColor: '#e9ecef', borderRadius: 10, marginHorizontal: 20, marginBottom: 20, padding: 3 },
|
||||
segmentedControl: { flexDirection: 'row',borderRadius: 10, marginHorizontal: 20, marginBottom: 20, padding: 3 },
|
||||
segmentButton: { flex: 1, paddingVertical: 12, alignItems: 'center', borderRadius: 8 },
|
||||
segmentActive: { backgroundColor: colors.pastelOrange, elevation: 3, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 5 },
|
||||
segmentActive: { backgroundColor: colors.pastelOrange, elevation: 5,borderRadius:15, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 5 },
|
||||
segmentText: { fontSize: 14, fontWeight: '600', color: '#6c757d' },
|
||||
segmentTextActive: { color: colors.textDark },
|
||||
contentArea: { flex: 1, paddingHorizontal: 20 },
|
||||
contentArea: { flex: 1},
|
||||
loader: { marginTop: 50 },
|
||||
emptyText: { textAlign: 'center', marginTop: 60, fontSize: 16, color: '#6c757d' },
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { View, Text, StyleSheet, Platform, 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'; // Correct import for react-native-vector-icons
|
||||
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { RootStackParamList } from '../../navigation/AppNavigator'; // Adjust path
|
||||
@@ -28,7 +28,7 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) =
|
||||
|
||||
console.log("option : ", options)
|
||||
// A custom option to pass the icon name
|
||||
const iconName = options.tabBarIconName ?? 'help-circle';
|
||||
const iconName = label === "Settings" ? "trail-sign-outline" : label === "Payments" ? "trending-up" : label === "Members" ? "people-outline" : "flower";
|
||||
|
||||
const isFocused = state.index === index;
|
||||
|
||||
@@ -79,7 +79,7 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) =
|
||||
|
||||
// --- Main Tab Navigator Component ---
|
||||
const ManagementTabNavigation: React.FC<Props> = ({ route }) => {
|
||||
const { groupId } = route.params
|
||||
const { groupId } = route.params
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
@@ -97,10 +97,10 @@ const ManagementTabNavigation: React.FC<Props> = ({ route }) => {
|
||||
options={{
|
||||
tabBarLabel: 'Settings',
|
||||
// Custom prop to define the icon for our FloatingTabBar
|
||||
|
||||
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
<Tab.Screen
|
||||
name="Payments"
|
||||
component={Payments}
|
||||
// Pass the groupId to the EditGroupScreen as an initial parameter
|
||||
@@ -111,16 +111,16 @@ const ManagementTabNavigation: React.FC<Props> = ({ route }) => {
|
||||
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
<Tab.Screen
|
||||
name="Member"
|
||||
component={Member}
|
||||
// Pass the groupId to the EditGroupScreen as an initial parameter
|
||||
initialParams={{ groupId }}
|
||||
initialParams={{ groupId }}
|
||||
options={{
|
||||
tabBarLabel: 'Members',
|
||||
|
||||
|
||||
}}
|
||||
|
||||
|
||||
/>
|
||||
{/* You can easily add more tabs here in the future */}
|
||||
</Tab.Navigator>
|
||||
|
||||
@@ -56,7 +56,7 @@ const MemberScreen = ({ route }) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await apiCall<ApiResponse>(
|
||||
`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/group/all/member?groupId=${groupId}`,
|
||||
`http://192.168.1.9:9060/api/fund/group/all/member?groupId=${groupId}`,
|
||||
'GET'
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import AppView from '../../components/AppView';
|
||||
import { apiCall } from '../../utils/ApiHelper';
|
||||
|
||||
// --- CONSTANTS ---
|
||||
const API_BASE_URL = 'https://exotic-humpback-mistakenly.ngrok-free.app/api/payments';
|
||||
const API_BASE_URL = 'http://192.168.1.9:9060/api/payments';
|
||||
|
||||
// --- TYPE DEFINITIONS ---
|
||||
interface ApiResponse<T> {
|
||||
@@ -54,6 +54,21 @@ const Payments: React.FC = () => {
|
||||
const [isModalVisible, setModalVisible] = useState(false);
|
||||
const [currentParticipant, setCurrentParticipant] = useState<Participant | null>(null);
|
||||
const [waiverAmount, setWaiverAmount] = useState('');
|
||||
const [cycleData, setCycleData] = useState<any>();
|
||||
|
||||
const getCycleData = async () => {
|
||||
try {
|
||||
|
||||
const response = await apiCall(`${API_BASE_URL}/groups/${groupId}/cycle-dashboard`, 'GET') as ApiResponse<any>;
|
||||
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.");
|
||||
}
|
||||
};
|
||||
|
||||
const fetchPaymentDetails = useCallback(async () => {
|
||||
if (!refreshing) setLoading(true);
|
||||
@@ -73,11 +88,13 @@ const Payments: React.FC = () => {
|
||||
}, [groupId, refreshing]);
|
||||
|
||||
useEffect(() => {
|
||||
getCycleData();
|
||||
fetchPaymentDetails();
|
||||
}, [fetchPaymentDetails]);
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true);
|
||||
getCycleData();
|
||||
fetchPaymentDetails();
|
||||
}, [fetchPaymentDetails]);
|
||||
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, FlatList, Dimensions, Alert, ActivityIndicator, Animated } from 'react-native';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, FlatList, Dimensions, Alert, ActivityIndicator, Animated, Button } from 'react-native';
|
||||
import { apiCall } from '../../utils/ApiHelper';
|
||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import ContributionDetails from '../ContributionScreen';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
const NUM_COLUMNS = 3;
|
||||
const ITEM_SIZE = width / NUM_COLUMNS - 20;
|
||||
|
||||
const RandomSelectUserScreen: React.FC<{ route: any }> = ({ route }) => {
|
||||
const navigation = useNavigation() as never
|
||||
const { groupId, cycleNumber } = route.params;
|
||||
const [users, setUsers] = useState<{ id: string; name: string }[]>([]);
|
||||
const [highlightedIndex, setHighlightedIndex] = useState<number | null>(null);
|
||||
const [winner, setWinner] = useState<{ id: string; name: string } | null>(null);
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [openModal, setOpenModal] = useState(false)
|
||||
|
||||
const scaleAnim = useRef(new Animated.Value(1)).current;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchParticipants = async () => {
|
||||
try {
|
||||
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/group/all/member?groupId=${groupId}`, "GET") as any;
|
||||
const response = await apiCall(`http://192.168.1.9:9060/api/fund/group/all/member?groupId=${groupId}`, "GET") as any;
|
||||
if (response.status === 'success' && response.data) {
|
||||
const formattedUsers = response.data.map((u: any) => ({
|
||||
id: u.id,
|
||||
@@ -73,10 +77,10 @@ const RandomSelectUserScreen: React.FC<{ route: any }> = ({ route }) => {
|
||||
const fetchWinner = async () => {
|
||||
const payload = { groupId: groupId };
|
||||
try {
|
||||
const response = await apiCall('https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/method/based/userSelect', 'POST', payload) as any;
|
||||
|
||||
if (response.status === 'success' && response.data?.id) {
|
||||
const winnerData = { id: response.data.id, name: `${response.data.firstName} ${response.data.lastName}` };
|
||||
const response = await apiCall('http://192.168.1.9:9060/api/fund/method/based/userSelect', 'POST', payload) as any;
|
||||
console.log("random select user response : ", response)
|
||||
if (response.status === 'success' && response.data?.selectedUser?.id) {
|
||||
const winnerData = { id: response?.data?.selectedUser?.id, name: `${response.data.selectedUser.firstName} ${response.data.selectedUser.lastName}` };
|
||||
const winnerIndex = users.findIndex(u => u.id === winnerData.id);
|
||||
|
||||
if (winnerIndex !== -1) {
|
||||
@@ -89,7 +93,7 @@ const RandomSelectUserScreen: React.FC<{ route: any }> = ({ route }) => {
|
||||
Alert.alert('Error', 'Failed to determine a winner from the API.');
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.alert('Error', 'Could not connect to the server to fetch the winner.');
|
||||
// Alert.alert('Error', 'Could not connect to the server to fetch the winner.');
|
||||
} finally {
|
||||
setIsAnimating(false);
|
||||
}
|
||||
@@ -147,9 +151,16 @@ const RandomSelectUserScreen: React.FC<{ route: any }> = ({ route }) => {
|
||||
<View style={styles.footer}>
|
||||
{winner && !isAnimating && (
|
||||
<View style={styles.winnerTextContainer}>
|
||||
<Text style={styles.winnerText}>🎉 Winner: {winner.name}</Text>
|
||||
<Text style={styles.winnerText}>🎉 Winner: {winner.name}</Text>
|
||||
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Button title='Pay' onPress={() => setOpenModal(true)} />
|
||||
{
|
||||
openModal && <ContributionDetails data={undefined} visible={openModal} onClose={() => setOpenModal(false)} onConfirm={() => setOpenModal(false)} />
|
||||
}
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.button, isAnimating && styles.buttonDisabled]}
|
||||
onPress={handleStartSelection}
|
||||
@@ -181,9 +192,9 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
header: {
|
||||
paddingTop: 50,
|
||||
paddingBottom: 20,
|
||||
alignItems: 'center'
|
||||
paddingTop: 50,
|
||||
paddingBottom: 20,
|
||||
alignItems: 'center'
|
||||
},
|
||||
title: {
|
||||
fontSize: 32,
|
||||
@@ -191,9 +202,9 @@ const styles = StyleSheet.create({
|
||||
color: '#111827',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 18,
|
||||
color: '#6b7280',
|
||||
marginTop: 4
|
||||
fontSize: 18,
|
||||
color: '#6b7280',
|
||||
marginTop: 4
|
||||
},
|
||||
grid: {
|
||||
paddingHorizontal: 10,
|
||||
@@ -211,8 +222,8 @@ const styles = StyleSheet.create({
|
||||
padding: 8,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 3.84,
|
||||
@@ -237,12 +248,12 @@ const styles = StyleSheet.create({
|
||||
color: '#000000ff',
|
||||
},
|
||||
footer: {
|
||||
width: '100%',
|
||||
padding: 20,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#e5e7eb',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fff'
|
||||
width: '100%',
|
||||
padding: 20,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#e5e7eb',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fff'
|
||||
},
|
||||
button: {
|
||||
paddingVertical: 16,
|
||||
@@ -252,8 +263,8 @@ const styles = StyleSheet.create({
|
||||
elevation: 3,
|
||||
shadowColor: "#4f46e5",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 4,
|
||||
width: 0,
|
||||
height: 4,
|
||||
},
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4.65,
|
||||
|
||||
Reference in New Issue
Block a user