This commit is contained in:
Abin Toppo
2025-10-01 15:43:42 +05:30
27 changed files with 526 additions and 2300 deletions
+132 -87
View File
@@ -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}
+13 -20
View File
@@ -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>
+1 -1
View File
@@ -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'
);
+18 -1
View File
@@ -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,