colors
This commit is contained in:
Generated
+6
-14
@@ -4858,6 +4858,7 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@@ -8223,6 +8224,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"devOptional": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
@@ -8973,6 +8975,7 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
@@ -9856,6 +9859,7 @@
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
@@ -13885,7 +13889,6 @@
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz",
|
||||
"integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerate": "^1.4.2",
|
||||
@@ -13909,7 +13912,6 @@
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz",
|
||||
"integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==",
|
||||
"devOptional": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"jsesc": "~3.1.0"
|
||||
@@ -13918,18 +13920,6 @@
|
||||
"regjsparser": "bin/parser"
|
||||
}
|
||||
},
|
||||
"node_modules/regjsparser/node_modules/jsesc": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
|
||||
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@@ -14951,6 +14941,7 @@
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/throat": {
|
||||
@@ -15405,6 +15396,7 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
|
||||
@@ -41,6 +41,7 @@ export type RootStackParamList = {
|
||||
Invite: { groupId: string | number; code: string | number };
|
||||
ManagementTabs:{ groupId: string | number;}
|
||||
TransactionList:undefined
|
||||
RandomSelectUserScreen: { groupId: string | number, cycleNumber: number };
|
||||
};
|
||||
|
||||
const Stack = createNativeStackNavigator<RootStackParamList>();
|
||||
|
||||
@@ -222,7 +222,7 @@ const Home: React.FC = () => {
|
||||
return (
|
||||
<AppView barStyle="dark-content" style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.greeting}>Hello, {user.firstName || apiUser?.firstName} {user.lastName || apiUser?.lastName}👋</Text>
|
||||
<Text style={styles.greeting}>Hello, {user.firstName || apiUser?.firstName} {user.lastName || apiUser?.lastName}</Text>
|
||||
<TouchableOpacity onPress={() => navigation.navigate("Profile" as never)}>
|
||||
<Image
|
||||
source={
|
||||
@@ -270,7 +270,7 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
justifyContent: "space-around",
|
||||
},
|
||||
greeting: { fontSize: 22, fontWeight: "600", color: colors.Home },
|
||||
greeting: { fontSize: 22, fontWeight: "600", color: colors.pastelPink },
|
||||
gridButton: {
|
||||
flex: 1,
|
||||
margin: 8,
|
||||
|
||||
@@ -110,7 +110,8 @@ const UserProfileStatus: React.FC = () => {
|
||||
{/* <Text style={styles.title}>Welcome Back 👋</Text> */}
|
||||
|
||||
<LinearGradient
|
||||
colors={[colors.primary, colors.Home]}
|
||||
colors={[colors.pastelPink, colors.pastelPink
|
||||
]}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.card}
|
||||
|
||||
@@ -17,9 +17,10 @@ import AppView from '../../components/AppView';
|
||||
import { Picker } from '@react-native-picker/picker';
|
||||
import colors from '../../components/colors';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { CompositeScreenProps } from '@react-navigation/native';
|
||||
import { CompositeScreenProps, useNavigation } from '@react-navigation/native';
|
||||
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
|
||||
import { ManagementTabParamList } from './ManagementTabNavigation'; // Adjust path
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
|
||||
// --- Type Definitions ---
|
||||
@@ -79,11 +80,11 @@ type EditGroupScreenProps = CompositeScreenProps<
|
||||
|
||||
type EditingCard = 'groupInfo' | 'financialRules' | 'configuration' | null;
|
||||
|
||||
|
||||
type GroupManagementNavProp = StackNavigationProp<RootStackParamList, 'EditGroup'>;
|
||||
// --- Main Screen Component ---
|
||||
const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
const { groupId } = route.params;
|
||||
|
||||
const navigation = useNavigation<GroupManagementNavProp>();
|
||||
|
||||
const [editingCard, setEditingCard] = useState<EditingCard>(null);
|
||||
const [groupDetails, setGroupDetails] = useState<GroupDetails | null>(null);
|
||||
@@ -105,7 +106,8 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
const [starting, setStarting] = useState(false);
|
||||
const [inviting, setInviting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [paymentStatus, setPaymentStatus] = useState<boolean | null>(null);
|
||||
const [currentCycle, setCurrentCycle] = useState<number | null>(null);
|
||||
|
||||
const initializeEditableData = (data: GroupDetails) => {
|
||||
setEditableData({
|
||||
@@ -141,10 +143,19 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [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 ,status: boolean }; message?: string };
|
||||
console.log("paymentStatus",response)
|
||||
if (response.status === 'success') {
|
||||
setPaymentStatus(response.data?.status || false);
|
||||
setCurrentCycle(response.data?.currentCycle || null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroupDetails();
|
||||
getPaymentStatus();
|
||||
}, [fetchGroupDetails]);
|
||||
|
||||
|
||||
@@ -177,7 +188,9 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDistribute = async () => {
|
||||
navigation.navigate('RandomSelectUserScreen', { groupId: groupDetails?.id || '', cycleNumber: currentCycle || 0 });
|
||||
};
|
||||
|
||||
const handleStartGroup = async () => {
|
||||
if (!groupDetails) return;
|
||||
@@ -322,7 +335,6 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (loading) return <View style={styles.centered}><ActivityIndicator size="large" color="#4F46E5" /></View>;
|
||||
if (error) return <View style={styles.centered}><Text style={styles.errorText}>{error}</Text></View>;
|
||||
if (!groupDetails) return null;
|
||||
@@ -332,7 +344,6 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
const isInvitable = groupDetails.status === 'APPROVED';
|
||||
const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants;
|
||||
|
||||
|
||||
return (
|
||||
<AppView style={{ flex: 1, paddingHorizontal: 0 }}>
|
||||
<View style={styles.headerContainer}>
|
||||
@@ -414,6 +425,21 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
{paymentStatus && (
|
||||
<View style={styles.floatingButtonContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.activateButton}
|
||||
onPress={handleDistribute}
|
||||
disabled={starting}
|
||||
>
|
||||
{starting ? (
|
||||
<ActivityIndicator color="#FFFFFF" size="small" />
|
||||
) : (
|
||||
<Text style={styles.activateButtonText}>Distribute</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</AppView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,248 +1,285 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, Animated, Easing, SafeAreaView, Dimensions, Alert } from 'react-native';
|
||||
import { Confetti, ConfettiMethods } from 'react-native-fast-confetti';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, FlatList, Dimensions, Alert, ActivityIndicator, Animated } from 'react-native';
|
||||
import { apiCall } from '../../utils/ApiHelper';
|
||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||
|
||||
import AppView from '../../components/AppView';
|
||||
|
||||
|
||||
// --- PROPS & TYPE DEFINITIONS ---
|
||||
export interface RandomWinnerScreenProps {
|
||||
participants: Participant[];
|
||||
excludedParticipantIds: string[];
|
||||
}
|
||||
|
||||
export interface Participant {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// --- CONSTANTS ---
|
||||
const { width } = Dimensions.get('window');
|
||||
const ITEM_HEIGHT = 70; // Height of each name item
|
||||
const ANIMATION_DURATION = 8000; // 8 seconds for suspense
|
||||
const SHUFFLE_MULTIPLIER = 5; // Repeats the list for a longer scroll effect
|
||||
const NUM_COLUMNS = 3;
|
||||
const ITEM_SIZE = width / NUM_COLUMNS - 20;
|
||||
|
||||
const RandomSelectUserScreen: React.FC = () => {
|
||||
// --- MOCK DATA (Replace with navigation props) ---
|
||||
const mockProps: RandomWinnerScreenProps = {
|
||||
participants: [
|
||||
{ id: '1', name: 'Vinay M.' },
|
||||
{ id: '2', name: 'Nitesh Y.' },
|
||||
{ id: '3', name: 'Alice' },
|
||||
{ id: '4', name: 'Bob' },
|
||||
{ id: '5', name: 'Charlie' },
|
||||
{ id: '6', name: 'David' },
|
||||
{ id: '7', name: 'Eve' },
|
||||
],
|
||||
excludedParticipantIds: ['4'], // Exclude Bob
|
||||
};
|
||||
// --- END MOCK DATA ---
|
||||
const RandomSelectUserScreen: React.FC<{ route: any }> = ({ route }) => {
|
||||
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 { participants, excludedParticipantIds } = mockProps;
|
||||
const scaleAnim = useRef(new Animated.Value(1)).current;
|
||||
|
||||
const [eligibleParticipants, setEligibleParticipants] = useState<Participant[]>([]);
|
||||
const [isSelecting, setIsSelecting] = useState(false);
|
||||
const [winner, setWinner] = useState<Participant | null>(null);
|
||||
const [animationList, setAnimationList] = useState<Participant[]>([]);
|
||||
|
||||
const scrollY = useRef(new Animated.Value(0)).current;
|
||||
const confettiRef = useRef<ConfettiMethods>(null);
|
||||
|
||||
// Filter participants
|
||||
useEffect(() => {
|
||||
const eligible = participants.filter(p => !excludedParticipantIds.includes(p.id));
|
||||
setEligibleParticipants(eligible);
|
||||
}, [participants, excludedParticipantIds]);
|
||||
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;
|
||||
if (response.status === 'success' && response.data) {
|
||||
const formattedUsers = response.data.map((u: any) => ({
|
||||
id: u.id,
|
||||
name: `${u.firstName} ${u.lastName}`,
|
||||
}));
|
||||
setUsers(formattedUsers);
|
||||
} else {
|
||||
Alert.alert('Error', 'Failed to fetch participants.');
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.alert('Error', 'Could not connect to the server to fetch participants.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchParticipants();
|
||||
}, [groupId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (highlightedIndex !== null) {
|
||||
Animated.sequence([
|
||||
Animated.timing(scaleAnim, { toValue: 1.15, duration: 80, useNativeDriver: true }),
|
||||
Animated.timing(scaleAnim, { toValue: 1, duration: 80, useNativeDriver: true }),
|
||||
]).start();
|
||||
}
|
||||
}, [highlightedIndex]);
|
||||
|
||||
const handleStartSelection = () => {
|
||||
if (eligibleParticipants.length < 2) {
|
||||
Alert.alert('Not enough eligible participants.');
|
||||
return;
|
||||
}
|
||||
if (users.length === 0 || isAnimating) return;
|
||||
|
||||
setIsSelecting(true);
|
||||
setWinner(null);
|
||||
scrollY.setValue(0);
|
||||
setIsAnimating(true);
|
||||
|
||||
// 1. Secretly select winner
|
||||
const winnerIndex = Math.floor(Math.random() * eligibleParticipants.length);
|
||||
const selectedWinner = eligibleParticipants[winnerIndex];
|
||||
let highlightCount = 0;
|
||||
const maxHighlights = 30 + Math.floor(Math.random() * 10); // Add some randomness
|
||||
|
||||
// 2. Create long, shuffled list for animation
|
||||
let listForAnimation: Participant[] = [];
|
||||
for (let i = 0; i < SHUFFLE_MULTIPLIER; i++) {
|
||||
const shuffled = [...eligibleParticipants].sort(() => Math.random() - 0.5);
|
||||
listForAnimation.push(...shuffled);
|
||||
const highlightInterval = setInterval(() => {
|
||||
const randomIndex = Math.floor(Math.random() * users.length);
|
||||
setHighlightedIndex(randomIndex);
|
||||
highlightCount++;
|
||||
|
||||
if (highlightCount >= maxHighlights) {
|
||||
clearInterval(highlightInterval);
|
||||
fetchWinner();
|
||||
}
|
||||
|
||||
// 3. Place winner at the end
|
||||
listForAnimation.push(selectedWinner);
|
||||
setAnimationList(listForAnimation);
|
||||
|
||||
// 4. Calculate final scroll position
|
||||
const finalY = -(listForAnimation.length - 1) * ITEM_HEIGHT;
|
||||
|
||||
// 5. Start animation
|
||||
Animated.timing(scrollY, {
|
||||
toValue: finalY,
|
||||
duration: ANIMATION_DURATION,
|
||||
useNativeDriver: true,
|
||||
easing: Easing.bezier(0.3, 0, 0.1, 1),
|
||||
}).start(() => {
|
||||
setWinner(selectedWinner);
|
||||
confettiRef.current?.start();
|
||||
});
|
||||
}, 120);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setIsSelecting(false);
|
||||
setWinner(null);
|
||||
scrollY.setValue(0);
|
||||
confettiRef.current?.stop();
|
||||
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 winnerIndex = users.findIndex(u => u.id === winnerData.id);
|
||||
|
||||
if (winnerIndex !== -1) {
|
||||
setWinner(winnerData);
|
||||
setHighlightedIndex(winnerIndex);
|
||||
} else {
|
||||
Alert.alert('Error', 'Winner not found among participants.');
|
||||
}
|
||||
} else {
|
||||
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.');
|
||||
} finally {
|
||||
setIsAnimating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderListItem = ({ item, index }: { item: Participant, index: number }) => (
|
||||
<View style={styles.itemContainer} key={`${item.id}-${index}`}>
|
||||
<Text style={styles.itemName}>{item.name}</Text>
|
||||
</View>
|
||||
);
|
||||
const renderItem = ({ item, index }: { item: { id: string; name: string }, index: number }) => {
|
||||
const isHighlighted = index === highlightedIndex;
|
||||
const isWinner = winner?.id === item.id && !isAnimating;
|
||||
|
||||
const animatedStyle = {
|
||||
transform: [{ scale: isHighlighted ? scaleAnim : 1 }],
|
||||
};
|
||||
|
||||
return (
|
||||
<AppView>
|
||||
<Confetti ref={confettiRef} />
|
||||
|
||||
<Text style={styles.title}>Select a Winner</Text>
|
||||
|
||||
<View style={styles.selectionMachine}>
|
||||
<View style={styles.winnerPointer} />
|
||||
{isSelecting || winner ? (
|
||||
<Animated.View style={{ transform: [{ translateY: scrollY }] }}>
|
||||
{animationList.map((item, index) => renderListItem({ item, index }))}
|
||||
<Animated.View
|
||||
key={item.id}
|
||||
style={[
|
||||
styles.gridItem,
|
||||
isHighlighted && styles.highlightedItem,
|
||||
isWinner && styles.winnerItem,
|
||||
animatedStyle,
|
||||
]}
|
||||
>
|
||||
<Ionicons name="person" size={40} color={isHighlighted || isWinner ? '#000000ff' : '#4b5563'} />
|
||||
<Text style={[styles.gridText, (isHighlighted || isWinner) && styles.highlightedText]} numberOfLines={2}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Animated.View>
|
||||
) : (
|
||||
<View style={styles.placeholderContainer}>
|
||||
<Text style={styles.placeholderText}>Press Start to Select</Text>
|
||||
);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={styles.centeredContainer}>
|
||||
<ActivityIndicator size="large" color="#4f46e5" />
|
||||
<Text style={styles.loadingText}>Loading Participants...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Select a Winner</Text>
|
||||
<Text style={styles.subtitle}>Cycle #{cycleNumber}</Text>
|
||||
</View>
|
||||
<FlatList
|
||||
data={users}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item) => item.id}
|
||||
numColumns={NUM_COLUMNS}
|
||||
extraData={{ highlightedIndex, winner }}
|
||||
contentContainerStyle={styles.grid}
|
||||
/>
|
||||
<View style={styles.footer}>
|
||||
{winner && !isAnimating && (
|
||||
<View style={styles.winnerTextContainer}>
|
||||
<Text style={styles.winnerText}>🎉 Winner: {winner.name}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{winner && (
|
||||
<View style={styles.winnerBanner}>
|
||||
<Text style={styles.winnerText}>Winner is</Text>
|
||||
<Text style={styles.winnerName}>{winner.name}!</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!isSelecting && !winner && (
|
||||
<TouchableOpacity style={styles.startButton} onPress={handleStartSelection}>
|
||||
<Text style={styles.startButtonText}>Start Selection</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, isAnimating && styles.buttonDisabled]}
|
||||
onPress={handleStartSelection}
|
||||
disabled={isAnimating}
|
||||
>
|
||||
<Text style={styles.buttonText}>{isAnimating ? 'Selecting...' : 'Start Selection'}</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{winner && (
|
||||
<TouchableOpacity style={styles.resetButton} onPress={handleReset}>
|
||||
<Text style={styles.resetButtonText}>Select Again</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
</AppView>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
centeredContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#f9fafb',
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 15,
|
||||
fontSize: 18,
|
||||
color: '#374151',
|
||||
fontWeight: '500',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#1C1C1E',
|
||||
backgroundColor: '#f9fafb',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: 40,
|
||||
},
|
||||
header: {
|
||||
paddingTop: 50,
|
||||
paddingBottom: 20,
|
||||
alignItems: 'center'
|
||||
},
|
||||
title: {
|
||||
fontSize: 32,
|
||||
fontWeight: 'bold',
|
||||
color: '#FFFFFF',
|
||||
color: '#111827',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 18,
|
||||
color: '#6b7280',
|
||||
marginTop: 4
|
||||
},
|
||||
grid: {
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
gridItem: {
|
||||
margin: 8,
|
||||
width: ITEM_SIZE,
|
||||
height: ITEM_SIZE,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#fff',
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e7eb',
|
||||
padding: 8,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 3.84,
|
||||
elevation: 5,
|
||||
},
|
||||
highlightedItem: {
|
||||
borderWidth: 5,
|
||||
borderColor: '#4f46e5',
|
||||
},
|
||||
winnerItem: {
|
||||
backgroundColor: '#10b981',
|
||||
borderColor: '#059669',
|
||||
},
|
||||
gridText: {
|
||||
marginTop: 8,
|
||||
color: '#1f2937',
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
marginBottom: 30,
|
||||
fontSize: 12,
|
||||
},
|
||||
selectionMachine: {
|
||||
height: ITEM_HEIGHT,
|
||||
width: '90%',
|
||||
backgroundColor: '#2C2C2E',
|
||||
borderRadius: 20,
|
||||
overflow: 'hidden',
|
||||
justifyContent: 'center',
|
||||
highlightedText: {
|
||||
color: '#000000ff',
|
||||
},
|
||||
winnerPointer: {
|
||||
position: 'absolute',
|
||||
width: '105%',
|
||||
height: ITEM_HEIGHT,
|
||||
borderRadius: 20,
|
||||
borderWidth: 3,
|
||||
borderColor: '#FBC02D',
|
||||
alignSelf: 'center',
|
||||
zIndex: 1,
|
||||
},
|
||||
itemContainer: {
|
||||
height: ITEM_HEIGHT,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
itemName: {
|
||||
fontSize: 36,
|
||||
fontWeight: 'bold',
|
||||
color: '#FFF',
|
||||
},
|
||||
placeholderContainer: {
|
||||
height: ITEM_HEIGHT,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
placeholderText: {
|
||||
fontSize: 24,
|
||||
color: '#666',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
winnerBanner: {
|
||||
marginTop: 30,
|
||||
footer: {
|
||||
width: '100%',
|
||||
padding: 20,
|
||||
backgroundColor: '#FBC02D',
|
||||
borderRadius: 15,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#e5e7eb',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fff'
|
||||
},
|
||||
button: {
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 50,
|
||||
backgroundColor: '#4f46e5',
|
||||
borderRadius: 30,
|
||||
elevation: 3,
|
||||
shadowColor: "#4f46e5",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 4,
|
||||
},
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4.65,
|
||||
},
|
||||
buttonDisabled: {
|
||||
backgroundColor: '#9ca3af',
|
||||
shadowColor: 'transparent',
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 18,
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
winnerTextContainer: {
|
||||
marginBottom: 20,
|
||||
padding: 12,
|
||||
backgroundColor: '#d1fae5',
|
||||
borderRadius: 12,
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
},
|
||||
winnerText: {
|
||||
fontSize: 20,
|
||||
color: '#000',
|
||||
},
|
||||
winnerName: {
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
color: '#000',
|
||||
},
|
||||
startButton: {
|
||||
backgroundColor: '#007AFF',
|
||||
paddingVertical: 18,
|
||||
paddingHorizontal: 40,
|
||||
borderRadius: 30,
|
||||
marginTop: 40,
|
||||
},
|
||||
startButtonText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
resetButton: {
|
||||
backgroundColor: '#333',
|
||||
paddingVertical: 18,
|
||||
paddingHorizontal: 40,
|
||||
borderRadius: 30,
|
||||
marginTop: 40,
|
||||
},
|
||||
resetButtonText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#065f46',
|
||||
},
|
||||
});
|
||||
|
||||
export default RandomSelectUserScreen;
|
||||
|
||||
|
||||
@@ -36,10 +36,10 @@ export async function apiCall<T>(
|
||||
});
|
||||
|
||||
// --- Response Logging ---
|
||||
console.log(`--- API Response: [${response.status}] ${url} ---`);
|
||||
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("---------------------------------------");
|
||||
console.log("---------------------------------------",response);
|
||||
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
|
||||
Reference in New Issue
Block a user