testing
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ['module:@react-native/babel-preset'],
|
||||
plugins: ['react-native-reanimated/plugin'],
|
||||
};
|
||||
|
||||
Generated
+16
-1941
File diff suppressed because it is too large
Load Diff
@@ -33,14 +33,17 @@
|
||||
"react-native-elements": "^3.4.3",
|
||||
"react-native-fast-confetti": "^1.1.1",
|
||||
"react-native-image-picker": "^8.2.1",
|
||||
"react-native-inset-shadow": "^1.0.3",
|
||||
"react-native-keychain": "^10.0.0",
|
||||
"react-native-linear-gradient": "^2.8.3",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-reanimated": "^4.1.2",
|
||||
"react-native-safe-area-context": "^5.6.1",
|
||||
"react-native-screens": "^4.16.0",
|
||||
"react-native-svg": "^15.13.0",
|
||||
"react-native-vector-icons": "^10.3.0",
|
||||
"react-native-walkthrough-tooltip": "^1.6.0",
|
||||
"react-native-worklets": "^0.6.0",
|
||||
"react-redux": "^9.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function AppView({
|
||||
children,
|
||||
backgroundColor = "#f5f5f5",
|
||||
barStyle = "dark-content",
|
||||
paddingHorizontal = 16, // default
|
||||
paddingHorizontal = 12, // default
|
||||
paddingTop = 8, // default
|
||||
style,
|
||||
}: AppViewProps) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { TouchableOpacity, Text, StyleSheet, ActivityIndicator, ViewStyle } from "react-native";
|
||||
import colors from "./colors";
|
||||
|
||||
type CustomButtonProps = {
|
||||
title: string;
|
||||
@@ -13,8 +14,8 @@ type CustomButtonProps = {
|
||||
export default function CustomButton({
|
||||
title,
|
||||
onPress,
|
||||
backgroundColor = "#000000ff",
|
||||
textColor = "#fff",
|
||||
backgroundColor = colors.pastelGreen,
|
||||
textColor = colors.textDark,
|
||||
loading = false,
|
||||
style,
|
||||
}: CustomButtonProps) {
|
||||
@@ -36,6 +37,7 @@ export default function CustomButton({
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
elevation:5,
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 12,
|
||||
|
||||
@@ -4,7 +4,6 @@ import React, { FC } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import colors from './colors';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
||||
// Update the type to match the full data structure
|
||||
interface JoinedGroup {
|
||||
id: string;
|
||||
@@ -80,8 +79,10 @@ export const JoinedGroupCard: FC<JoinedGroupCardProps> = ({ item, onViewPress })
|
||||
{/* Financial Info */}
|
||||
<View style={styles.infoRow}>
|
||||
<View style={styles.infoItem}>
|
||||
<Text style={styles.infoLabel}>Contribution</Text>
|
||||
<Text style={styles.infoValue}>₹{item.contributionAmount.toLocaleString('en-IN')}</Text>
|
||||
|
||||
<Text style={styles.infoLabel}>Contribution</Text>
|
||||
<Text style={styles.infoValue}>₹{item.contributionAmount.toLocaleString('en-IN')}</Text>
|
||||
|
||||
</View>
|
||||
<View style={styles.infoItem}>
|
||||
<Text style={styles.infoLabel}>Cycle</Text>
|
||||
@@ -89,25 +90,12 @@ export const JoinedGroupCard: FC<JoinedGroupCardProps> = ({ item, onViewPress })
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Participants Progress */}
|
||||
<View style={styles.progressContainer}>
|
||||
<View style={styles.progressLabels}>
|
||||
<Text style={styles.participantText}>Participants</Text>
|
||||
<Text style={styles.participantCount}>{item.currentParticipants} / {item.maxParticipants}</Text>
|
||||
</View>
|
||||
<View style={styles.progressBarBackground}>
|
||||
<View style={[styles.progressBarFill, { width: `${progress}%` }]} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Footer with Dates and View Button */}
|
||||
<View style={styles.footer}>
|
||||
<Text style={styles.dateText}>
|
||||
{formatDate(item.startDate)} - {formatDate(item.endDate)}
|
||||
</Text>
|
||||
<TouchableOpacity style={styles.viewButton} onPress={onViewPress}>
|
||||
<Text style={styles.viewButtonText}>View</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
@@ -115,7 +103,7 @@ export const JoinedGroupCard: FC<JoinedGroupCardProps> = ({ item, onViewPress })
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
backgroundColor:colors.pastelOrange,
|
||||
borderRadius: 16,
|
||||
padding: 20,
|
||||
marginBottom: 16,
|
||||
@@ -123,7 +111,8 @@ const styles = StyleSheet.create({
|
||||
shadowOpacity: 0.08,
|
||||
shadowRadius: 10,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
elevation: 3,
|
||||
elevation: 5,
|
||||
marginHorizontal:12
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
@@ -160,8 +149,14 @@ const styles = StyleSheet.create({
|
||||
borderBottomWidth: 1,
|
||||
borderColor: colors.pastelOrange,
|
||||
marginBottom: 16,
|
||||
backgroundColor:colors.white,
|
||||
borderRadius:15,
|
||||
},
|
||||
infoItem: {
|
||||
padding:5,
|
||||
paddingHorizontal:10,
|
||||
|
||||
backgroundColor:colors.white,
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
infoLabel: {
|
||||
@@ -207,10 +202,7 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginTop: 8,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#F3F4F6',
|
||||
paddingTop: 16,
|
||||
|
||||
},
|
||||
dateText: {
|
||||
fontSize: 14,
|
||||
|
||||
@@ -6,20 +6,20 @@ import {
|
||||
TouchableWithoutFeedback,
|
||||
Animated,
|
||||
ViewStyle,
|
||||
StyleProp,
|
||||
} from "react-native";
|
||||
import LinearGradient from "react-native-linear-gradient";
|
||||
import Ionicons from "react-native-vector-icons/Ionicons";
|
||||
import { IoniconsIconName } from "react-native-vector-icons/Ionicons";
|
||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||
import colors from "../colors"; // Assuming your pastel colors are defined here
|
||||
|
||||
type CardProps = {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
iconName?: IoniconsIconName;
|
||||
iconName?: string;
|
||||
onPress?: () => void;
|
||||
backOne?: string;
|
||||
backTwo?: string;
|
||||
style?: ViewStyle;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
const AnimatedCard: React.FC<CardProps> = ({
|
||||
@@ -27,18 +27,26 @@ const AnimatedCard: React.FC<CardProps> = ({
|
||||
subtitle,
|
||||
iconName = "ellipse",
|
||||
onPress,
|
||||
backOne = colors.pastelPink, // Defaulting to pastelPink
|
||||
backOne = colors.pastelPink, // Defaulting to pastelPink from your preferences
|
||||
backTwo = colors.primary,
|
||||
style,
|
||||
}) => {
|
||||
const scale = useRef(new Animated.Value(1)).current;
|
||||
|
||||
const handlePressIn = () => {
|
||||
Animated.spring(scale, { toValue: 0.96, useNativeDriver: true, friction: 4 }).start();
|
||||
Animated.spring(scale, {
|
||||
toValue: 0.96,
|
||||
useNativeDriver: true,
|
||||
friction: 4,
|
||||
}).start();
|
||||
};
|
||||
|
||||
const handlePressOut = () => {
|
||||
Animated.spring(scale, { toValue: 1, useNativeDriver: true, friction: 4 }).start(() => {
|
||||
Animated.spring(scale, {
|
||||
toValue: 1,
|
||||
useNativeDriver: true,
|
||||
friction: 4,
|
||||
}).start(() => {
|
||||
if (onPress) {
|
||||
onPress();
|
||||
}
|
||||
@@ -60,7 +68,14 @@ const AnimatedCard: React.FC<CardProps> = ({
|
||||
|
||||
{/* Content */}
|
||||
<View style={styles.content}>
|
||||
{iconName && <Ionicons name={iconName} size={38} color="#fff" style={styles.icon} />}
|
||||
{iconName && (
|
||||
<Ionicons
|
||||
name={iconName}
|
||||
size={38}
|
||||
color="#fff"
|
||||
style={styles.icon}
|
||||
/>
|
||||
)}
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
{subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
|
||||
@@ -74,6 +89,10 @@ const AnimatedCard: React.FC<CardProps> = ({
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
minWidth: 170,
|
||||
flex: 1,
|
||||
height: 170,
|
||||
marginHorizontal: 8,
|
||||
marginVertical: 10,
|
||||
borderRadius: 24,
|
||||
overflow: "hidden",
|
||||
@@ -85,7 +104,6 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: colors.white,
|
||||
},
|
||||
gradient: {
|
||||
minHeight: 150, // Increased height for better spacing
|
||||
position: "relative",
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
@@ -121,15 +139,15 @@ const styles = StyleSheet.create({
|
||||
marginBottom: 12,
|
||||
},
|
||||
textContainer: {
|
||||
alignItems: 'center',
|
||||
alignItems: "center",
|
||||
},
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
textTransform: 'uppercase',
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
textAlign: 'center',
|
||||
textAlign: "center",
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 13,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
withRepeat,
|
||||
withSequence,
|
||||
withDelay,
|
||||
Easing,
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
const AnimatedText = ({ text, style, delay = 0 }) => {
|
||||
const opacity = useSharedValue(0);
|
||||
const translateY = useSharedValue(20);
|
||||
|
||||
useEffect(() => {
|
||||
opacity.value = withDelay(delay, withTiming(1, { duration: 800 }));
|
||||
translateY.value = withDelay(delay, withTiming(0, { duration: 800 }));
|
||||
}, []);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: opacity.value,
|
||||
transform: [{ translateY: translateY.value }],
|
||||
}));
|
||||
|
||||
return (
|
||||
<Animated.Text style={[styles.text, style, animatedStyle]}>
|
||||
{text}
|
||||
</Animated.Text>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnimatedText;
|
||||
|
||||
// Usage
|
||||
const MyComponent = () => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<AnimatedText text="Hello World" style={styles.title} />
|
||||
<AnimatedText text="Welcome to Reanimated" style={styles.subtitle} delay={200} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
fontSize: 32,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 10,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 18,
|
||||
color: '#666',
|
||||
},
|
||||
text: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
@@ -34,6 +34,9 @@ const CustomInput: React.FC<CustomInputProps> = ({
|
||||
<TextInput
|
||||
style={[styles.input, icon && { paddingLeft: 36 }, style]}
|
||||
placeholderTextColor={colors.textLight}
|
||||
|
||||
value={value ? String(value) : undefined}
|
||||
{...(error ? {borderColor:'red', borderWidth:1} : {})}
|
||||
{...rest}
|
||||
/>
|
||||
</View>
|
||||
@@ -57,21 +60,14 @@ const CustomButton: React.FC<CustomButtonProps> = ({
|
||||
type = "primary",
|
||||
icon,
|
||||
style = {},
|
||||
buttonBackground,
|
||||
buttonBackground,
|
||||
}) => {
|
||||
const backgroundColor =
|
||||
buttonBackground ??
|
||||
(type === "secondary"
|
||||
? colors.pastelBlue
|
||||
: type === "danger"
|
||||
? colors.pastelOrange
|
||||
: colors.primary);
|
||||
|
||||
const textColor = type === "secondary" ? colors.textDark : '#000';
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.button, { backgroundColor }, style]}
|
||||
style={[styles.button, { backgroundColor: buttonBackground }, style]}
|
||||
onPress={onPress}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
|
||||
@@ -17,7 +17,7 @@ const Header: React.FC<HeaderProps> = ({
|
||||
showBack = true,
|
||||
onBackPress,
|
||||
rightAction,
|
||||
titleColor = 'pink',
|
||||
titleColor = "",
|
||||
arrowColor='pink'
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
@@ -32,13 +32,6 @@ const Header: React.FC<HeaderProps> = ({
|
||||
|
||||
return (
|
||||
<View style={styles.header}>
|
||||
{showBack ? (
|
||||
<TouchableOpacity onPress={handleBack}>
|
||||
<Ionicons name="arrow-back" size={24} color={arrowColor} />
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<View style={{ width: 24 }} /> // placeholder to keep spacing
|
||||
)}
|
||||
<Text style={[styles.headerTitle, {color:titleColor}]}>{title}</Text>
|
||||
<View>{rightAction}</View>
|
||||
</View>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Animated,
|
||||
Easing,
|
||||
} from "react-native";
|
||||
import {Ionicons} from "@react-native-vector-icons/ionicons";
|
||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||
import colors from "../components/colors";
|
||||
import CustomModal from "../components/common/CustomModal";
|
||||
|
||||
@@ -77,9 +77,13 @@ const ContributionDetails: React.FC<ContributionProps> = ({
|
||||
>
|
||||
<Text style={styles.label}>Base Amount</Text>
|
||||
<Text style={styles.value}>₹{data?.baseAmount}</Text>
|
||||
{
|
||||
data?.alreadyPaid ? <Text style={styles?.valueHighlight}>Already Paid Amount</Text> :
|
||||
<View>
|
||||
<Text style={styles.label}>Total Payable</Text>
|
||||
<Text style={styles.valueHighlight}>₹{data?.totalPayable}</Text>
|
||||
</View>}
|
||||
|
||||
<Text style={styles.label}>Total Payable</Text>
|
||||
<Text style={styles.valueHighlight}>₹{data?.totalPayable}</Text>
|
||||
|
||||
<Text style={styles.label}>Due Date</Text>
|
||||
<Text style={styles.value}>{data?.dueDate}</Text>
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function GroupCreationForm() {
|
||||
if (!form.minParticipants || Number(form.minParticipants) <= 1) newErrors.minParticipants = "Min participants must be at least 2.";
|
||||
if (!form.maxParticipants || Number(form.maxParticipants) < Number(form.minParticipants)) newErrors.maxParticipants = "Max must be greater than or equal to min.";
|
||||
if (!form.contributionAmount || Number(form.contributionAmount) <= 0) newErrors.contributionAmount = "Amount must be greater than 0.";
|
||||
if (form.cycleType === "CUSTOM" && (!form.customCycleDays || Number(form.customCycleDays) <= 0)) newErrors.customCycleDays = "Custom days must be greater than 0.";
|
||||
// if (form.cycleType === "CUSTOM" && (!form.customCycleDays || Number(form.customCycleDays) <= 0)) newErrors.customCycleDays = "Custom days must be greater than 0.";
|
||||
if (form.lateFeeMode && (!form.lateFeeValue || Number(form.lateFeeValue) <= 0)) newErrors.lateFeeValue = "Late fee value is required.";
|
||||
|
||||
setErrors(newErrors);
|
||||
@@ -76,6 +76,7 @@ export default function GroupCreationForm() {
|
||||
navigation.navigate("GroupReview", { form: payload });
|
||||
} else {
|
||||
Alert.alert("Incomplete Form", "Please fill out all required fields correctly.");
|
||||
setErrors('')
|
||||
}
|
||||
};
|
||||
|
||||
@@ -88,7 +89,7 @@ export default function GroupCreationForm() {
|
||||
>
|
||||
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
|
||||
<View style={styles.titleContainer}><Text style={styles.title}>Create New Group</Text></View>
|
||||
|
||||
|
||||
<CustomInput label="Group Name" placeholder="e.g., 'Family Savings'" value={form.groupName} onChangeText={(val) => handleChange("groupName", val)} icon="people" error={errors.groupName} />
|
||||
<CustomInput label="Description" placeholder="e.g., 'Monthly fund for family needs'" value={form.description} onChangeText={(val) => handleChange("description", val)} multiline icon="document-text" error={errors.description} />
|
||||
|
||||
|
||||
+57
-49
@@ -27,8 +27,6 @@ import { ApiResponse } from "./home/UserProfileStatus";
|
||||
import * as Keychain from "react-native-keychain";
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
|
||||
|
||||
// --- Types ---
|
||||
interface Group {
|
||||
id: string;
|
||||
@@ -39,46 +37,66 @@ interface Group {
|
||||
contributionAmount: number;
|
||||
cycleType: string;
|
||||
createdByName: string;
|
||||
imageBlob: string
|
||||
}
|
||||
|
||||
// --- UI Components ---
|
||||
const GroupCard = ({ item, onJoin, onView, isGrid }: { item: Group; onJoin: (groupId: string) => void; onView: (group: Group) => void; isGrid?: boolean; }) => {
|
||||
const GroupCard = ({ item, onJoin, onView, isGrid }: { item: Group; onJoin:()=>void; onView: (group: Group) => void; isGrid?: boolean; }) => {
|
||||
const isFull = item.currentParticipants >= item.maxParticipants;
|
||||
const cardStyle = isGrid ? styles.gridCard : styles.listCard;
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const navigation = useNavigation()
|
||||
|
||||
return (
|
||||
<View style={cardStyle}>
|
||||
<Text style={styles.groupName} numberOfLines={1}>{item.groupName}</Text>
|
||||
<Text style={styles.groupDescription} numberOfLines={isGrid ? 2 : 1}>{item.description}</Text>
|
||||
{/* <TouchableOpacity onPress={()=> navigation.navigate("GroupDetails" as never)}> */}
|
||||
<View style={{ flex: 1, flexDirection: 'row', justifyContent: "space-between" }}>
|
||||
<View>
|
||||
<Text style={[styles.groupName,{width:isGrid?"50%":"100%"}]} numberOfLines={isGrid ? 2 : 1}>{item.groupName}</Text>
|
||||
<Text style={styles.groupDescription} numberOfLines={isGrid ? 2 : 1}>{item.description}</Text>
|
||||
</View>
|
||||
<Image
|
||||
source={{ uri: item?.imageBlob !== null ? `data:image/jpeg;base64,${item?.imageBlob}` : `https://i.pravatar.cc/150?u=${item.id}` }}
|
||||
style={{ width: 50, borderWidth: 1, height: 50, borderColor: colors.pastelGreen, borderRadius: 25, }}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.groupMeta}>
|
||||
<Text style={styles.groupMetaText}>{item.currentParticipants}/{item.maxParticipants} Members</Text>
|
||||
<Text style={styles.groupMetaText}>{item.cycleType}</Text>
|
||||
</View>
|
||||
{/* </TouchableOpacity> */}
|
||||
<View style={styles.buttonContainer}>
|
||||
<TouchableOpacity style={styles.viewButton} onPress={() => onView(item)}>
|
||||
{/* <TouchableOpacity style={styles.viewButton} onPress={() => onView(item)}>
|
||||
<Text style={styles.viewButtonText}>View</Text>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity> */}
|
||||
<TouchableOpacity
|
||||
style={[styles.joinButton, isFull && styles.joinButtonDisabled]}
|
||||
onPress={() => onJoin(item.id)}
|
||||
disabled={isFull}
|
||||
onPress={async () => {
|
||||
setLoading(true);
|
||||
await onJoin(item.id);
|
||||
setLoading(false);
|
||||
}}
|
||||
disabled={isFull || loading} // Also disable button while loading
|
||||
>
|
||||
<Text style={styles.joinButtonText}>{isFull ? "Full" : "Join"}</Text>
|
||||
{loading ? <ActivityIndicator color={colors.black} /> : <Text style={styles.joinButtonText}>{isFull ? "Full" : "Join"}</Text>}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const FeaturedGroupCard = ({ item }: { item: Group }) => (
|
||||
<View style={styles.featuredCard}>
|
||||
<Image source={{ uri: `https://i.pravatar.cc/150?u=${item.id}` }} style={styles.featuredImage} />
|
||||
<View style={styles.featuredContent}>
|
||||
<Text style={styles.featuredName} numberOfLines={1}>{item.groupName}</Text>
|
||||
<Text style={styles.featuredCreator}>by {item.createdByName}</Text>
|
||||
const FeaturedGroupCard = ({ item }: { item: Group }) => {
|
||||
return (
|
||||
<View style={styles.featuredCard}>
|
||||
<Image source={{ uri: item?.imageBlob !== null ? `data:image/jpeg;base64${item?.imageBlob}` : `https://i.pravatar.cc/150?u=${item.id}` }} style={styles.featuredImage} />
|
||||
<View style={styles.featuredContent}>
|
||||
<Text style={styles.featuredName} numberOfLines={1}>{item.groupName}</Text>
|
||||
<Text style={styles.featuredCreator}>by {item.createdByName}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
)
|
||||
};
|
||||
|
||||
export default function GroupScreen() {
|
||||
const [activeTab, setActiveTab] = useState("Public");
|
||||
@@ -114,6 +132,7 @@ export default function GroupScreen() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const fetchJoinedGroups = async () => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
@@ -152,21 +171,21 @@ export default function GroupScreen() {
|
||||
try {
|
||||
const url = `${JOIN_GROUP_URL}groups/${groupId}/join/10`;
|
||||
console.log("Joining group URL:", url);
|
||||
const res = await apiCall(url, "POST");
|
||||
const res = await apiCall(url, "POST") as any;
|
||||
|
||||
console.log("JOIN_GROUP_URL response:", res);
|
||||
|
||||
// if (res.statusCode === 200) {
|
||||
// Alert.alert("Success", "You requested to join the group.");
|
||||
// } else {
|
||||
// Alert.alert("Error", res.message || "Failed to join group.");
|
||||
// }
|
||||
if (res.statusCode === 200) {
|
||||
Alert.alert("Success", "You requested to join the group.");
|
||||
} else {
|
||||
Alert.alert("Error", res.message || "Failed to join group.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Join Group Error:", error);
|
||||
Alert.alert("Error", "An unexpected error occurred.");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const userStatus = async () => {
|
||||
try {
|
||||
const credentials = await Keychain.getGenericPassword();
|
||||
@@ -264,13 +283,13 @@ export default function GroupScreen() {
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||
}
|
||||
// ListFooterComponent={<ActivityIndicator size={'large'} color={colors.Group} />}
|
||||
// ListFooterComponent={<ActivityIndicator size={'large'} color={colors.Group} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AppView paddingHorizontal={0} style={styles.container}>
|
||||
<AppView paddingHorizontal={0}>
|
||||
{/* Fixed Header */}
|
||||
<View style={styles.headerContainer}>
|
||||
<Text style={styles.title}>Explore Groups</Text>
|
||||
@@ -282,21 +301,11 @@ export default function GroupScreen() {
|
||||
|
||||
{/* Scrollable Content */}
|
||||
<ScrollView style={styles.container}>
|
||||
<Text style={styles.sectionTitle}>Featured</Text>
|
||||
<FlatList
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
data={featuredGroups}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => <FeaturedGroupCard item={item} />}
|
||||
style={styles.featuredList}
|
||||
// ListEmptyComponent={!loading && featuredGroups.length === 0 ? <Text style={styles.emptyFeaturedText}>No featured groups.</Text> : null}
|
||||
/>
|
||||
|
||||
<View style={styles.tabHeader}>
|
||||
<View style={styles.segmentedControl}>
|
||||
|
||||
{role === "ORGANIZER" ? (["My Groups", "Joined", "Public"].map((tab) => (
|
||||
{role === "ORGANIZER" ? (["Public"].map((tab) => (
|
||||
<TouchableOpacity key={tab} style={[styles.segmentButton, activeTab === tab && styles.segmentButtonActive]} onPress={() => setActiveTab(tab)}>
|
||||
<Text style={[styles.segmentText, activeTab === tab && styles.segmentTextActive]}>{tab}</Text>
|
||||
</TouchableOpacity>
|
||||
@@ -312,7 +321,7 @@ export default function GroupScreen() {
|
||||
</View>
|
||||
|
||||
{/* The list now renders inside a simple View, as FlatList handles its own scroll */}
|
||||
<View style={styles.contentArea}>{renderContent()}</View>
|
||||
<View style={{}}>{renderContent()}</View>
|
||||
</ScrollView>
|
||||
{role === "ORGANIZER" && (
|
||||
<FAB style={styles.fab} icon={() => <MaterialIcons name="group-add" size={24} color="#333" />} onPress={() => navigation.navigate('GroupCreation')} />
|
||||
@@ -322,10 +331,10 @@ export default function GroupScreen() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#f5f6fa' },
|
||||
|
||||
headerContainer: { backgroundColor: '#f5f6fa', paddingHorizontal: 16 },
|
||||
title: { fontSize: 22, fontWeight: "600", color: colors.Group },
|
||||
searchContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderRadius: 12, paddingHorizontal: 10, elevation: 2, marginVertical: 20 },
|
||||
searchContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderRadius: 20, paddingHorizontal: 10, elevation: 5, marginVertical: 20 , borderWidth:2,borderColor:colors.pastelGreen },
|
||||
searchIcon: { marginRight: 8 },
|
||||
searchInput: { flex: 1, height: 45, fontSize: 16, color: '#333' },
|
||||
sectionTitle: { fontSize: 20, fontWeight: 'bold', color: colors.Group, marginBottom: 12, paddingHorizontal: 16 },
|
||||
@@ -336,18 +345,17 @@ const styles = StyleSheet.create({
|
||||
featuredName: { fontSize: 14, fontWeight: 'bold', color: '#333' },
|
||||
featuredCreator: { fontSize: 12, color: '#777', marginTop: 2 },
|
||||
tabHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, marginBottom: 16 },
|
||||
segmentedControl: { flex: 1, flexDirection: 'row', backgroundColor: '#e9ecef', borderRadius: 10, padding: 4, marginRight: 10 },
|
||||
segmentedControl: { flex: 1, flexDirection: 'row', borderRadius: 10, padding: 4, marginRight: 10 },
|
||||
segmentButton: { flex: 1, paddingVertical: 10, borderRadius: 8, alignItems: 'center' },
|
||||
segmentButtonActive: { backgroundColor: colors.pastelGreen, elevation: 2, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 5 },
|
||||
segmentButtonActive: { backgroundColor: colors.pastelGreen, elevation: 5, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 5 , borderRadius:15 },
|
||||
segmentText: { fontSize: 14, fontWeight: '600', color: '#555' },
|
||||
segmentTextActive: { color: '#333' },
|
||||
layoutToggleButton: { padding: 8 },
|
||||
contentArea: { paddingHorizontal: 16 },
|
||||
fab: { position: "absolute", right: 20, bottom: 90, width: 60, height: 60, justifyContent: "center", alignItems: "center", backgroundColor: colors.pastelGreen, borderRadius: 30, elevation: 4 },
|
||||
listCard: { backgroundColor: '#fff', padding: 16, borderRadius: 12, marginBottom: 12, elevation: 1 },
|
||||
gridCard: { backgroundColor: '#fff', padding: 12, borderRadius: 12, marginBottom: 12, elevation: 1, width: (width - 48) / 2 },
|
||||
groupName: { fontSize: 16, fontWeight: 'bold', color: '#333' },
|
||||
groupDescription: { fontSize: 14, color: '#666', marginVertical: 4 },
|
||||
listCard: { backgroundColor: colors.pastelGreen, padding: 16, borderRadius: 20, marginBottom: 12,marginHorizontal:15, elevation: 5 },
|
||||
gridCard: { backgroundColor: colors.pastelGreen, padding: 12, borderRadius: 20, marginBottom: 12,marginHorizontal:15, elevation: 5, width: (width - 48) / 2 },
|
||||
groupName: { fontSize: 16, fontWeight: 'bold', color: '#333', width: '50%' },
|
||||
groupDescription: { fontSize: 14, color: '#666', marginVertical: 4, width: '40%' },
|
||||
groupMeta: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 8, borderTopWidth: 1, borderTopColor: '#f0f0f0', paddingTop: 8, marginBottom: 10 },
|
||||
groupMetaText: { fontSize: 12, color: '#888', fontWeight: '500', textTransform: 'capitalize' },
|
||||
emptyText: { textAlign: 'center', marginTop: 40, fontSize: 16, color: '#888' },
|
||||
@@ -355,7 +363,7 @@ const styles = StyleSheet.create({
|
||||
buttonContainer: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 8 },
|
||||
viewButton: { flex: 1, backgroundColor: '#e9ecef', paddingVertical: 10, borderRadius: 8, alignItems: 'center', marginRight: 5 },
|
||||
viewButtonText: { color: '#333', fontWeight: 'bold', fontSize: 14 },
|
||||
joinButton: { flex: 1, backgroundColor: colors.pastelGreen, paddingVertical: 10, borderRadius: 8, alignItems: 'center', marginLeft: 5 },
|
||||
joinButton: { flex: 1, backgroundColor: colors.white, paddingVertical: 10, borderRadius: 8, alignItems: 'center', marginLeft: 5 },
|
||||
joinButtonDisabled: { backgroundColor: '#bdc3c7' },
|
||||
joinButtonText: { color: '#333', fontWeight: 'bold', fontSize: 14 },
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ const InviteScreen: FC<InviteScreenProps> = ({ 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 };
|
||||
if (response.status === 'success' && response.data) {
|
||||
setGroupDetails(response.data);
|
||||
} else {
|
||||
@@ -76,7 +76,7 @@ const InviteScreen: FC<InviteScreenProps> = ({ route }) => {
|
||||
try {
|
||||
// Replace with your actual API call to join the group
|
||||
// For now, it just shows an alert.
|
||||
await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/groups/join/invite/${code}`, 'POST');
|
||||
await apiCall(`http://192.168.1.9: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.');
|
||||
|
||||
@@ -20,7 +20,7 @@ import { setUserData } from "../../redux/userSlice";
|
||||
import { TabNavigationProp } from "../../navigation/BottomTabs";
|
||||
import UserProfileStatus from "./UserProfileStatus";
|
||||
import AnimatedCard from "../../components/common/AnimatedCard";
|
||||
import { IoniconsIconName } from "@react-native-vector-icons/ionicons";
|
||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||
import { apiCall } from "../../utils/ApiHelper";
|
||||
|
||||
if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
@@ -52,6 +52,7 @@ const organizerActions: ActionItem[] = [
|
||||
{ id: "1", title: "My Groups", subtitle: "Manage your groups", action: "NAVIGATE_MANAGEMENT", tab: "My Groups", iconName: "people-circle-outline", backOne: '#decbfcff', backTwo: '#B799FF' },
|
||||
{ id: "2", title: "Joined Groups", subtitle: "View member groups", action: "NAVIGATE_MANAGEMENT", tab: "Joined Groups", iconName: "people-outline", backOne: '#D7E9F7', backTwo: '#82AAE3' },
|
||||
{ id: "3", title: "Requests", subtitle: "Check new requests", action: "NAVIGATE_MANAGEMENT", tab: "Requests", iconName: "notifications-outline", backOne: '#DFF6DD', backTwo: '#88D18A' },
|
||||
{ id: "4", title: "Create Group", subtitle: "Create a new group", action: "NAVIGATE_CREATE_GROUP", tab: "Requests", iconName: "people-outline", backOne: '#DFF6DD', backTwo: '#88D18A' },
|
||||
];
|
||||
|
||||
const userActions: ActionItem[] = [
|
||||
@@ -90,6 +91,8 @@ const Home: React.FC = () => {
|
||||
navigation.navigate('Management', { actionTab: item.tab });
|
||||
} else if (item.action === "TRANSACTION") {
|
||||
navigation.navigate("TransactionList" as never);
|
||||
} else if (item.action === "NAVIGATE_CREATE_GROUP") {
|
||||
navigation.navigate("GroupCreation" as never);
|
||||
} else {
|
||||
Alert.alert("Unknown action", item.title);
|
||||
}
|
||||
@@ -158,7 +161,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
greeting: {
|
||||
fontSize: 22,
|
||||
color: colors.textSecondary,
|
||||
color: colors.textDark,
|
||||
},
|
||||
userName: {
|
||||
fontSize: 28,
|
||||
|
||||
@@ -58,7 +58,7 @@ const RequestList: React.FC = () => {
|
||||
setError(null);
|
||||
|
||||
const response = await fetch(
|
||||
"https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/group/pending/request",
|
||||
"http://192.168.1.9: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://exotic-humpback-mistakenly.ngrok-free.app/api/fund/requests/${requestId}/approve`,
|
||||
`http://192.168.1.9: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://exotic-humpback-mistakenly.ngrok-free.app/api/fund/requests/${requestedId}/reject?reason=/${reason}`,
|
||||
`http://192.168.1.9:9060/api/fund/requests/${requestedId}/reject?reason=/${reason}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
|
||||
@@ -37,8 +37,9 @@ const GroupDetails = () => {
|
||||
const [memberData, setMemberData] = useState<Member[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [openModal, setOpenModal] = useState(false)
|
||||
const [payable, setPayable] = useState()
|
||||
const [payable, setPayable] = useState() as any
|
||||
|
||||
console.log("route param ggggggggggg : ", route?.params)
|
||||
// Group object
|
||||
const group = {
|
||||
id: route.params.item?.id,
|
||||
@@ -108,7 +109,7 @@ const GroupDetails = () => {
|
||||
|
||||
const confirmPayment = async () => {
|
||||
try {
|
||||
const res = await apiCall(`${BASE_URL}payments/contribute/${group?.id}?amount=${group?.collected}`, 'POST') as any
|
||||
const res = await apiCall(`${BASE_URL}payments/contribute/${group?.id}?amount=${payable?.totalPayable}`, 'POST') as any
|
||||
console.log("payment response : ", res)
|
||||
if (res.status) {
|
||||
ToastAndroid.show('Payment done!', ToastAndroid.SHORT)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -43,7 +43,7 @@ const BecomeOrg = () => {
|
||||
const token = credentials.password;
|
||||
|
||||
const response = await fetch(
|
||||
"https://exotic-humpback-mistakenly.ngrok-free.app/api/organizer/registration/apply",
|
||||
"http://192.168.1.9:9060/api/organizer/registration/apply",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
||||
@@ -369,7 +369,7 @@ const Profile = () => {
|
||||
|
||||
|
||||
return (
|
||||
<AppView>
|
||||
<AppView paddingHorizontal={5} >
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.headerTitle}>User Profile</Text>
|
||||
@@ -425,7 +425,6 @@ const Profile = () => {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
<View style={styles.profileImageSection}>
|
||||
<View style={styles.avatarContainer}>
|
||||
<Image
|
||||
@@ -472,12 +471,13 @@ const Profile = () => {
|
||||
onPress={() => setEditing(true)}
|
||||
icon="create"
|
||||
style={styles.editButton}
|
||||
buttonBackground={colors.pastelBlue}
|
||||
buttonBackground={colors.white}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style ={{backgroundColor:colors.pastelBlue,borderRadius:20,elevation:5,paddingVertical:10}}>
|
||||
<View style={styles.detailRow}>
|
||||
<Ionicons name="person" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
|
||||
<Ionicons name="person" size={20} color={colors.black} style={styles.detailIcon} />
|
||||
<View>
|
||||
<Text style={styles.detailLabel}>Name</Text>
|
||||
<Text style={styles.detailText}>{user?.firstName} {user?.lastName}</Text>
|
||||
@@ -486,7 +486,7 @@ const Profile = () => {
|
||||
|
||||
|
||||
<View style={styles.detailRow}>
|
||||
<Ionicons name="mail" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
|
||||
<Ionicons name="mail" size={20} color={colors.black} style={styles.detailIcon} />
|
||||
<View>
|
||||
<Text style={styles.detailLabel}>Email</Text>
|
||||
<Text style={styles.detailText}>{user?.email || "Not provided"}</Text>
|
||||
@@ -495,7 +495,7 @@ const Profile = () => {
|
||||
|
||||
|
||||
<View style={styles.detailRow}>
|
||||
<Ionicons name="call" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
|
||||
<Ionicons name="call" size={20} color={colors.black} style={styles.detailIcon} />
|
||||
<View>
|
||||
<Text style={styles.detailLabel}>Mobile</Text>
|
||||
<Text style={styles.detailText}>{user?.mobile || "Not provided"}</Text>
|
||||
@@ -504,16 +504,16 @@ const Profile = () => {
|
||||
|
||||
|
||||
<View style={styles.detailRow}>
|
||||
<Ionicons name="at-circle" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
|
||||
<Ionicons name="at-circle" size={20} color={colors.black} style={styles.detailIcon} />
|
||||
<View>
|
||||
<Text style={styles.detailLabel}>Username</Text>
|
||||
<Text style={styles.detailLabel}>Usernames</Text>
|
||||
<Text style={styles.detailText}>{user?.username}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
<View style={styles.detailRow}>
|
||||
<Ionicons name="location" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
|
||||
<Ionicons name="location" size={20} color={colors.black} style={styles.detailIcon} />
|
||||
<View>
|
||||
<Text style={styles.detailLabel}>Address</Text>
|
||||
<Text style={styles.detailText}>{user?.address || "Not provided"}</Text>
|
||||
@@ -522,7 +522,7 @@ const Profile = () => {
|
||||
|
||||
|
||||
<View style={styles.detailRow}>
|
||||
<Ionicons name="male-female" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
|
||||
<Ionicons name="male-female" size={20} color={colors.black} style={styles.detailIcon} />
|
||||
<View>
|
||||
<Text style={styles.detailLabel}>Gender</Text>
|
||||
<Text style={styles.detailText}>{user?.gender || "Not specified"}</Text>
|
||||
@@ -531,18 +531,19 @@ const Profile = () => {
|
||||
|
||||
|
||||
<View style={styles.detailRow}>
|
||||
<Ionicons name="key" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
|
||||
<Ionicons name="key" size={20} color={colors.black} style={styles.detailIcon} />
|
||||
<View>
|
||||
<Text style={styles.detailLabel}>Role</Text>
|
||||
<Text style={styles.detailText}>{user?.role || "User"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
</View>
|
||||
{/* BANK DETAILS SECTION */}
|
||||
<View style={{ marginTop: 20 }}>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Text style={styles.sectionTitle}>Bank Information</Text>
|
||||
{/* Navigate to a screen you will create for editing bank details */}
|
||||
|
||||
<CustomButton
|
||||
title="Edit Bank Details"
|
||||
onPress={() =>{ navigation.navigate("EditBankDetails", {
|
||||
@@ -553,120 +554,115 @@ const Profile = () => {
|
||||
|
||||
icon="create-outline"
|
||||
style={styles.editButton}
|
||||
buttonBackground={colors.pastelBlue}
|
||||
buttonBackground={colors.white}
|
||||
/>
|
||||
</View>
|
||||
<View style ={{backgroundColor:colors.pastelBlue,borderRadius:20,elevation:5,paddingVertical:10}}>
|
||||
<View style={styles.detailRow}>
|
||||
<Ionicons name="business" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
|
||||
<Ionicons name="business" size={20} color={colors.black}style={styles.detailIcon} />
|
||||
<View>
|
||||
<Text style={styles.detailLabel}>Bank Name</Text>
|
||||
<Text style={styles.detailText}>{user?.bankName || "Not provided"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.detailRow}>
|
||||
<Ionicons name="barcode" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
|
||||
<Ionicons name="barcode" size={20} color={colors.black} style={styles.detailIcon} />
|
||||
<View>
|
||||
<Text style={styles.detailLabel}>Branch Code</Text>
|
||||
<Text style={styles.detailText}>{user?.branchCode || "Not provided"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.detailRow}>
|
||||
<Ionicons name="card" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
|
||||
<Ionicons name="card" size={20} color={colors.black} style={styles.detailIcon} />
|
||||
<View>
|
||||
<Text style={styles.detailLabel}>Account Number</Text>
|
||||
<Text style={styles.detailText}>{user?.bankAccountNumber || "Not provided"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* Edit Form */}
|
||||
<View style={styles.editSection}>
|
||||
<Text style={styles.sectionTitle}>Edit Profile</Text>
|
||||
<>
|
||||
{/* Edit Form */}
|
||||
<View style={styles.editSection}>
|
||||
<Text style={styles.sectionTitle}>Edit Profile</Text>
|
||||
|
||||
<CustomInput
|
||||
error=""
|
||||
label="First Name"
|
||||
placeholder="Enter first name"
|
||||
value={user?.firstName ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, firstName: text })}
|
||||
icon="person"
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
error=""
|
||||
label="First Name"
|
||||
placeholder="Enter first name"
|
||||
value={user?.firstName ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, firstName: text })}
|
||||
icon="person"
|
||||
/>
|
||||
<CustomInput
|
||||
error=""
|
||||
label="Last Name"
|
||||
placeholder="Enter last name"
|
||||
value={user?.lastName ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, lastName: text })}
|
||||
icon="person"
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
error=""
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
value={user?.email ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, email: text })}
|
||||
icon="mail"
|
||||
keyboardType="email-address"
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
error=""
|
||||
label="Last Name"
|
||||
placeholder="Enter last name"
|
||||
value={user?.lastName ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, lastName: text })}
|
||||
icon="person"
|
||||
/>
|
||||
<CustomInput
|
||||
error=""
|
||||
label="Username"
|
||||
placeholder="Enter username"
|
||||
value={user?.username ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, username: text })}
|
||||
icon="at"
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
error=""
|
||||
label="Address"
|
||||
placeholder="Enter address"
|
||||
value={user?.address ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, address: text })}
|
||||
icon="location"
|
||||
multiline
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
error=""
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
value={user?.email ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, email: text })}
|
||||
icon="mail"
|
||||
keyboardType="email-address"
|
||||
/>
|
||||
<CustomInput
|
||||
error=""
|
||||
label="Gender"
|
||||
placeholder="Enter gender"
|
||||
value={user?.gender ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, gender: text })}
|
||||
icon="male-female"
|
||||
/>
|
||||
|
||||
|
||||
<CustomInput
|
||||
error=""
|
||||
label="Username"
|
||||
placeholder="Enter username"
|
||||
value={user?.username ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, username: text })}
|
||||
icon="at"
|
||||
/>
|
||||
<CustomInput
|
||||
error=""
|
||||
label="Address"
|
||||
placeholder="Enter address"
|
||||
value={user?.address ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, address: text })}
|
||||
icon="location"
|
||||
multiline
|
||||
/>
|
||||
<CustomInput
|
||||
error=""
|
||||
label="Gender"
|
||||
placeholder="Enter gender"
|
||||
value={user?.gender ?? ""}
|
||||
onChangeText={(text) => setUser({ ...user, gender: text })}
|
||||
icon="male-female"
|
||||
/>
|
||||
|
||||
|
||||
<View style={styles.editButtons}>
|
||||
<CustomButton
|
||||
title="Save Changes"
|
||||
onPress={updateProfile}
|
||||
icon="save"
|
||||
style={styles.saveButton}
|
||||
buttonBackground={colors.pastelBlue}
|
||||
/>
|
||||
<CustomButton
|
||||
title="Cancel"
|
||||
onPress={() => setEditing(false)}
|
||||
icon="close"
|
||||
style={styles.cancelButton}
|
||||
buttonBackground={colors.white }
|
||||
/>
|
||||
</View>
|
||||
|
||||
|
||||
</View>
|
||||
</>
|
||||
<View style={styles.editButtons}>
|
||||
<CustomButton
|
||||
title="Save Changes"
|
||||
onPress={updateProfile}
|
||||
icon="save"
|
||||
style={styles.saveButton}
|
||||
buttonBackground={colors.pastelBlue}
|
||||
/>
|
||||
<CustomButton
|
||||
title="Cancel"
|
||||
onPress={() => setEditing(false)}
|
||||
icon="close"
|
||||
style={styles.cancelButton}
|
||||
buttonBackground={colors.white}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
@@ -775,10 +771,10 @@ const styles = StyleSheet.create({
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginBottom: 16,
|
||||
paddingHorizontal: 8,
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
detailIcon: {
|
||||
marginRight: 12,
|
||||
marginRight: 20,
|
||||
elevation:5
|
||||
},
|
||||
detailLabel: {
|
||||
@@ -794,7 +790,10 @@ const styles = StyleSheet.create({
|
||||
editButton: {
|
||||
elevation:5,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical:4,
|
||||
borderRadius: 15,
|
||||
borderWidth:2,
|
||||
borderColor:colors.pastelBlue
|
||||
},
|
||||
editSection: {
|
||||
marginBottom: '100%',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const BASE_URL = "https://exotic-humpback-mistakenly.ngrok-free.app/api/"
|
||||
export const BASE_URL = "http://192.168.1.9:9060/api/"
|
||||
// export const BASE_URL = "https://api.godemo.in/FundCollect/api/"
|
||||
export const LOGIN_URL = `${BASE_URL}user/generate/otp?mobiles=`
|
||||
export const VARIFY_OTP_URL = `${BASE_URL}user/otp/verify`
|
||||
|
||||
Reference in New Issue
Block a user