with helper funtion

This commit is contained in:
Abin Toppo
2025-09-19 13:39:04 +05:30
parent 4de484c0a5
commit d4a1866f34
8 changed files with 314 additions and 316 deletions
+3 -1
View File
@@ -19,6 +19,7 @@ import type { StackNavigationProp } from "@react-navigation/stack";
import { RootStackParamList } from "../navigation/AppNavigator"; import { RootStackParamList } from "../navigation/AppNavigator";
import { CustomButton, CustomInput } from "../components/common/Custom"; import { CustomButton, CustomInput } from "../components/common/Custom";
import colors from "../components/colors"; import colors from "../components/colors";
import { useSelector } from "react-redux";
const theme = { const theme = {
primary: "#111111", primary: "#111111",
@@ -41,7 +42,8 @@ export default function LoginScreen() {
const fade = useRef(new Animated.Value(0)).current; const fade = useRef(new Animated.Value(0)).current;
const slide = useRef(new Animated.Value(24)).current; const slide = useRef(new Animated.Value(24)).current;
const ctaScale = useRef(new Animated.Value(1)).current; const ctaScale = useRef(new Animated.Value(1)).current;
const user = useSelector((state:any) => state.user)
console.log("current user ----",user)
useEffect(() => { useEffect(() => {
Animated.parallel([ Animated.parallel([
Animated.timing(fade, { toValue: 1, duration: 600, useNativeDriver: true }), Animated.timing(fade, { toValue: 1, duration: 600, useNativeDriver: true }),
+7 -1
View File
@@ -18,9 +18,12 @@ import {
} from "react-native"; } from "react-native";
import { RootStackParamList } from "../navigation/AppNavigator"; import { RootStackParamList } from "../navigation/AppNavigator";
import * as Keychain from "react-native-keychain"; import * as Keychain from "react-native-keychain";
import { LOGIN_URL, VARIFY_OTP_URL } from "../utils/ApiUrls"; import { BASE_URL, LOGIN_URL, VARIFY_OTP_URL } from "../utils/ApiUrls";
import { StackNavigationProp } from "@react-navigation/stack"; import { StackNavigationProp } from "@react-navigation/stack";
import colors from "../components/colors"; import colors from "../components/colors";
import { useDispatch } from "react-redux";
import { setUserData } from "../redux/userSlice";
import { fetchUserProfile } from "../utils/HelperFunction";
const theme = { const theme = {
primary: "#111111", primary: "#111111",
@@ -47,6 +50,7 @@ export default function OtpVerificationScreen() {
const slide = useRef(new Animated.Value(24)).current; const slide = useRef(new Animated.Value(24)).current;
const ctaScale = useRef(new Animated.Value(1)).current; const ctaScale = useRef(new Animated.Value(1)).current;
useEffect(() => { useEffect(() => {
Animated.parallel([ Animated.parallel([
Animated.timing(fade, { toValue: 1, duration: 600, useNativeDriver: true }), Animated.timing(fade, { toValue: 1, duration: 600, useNativeDriver: true }),
@@ -74,6 +78,7 @@ export default function OtpVerificationScreen() {
} }
}; };
const onSubmit = async (otp: string, mobile: string) => { const onSubmit = async (otp: string, mobile: string) => {
try { try {
const payload = { mobile, otp }; const payload = { mobile, otp };
@@ -86,6 +91,7 @@ export default function OtpVerificationScreen() {
console.log("✅ OTP Verified:", res.data); console.log("✅ OTP Verified:", res.data);
if (res.data.statusCode === 200) { if (res.data.statusCode === 200) {
saveToken(res.data.data); saveToken(res.data.data);
fetchUserProfile()
navigation.navigate('Home') navigation.navigate('Home')
ToastAndroid.show("OTP Verified Successfully!", ToastAndroid.SHORT); ToastAndroid.show("OTP Verified Successfully!", ToastAndroid.SHORT);
} }
+6 -54
View File
@@ -9,67 +9,16 @@ import { useDispatch, useSelector } from "react-redux";
import { setUserData } from "../redux/userSlice"; import { setUserData } from "../redux/userSlice";
import colors from "../components/colors"; import colors from "../components/colors";
import { ApiResponse } from "./home/UserProfileStatus"; import { ApiResponse } from "./home/UserProfileStatus";
import { fetchUserProfile } from "../utils/HelperFunction";
const SplashScreen = () => { const SplashScreen = () => {
const dispatch = useDispatch();
const user = useSelector((state: any) => state.user); const user = useSelector((state: any) => state.user);
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>(); const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
const fadeAnim = useRef(new Animated.Value(1)).current; const fadeAnim = useRef(new Animated.Value(1)).current;
const fetchUserProfile = async () => {
try {
const credentials = await Keychain.getGenericPassword();
if (!credentials) {
// Alert.alert("Error", "You are not logged in");
return;
}
const token = credentials.password;
const response = await fetch(
`${BASE_URL}user/profile/view`,
{
method: "GET",
headers: {
Accept: "*/*",
Authorization: `Bearer ${token}`,
},
}
);
const result = await response.json();
if (result.status === "success") {
dispatch(setUserData({
id: result?.data.id,
userId: result?.data.userId,
username: result?.data.username,
firstName: result?.data.firstName,
lastName: result?.data.lastName,
gender: result?.data.gender,
mobile: result?.data.mobile,
email: result?.data.email,
address: result?.data.address,
bankAccountNumber: result?.data.bankAccountNumber,
bankName: result?.data.bankName,
branchCode: result?.data.branchCode,
profilePicture: result?.data.filePath, // or keep as `filePath`
filePath: result?.data.filePath,
imageBlob: result?.data.imageBlob,
role: result?.data.role,
organizerId: result?.data.organizerId,
organizerStatus: result?.data.organizerStatus,
profileStatus: true, // set true once profile is fetched
}));
console.log("splash user data:", result.data);
} else {
Alert.alert("Error", "Failed to fetch user profile");
}
} catch (error) {
console.error("❌ Error fetching profile:", error);
Alert.alert("Error", "Could not connect to server");
}
};
const userStatus = async () => { const userStatus = async () => {
@@ -133,7 +82,10 @@ const SplashScreen = () => {
}, [navigation, fadeAnim]); }, [navigation, fadeAnim]);
useEffect(() => { useEffect(() => {
fetchUserProfile(); const fetchProfile = async () => {
await fetchUserProfile();
};
fetchProfile();
}, []) }, [])
return ( return (
+211 -242
View File
@@ -1,5 +1,5 @@
import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { NativeStackScreenProps } from '@react-navigation/native-stack';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { import {
View, View,
Text, Text,
@@ -30,282 +30,251 @@ export interface GroupDetails {
startDate: [number, number, number]; startDate: [number, number, number];
endDate: [number, number, number] | null; endDate: [number, number, number] | null;
contributionAmount: number; contributionAmount: number;
minParticipants: number;
maxParticipants: number; maxParticipants: number;
latePaymentCharge: number | null; latePaymentCharge: number | null;
cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM'; cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM';
status: 'PENDING' | 'ACTIVE' | 'COMPLETED';
visibility: 'PUBLIC' | 'PRIVATE'; visibility: 'PUBLIC' | 'PRIVATE';
organizerFirstName: string;
organizerLastName: string;
customCycleDays: number;
status: string;
totalParticipants: number; totalParticipants: number;
participants: Participant[]; participants: Participant[];
} }
export interface EditGroupPayload { export interface EditGroupPayload {
groupName: string; groupName?: string;
description: string; description?: string;
visibility: 'PUBLIC' | 'PRIVATE'; maxParticipants?: number;
cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM';
contributionAmount: number;
maxParticipants: number;
latePaymentCharge: number;
customCycleDays: number;
startDate: string;
endDate: string | null;
} }
type EditGroupScreenProps = NativeStackScreenProps<RootStackParamList, 'EditGroup'>; type EditGroupScreenProps = NativeStackScreenProps<RootStackParamList, 'EditGroup'>;
type EditingSection = 'details' | 'financials' | null;
// --- Helper Components for Read-Only View --- // --- Helper Components ---
const InfoRow: React.FC<{ label: string; value: string | number; isStatus?: boolean }> = ({ label, value, isStatus = false }) => ( const InfoRow: React.FC<{ label: string; value: string | number | null; isStatus?: boolean }> = ({ label, value, isStatus = false }) => (
<View style={styles.infoRow}> <View style={styles.infoRow}>
<Text style={styles.infoLabel}>{label}</Text> <Text style={styles.infoLabel}>{label}</Text>
<Text <Text style={[styles.infoValue, isStatus && { color: value === 'ACTIVE' ? '#22c55e' : '#f97316' }]}>
style={[ {value ?? 'N/A'}
styles.infoValue, </Text>
isStatus && { color: value === 'ACTIVE' ? '#059669' : '#D97706' } // Green for Active, Amber for others </View>
]}
>
{value}
</Text>
</View>
); );
const ParticipantRow: React.FC<{ participant: Participant }> = ({ participant }) => ( const ParticipantRow: React.FC<{ participant: Participant }> = ({ participant }) => (
<View style={styles.participantRow}> <View style={styles.participantRow}>
<View> <View>
<View style={{ flexDirection: 'row', alignItems: 'center' }}> <View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={styles.participantName}>{`${participant.firstName} ${participant.lastName || ''}`.trim()}</Text> <Text style={styles.participantName}>{`${participant.firstName} ${participant.lastName || ''}`.trim()}</Text>
{participant.role === 'ORGANIZER' && ( {participant.role === 'ORGANIZER' && (
<View style={styles.organizerBadge}> <View style={styles.organizerBadge}><Text style={styles.organizerBadgeText}>Organizer</Text></View>
<Text style={styles.organizerBadgeText}>Organizer</Text> )}
</View> </View>
)} <Text style={styles.participantMobile}>{participant.mobile}</Text>
</View> </View>
<Text style={styles.participantMobile}>{participant.mobile}</Text>
</View> </View>
</View>
); );
const FormInput: React.FC<{ label: string; value: string; onChangeText: (text: string) => void; multiline?: boolean; keyboardType?: 'default' | 'numeric' }> =
({ label, value, onChangeText, multiline = false, keyboardType = 'default' }) => (
<View style={styles.inputContainer}>
<Text style={styles.inputLabel}>{label}</Text>
<TextInput
style={[styles.input, multiline && styles.textArea]}
value={value}
onChangeText={onChangeText}
multiline={multiline}
keyboardType={keyboardType}
placeholderTextColor="#9CA3AF"
/>
</View>
);
// --- Main Screen Component --- // --- Main Screen Component ---
const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route, navigation }) => { const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
const { groupId } = route.params; const { groupId } = route.params;
// --- State Management --- const [editingSection, setEditingSection] = useState<EditingSection>(null);
const [isEditing, setIsEditing] = useState(false); const [groupDetails, setGroupDetails] = useState<GroupDetails | null>(null);
const [groupDetails, setGroupDetails] = useState<GroupDetails | null>(null); const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false);
const [saving, setSaving] = useState(false); const [error, setError] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
// --- Form State --- const [groupName, setGroupName] = useState('');
const [groupName, setGroupName] = useState(''); const [description, setDescription] = useState('');
const [description, setDescription] = useState(''); const [maxParticipants, setMaxParticipants] = useState('');
const [contributionAmount, setContributionAmount] = useState('');
const [maxParticipants, setMaxParticipants] = useState('');
const [latePaymentCharge, setLatePaymentCharge] = useState('');
// --- Data Fetching --- const fetchGroupDetails = useCallback(async () => {
const fetchGroupDetails = async () => { setLoading(true);
if (!groupId) { setError(null);
setError("Group ID not provided."); try {
setLoading(false); const response = await apiCall(`https://api.godemo.in/FundCollect/api/fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string };
return; if (response.status === 'success' && response.data) {
} setGroupDetails(response.data);
setLoading(true); setGroupName(response.data.groupName);
try { setDescription(response.data.description);
console.log(`Fetching details for group ID: ${groupId}`); setMaxParticipants(response.data.maxParticipants.toString());
const response = await apiCall( } else {
`https://api.godemo.in/FundCollect/api/fund/group/details?groupId=${groupId}`, throw new Error(response.message || 'Failed to fetch group data.');
'GET', }
) as { status: string; data?: GroupDetails; message?: string }; } catch (e: any) {
setError(e.message);
if (response.status === 'success' && response.data) { } finally {
setGroupDetails(response.data); setLoading(false);
// Populate form state immediately }
const data = response.data; }, [groupId]);
setGroupName(data.groupName);
setDescription(data.description);
setContributionAmount(data.contributionAmount.toString());
setMaxParticipants(data.maxParticipants.toString());
setLatePaymentCharge((data.latePaymentCharge || 0).toString());
} else {
throw new Error(response.message || 'Failed to fetch group data.');
}
} catch (e: any) {
setError(e.message);
} finally {
setLoading(false);
}
};
useEffect(() => { useEffect(() => {
fetchGroupDetails(); fetchGroupDetails();
}, [groupId]); }, [fetchGroupDetails]);
// --- Action Handlers --- const handleCancel = () => {
const handleCancelEdit = () => { if (groupDetails) {
// Re-populate state from original details to discard any changes setGroupName(groupDetails.groupName);
if (groupDetails) { setDescription(groupDetails.description);
setGroupName(groupDetails.groupName); setMaxParticipants(groupDetails.maxParticipants.toString());
setDescription(groupDetails.description); }
setContributionAmount(groupDetails.contributionAmount.toString()); setEditingSection(null);
setMaxParticipants(groupDetails.maxParticipants.toString());
setLatePaymentCharge((groupDetails.latePaymentCharge || 0).toString());
}
setIsEditing(false);
};
const handleSaveChanges = async () => {
if (!groupDetails) return;
setSaving(true);
const payload: EditGroupPayload = {
groupName,
description,
visibility: groupDetails.visibility, // Assuming these are not editable
cycleType: groupDetails.cycleType,
contributionAmount: parseFloat(contributionAmount) || 0,
maxParticipants: parseInt(maxParticipants, 10) || 0,
latePaymentCharge: parseFloat(latePaymentCharge) || 0,
customCycleDays: groupDetails.customCycleDays,
startDate: new Date(groupDetails.startDate.join('-')).toISOString().split('T')[0],
endDate: groupDetails.endDate ? new Date(groupDetails.endDate.join('-')).toISOString().split('T')[0] : null,
}; };
try { const handleSave = async (section: EditingSection) => {
const response = await apiCall( if (!groupDetails || !section) return;
`https://api.godemo.in/FundCollect/api/fund/groups/${groupDetails.id}/edit`, 'PUT', payload
) as { status: string; message?: string };
if (response.status === 'success') { let payload: EditGroupPayload = {};
Alert.alert('Success', 'Group details have been updated.'); if (section === 'details') {
setIsEditing(false); payload = { groupName, description };
await fetchGroupDetails(); // Refresh data to show updated view } else if (section === 'financials') {
} else { const newMaxParticipants = parseInt(maxParticipants, 10);
throw new Error(response.message || 'Failed to save changes.'); if (newMaxParticipants < groupDetails.totalParticipants) {
} Alert.alert('Invalid Input', `Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`);
} catch (e: any) { return;
Alert.alert('Save Error', e.message); }
} finally { payload = { maxParticipants: newMaxParticipants };
setSaving(false); }
}
};
// --- Render Logic --- setSaving(true);
if (loading) return <View style={styles.centered}><ActivityIndicator size="large" color="#4F46E5" /></View>; try {
if (error) return <View style={styles.centered}><Text style={styles.errorText}>{error}</Text></View>; await apiCall(`https://api.godemo.in/FundCollect/api/fund/groups/${groupDetails.id}/edit`, 'PUT', payload);
if (!groupDetails) return null; Alert.alert('Success', 'Group has been updated.');
setEditingSection(null);
return ( await fetchGroupDetails();
<AppView paddingHorizontal={0}> } catch (e: any) {
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}> Alert.alert('Save Error', e.message || 'An unexpected error occurred.');
<View style={styles.header}> } finally {
<Text style={styles.headerTitle}>Group Overview</Text> setSaving(false);
{!isEditing && groupDetails.status !== 'ACTIVE' ? ( }
<TouchableOpacity style={styles.editButton} onPress={() => setIsEditing(true)}> };
<Text style={styles.editButtonText}>Edit</Text>
</TouchableOpacity> const CardActionButtons: React.FC<{ section: EditingSection }> = ({ section }) => (
) : isEditing ? ( <View style={styles.cardActions}>
<TouchableOpacity onPress={handleCancelEdit}> <TouchableOpacity style={[styles.button, styles.cancelButton]} onPress={handleCancel} disabled={saving}>
<Text style={styles.cancelButtonText}>Cancel</Text> <Text style={[styles.buttonText, { color: '#374151' }]}>Cancel</Text>
</TouchableOpacity> </TouchableOpacity>
) : null} <TouchableOpacity style={[styles.button, styles.saveButton]} onPress={() => handleSave(section)} disabled={saving}>
</View> {saving ? <ActivityIndicator color="#FFFFFF" size="small" /> : <Text style={styles.buttonText}>Save</Text>}
</TouchableOpacity>
{/* --- Main Details Card --- */}
<View style={styles.card}>
{isEditing ? (
<>
<Text style={styles.inputLabel}>Group Name</Text>
<TextInput style={styles.input} value={groupName} onChangeText={setGroupName} />
<Text style={styles.inputLabel}>Description</Text>
<TextInput style={[styles.input, styles.textArea]} value={description} onChangeText={setDescription} multiline />
</>
) : (
<>
<Text style={styles.groupNameView}>{groupDetails.groupName}</Text>
<Text style={styles.descriptionView}>{groupDetails.description}</Text>
</>
)}
<View style={styles.divider} />
<View style={styles.row}>
<View style={styles.column}>
<Text style={styles.inputLabel}>Contribution</Text>
<Text style={styles.dataText}>{`₹${groupDetails.contributionAmount.toLocaleString('en-IN')}`}</Text>
</View>
<View style={styles.column}>
<Text style={styles.inputLabel}>Cycle</Text>
<Text style={styles.dataText}>{groupDetails.cycleType}</Text>
</View>
</View> </View>
</View> );
{/* --- Participants Card --- */}
<View style={styles.card}>
<Text style={styles.cardTitle}>Participants ({groupDetails.totalParticipants}/{groupDetails.maxParticipants})</Text>
{groupDetails.participants.map(p => <ParticipantRow key={`${p.id}-${p.mobile}`} participant={p} />)}
</View>
{/* --- Other Details Card (Always Read-Only) --- */} if (loading) return <View style={styles.centered}><ActivityIndicator size="large" color="#4F46E5" /></View>;
<View style={styles.card}> if (error) return <View style={styles.centered}><Text style={styles.errorText}>{error}</Text></View>;
<InfoRow label="Group Status" value={groupDetails.status} isStatus /> if (!groupDetails) return null;
<InfoRow label="Visibility" value={groupDetails.visibility} />
<InfoRow label="Start Date" value={new Date(groupDetails.startDate.join('-')).toLocaleDateString()} /> const isEditable = groupDetails.status === 'PENDING';
</View>
return (
{isEditing && ( <AppView paddingHorizontal={0}>
<TouchableOpacity style={styles.saveButton} onPress={handleSaveChanges} disabled={saving}> <ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
{saving ? <ActivityIndicator color="#FFFFFF" /> : <Text style={styles.saveButtonText}>Save Changes</Text>} <Text style={styles.headerTitle}>Group Overview</Text>
</TouchableOpacity>
)} <View style={styles.card}>
</ScrollView> <View style={styles.cardHeader}>
</AppView> <Text style={styles.cardTitle}>Group Details</Text>
); {isEditable && editingSection !== 'details' && (
<TouchableOpacity onPress={() => setEditingSection('details')}><Text style={styles.editButtonText}>Edit</Text></TouchableOpacity>
)}
</View>
{editingSection === 'details' ? (
<>
<FormInput label="Group Name" value={groupName} onChangeText={setGroupName} />
<FormInput label="Description" value={description} onChangeText={setDescription} multiline />
<CardActionButtons section="details" />
</>
) : (
<>
<Text style={styles.groupNameView}>{groupDetails.groupName}</Text>
<Text style={styles.descriptionView}>{groupDetails.description}</Text>
</>
)}
</View>
<View style={styles.card}>
<View style={styles.cardHeader}>
<Text style={styles.cardTitle}>Financial Rules</Text>
{isEditable && editingSection !== 'financials' && (
<TouchableOpacity onPress={() => setEditingSection('financials')}><Text style={styles.editButtonText}>Edit</Text></TouchableOpacity>
)}
</View>
<InfoRow label="Contribution" value={`₹${groupDetails.contributionAmount.toLocaleString('en-IN')}`} />
{editingSection === 'financials' ? (
<>
<FormInput label="Max Participants" value={maxParticipants} onChangeText={setMaxParticipants} keyboardType="numeric" />
<CardActionButtons section="financials" />
</>
) : (
<InfoRow label="Participants" value={`${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}`} />
)}
<InfoRow label="Payment Cycle" value={groupDetails.cycleType} />
<InfoRow label="Group Status" value={groupDetails.status} isStatus />
</View>
<View style={styles.card}>
<Text style={styles.cardTitle}>Participants ({groupDetails.totalParticipants})</Text>
{groupDetails.participants.length > 0 ? (
groupDetails.participants.map(p => <ParticipantRow key={p.id} participant={p} />)
) : (
<Text style={styles.noParticipantsText}>No participants have joined yet.</Text>
)}
</View>
</ScrollView>
</AppView>
);
}; };
// --- Styles ---
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#F3F4F6' }, container: { flex: 1, backgroundColor: '#F3F4F6' },
contentContainer: { paddingVertical: 20, paddingHorizontal: 16 }, contentContainer: { padding: 16, paddingBottom: 40 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F3F4F6' }, centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
errorText: { color: '#EF4444', fontSize: 16, textAlign: 'center' }, errorText: { color: '#EF4444', fontSize: 16, textAlign: 'center' },
headerTitle: { fontSize: 28, fontWeight: 'bold', color: '#111827', marginBottom: 20 },
header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 },
headerTitle: { fontSize: 26, fontWeight: 'bold', color: '#111827' }, card: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 16, shadowColor: '#374151', shadowOpacity: 0.05, shadowRadius: 10 },
editButton: { backgroundColor: '#E0E7FF', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20 }, cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', borderBottomWidth: 1, borderBottomColor: '#E5E7EB', paddingBottom: 10, marginBottom: 16 },
editButtonText: { color: '#4338CA', fontSize: 14, fontWeight: '600' }, cardTitle: { fontSize: 18, fontWeight: 'bold', color: '#1F2937' },
cancelButtonText: { fontSize: 16, color: '#6B7280', fontWeight: '500' }, editButtonText: { color: '#4F46E5', fontSize: 15, fontWeight: '600' },
card: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 16, elevation: 1, shadowColor: '#374151', shadowOpacity: 0.05, shadowRadius: 10, shadowOffset: { width: 0, height: 4 } }, groupNameView: { fontSize: 22, fontWeight: '700', color: '#1F2937', marginBottom: 8 },
cardTitle: { fontSize: 18, fontWeight: '600', color: '#1F2937', marginBottom: 10 }, descriptionView: { fontSize: 16, color: '#6B7280', lineHeight: 24 },
groupNameView: { fontSize: 22, fontWeight: '700', color: '#1F2937', marginBottom: 8 }, inputContainer: { marginBottom: 16 },
descriptionView: { fontSize: 16, color: '#6B7280', lineHeight: 24 }, inputLabel: { fontSize: 14, color: '#6B7280', marginBottom: 8, fontWeight: '500' },
input: { backgroundColor: '#F9FAFB', borderWidth: 1, borderColor: '#D1D5DB', borderRadius: 10, padding: 14, fontSize: 16 },
divider: { height: 1, backgroundColor: '#E5E7EB', marginVertical: 20 }, textArea: { height: 100, textAlignVertical: 'top' },
row: { flexDirection: 'row', gap: 20 }, infoRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: '#F3F4F6' },
column: { flex: 1 }, infoLabel: { fontSize: 16, color: '#6B7280' },
infoValue: { fontSize: 16, fontWeight: '600', textTransform: 'capitalize' },
inputLabel: { fontSize: 14, color: '#6B7280', marginBottom: 8, fontWeight: '500' },
input: { backgroundColor: '#F9FAFB', borderWidth: 1, borderColor: '#D1D5DB', borderRadius: 10, paddingHorizontal: 14, paddingVertical: 12, fontSize: 16, color: '#111827', marginBottom: 16 }, participantRow: { paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#F3F4F6' },
textArea: { height: 80, textAlignVertical: 'top' }, participantName: { fontSize: 16, fontWeight: '600', color: '#111827' },
dataText: { fontSize: 18, fontWeight: '600', color: '#111827' }, participantMobile: { fontSize: 14, color: '#6B7280', marginTop: 4 },
organizerBadge: { backgroundColor: '#FEF3C7', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 12, marginLeft: 10 },
infoRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#F3F4F6' }, organizerBadgeText: { color: '#92400E', fontSize: 12, fontWeight: '500' },
infoLabel: { fontSize: 16, color: '#6B7280' }, noParticipantsText: { fontSize: 15, color: '#6B7280', textAlign: 'center', paddingVertical: 20 },
infoValue: { fontSize: 16, color: '#1F2937', fontWeight: '600' },
cardActions: { flexDirection: 'row', justifyContent: 'flex-end', marginTop: 20, borderTopWidth: 1, borderTopColor: '#E5E7EB', paddingTop: 16 },
participantRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#F3F4F6' }, button: { borderRadius: 10, paddingVertical: 10, paddingHorizontal: 20, marginLeft: 10, minWidth: 80, alignItems: 'center' },
participantName: { fontSize: 16, fontWeight: '600', color: '#111827' }, saveButton: { backgroundColor: '#4F46E5' },
participantMobile: { fontSize: 14, color: '#6B7280', marginTop: 4 }, cancelButton: { backgroundColor: '#E5E7EB' },
organizerBadge: { backgroundColor: '#FEF3C7', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 12, marginLeft: 10 }, buttonText: { color: '#FFFFFF', fontSize: 15, fontWeight: '600' },
organizerBadgeText: { color: '#92400E', fontSize: 12, fontWeight: '500' },
saveButton: { backgroundColor: '#4F46E5', borderRadius: 12, paddingVertical: 16, alignItems: 'center', marginTop: 10, elevation: 2 },
saveButtonText: { color: '#FFFFFF', fontSize: 16, fontWeight: '600' },
}); });
export default EditGroupScreen; export default EditGroupScreen;
+20 -15
View File
@@ -14,7 +14,7 @@ import {
import { useNavigation, useFocusEffect } from "@react-navigation/native"; import { useNavigation, useFocusEffect } from "@react-navigation/native";
import { StackNavigationProp } from "@react-navigation/stack"; import { StackNavigationProp } from "@react-navigation/stack";
import { RootStackParamList } from "../../navigation/AppNavigator"; import { RootStackParamList } from "../../navigation/AppNavigator";
import { APPROVE_REQUEST_URL, BASE_URL, DECLINE_REQUEST_URL, JOIN_GROUP_URL, MY_GROUPS_URL, REQUESTS_URL } from "../../utils/ApiUrls"; import { APPROVE_REQUEST_URL, BASE_URL, DECLINE_REQUEST_URL, GET_JOINED_GROUP_URL, JOIN_GROUP_URL, MY_GROUPS_URL, REQUESTS_URL } from "../../utils/ApiUrls";
import colors from "../../components/colors"; import colors from "../../components/colors";
import AppView from "../../components/AppView"; import AppView from "../../components/AppView";
import { apiCall } from "../../utils/ApiHelper"; import { apiCall } from "../../utils/ApiHelper";
@@ -23,11 +23,13 @@ import { MyGroup, JoinedGroup, JoinRequest, ApiResponse } from '../../types';
import { MyGroupCard } from "../../components/MyGroupCard"; import { MyGroupCard } from "../../components/MyGroupCard";
import { JoinedGroupCard } from "../../components/JoinedGroupCard"; import { JoinedGroupCard } from "../../components/JoinedGroupCard";
import { RequestCard } from "../../components/RequestCard"; import { RequestCard } from "../../components/RequestCard";
import { useSelector } from "react-redux";
type GroupManagementNavProp = StackNavigationProp<RootStackParamList>; type GroupManagementNavProp = StackNavigationProp<RootStackParamList>;
type ActiveTab = "My Groups" | "Joined Groups" | "Requests"; type ActiveTab = "My Groups" | "Joined Groups" | "Requests" | "Your Requests";
const ManagementScreen: FC = () => { const ManagementScreen: FC = () => {
const user = useSelector((state:any) => state.user)
const [activeTab, setActiveTab] = useState<ActiveTab>("My Groups"); const [activeTab, setActiveTab] = useState<ActiveTab>("My Groups");
const navigation = useNavigation<GroupManagementNavProp>(); const navigation = useNavigation<GroupManagementNavProp>();
const [myGroups, setMyGroups] = useState<MyGroup[]>([]); const [myGroups, setMyGroups] = useState<MyGroup[]>([]);
@@ -35,8 +37,9 @@ const ManagementScreen: FC = () => {
const [requests, setRequests] = useState<JoinRequest[]>([]); const [requests, setRequests] = useState<JoinRequest[]>([]);
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const userRole = "ORGANIZER"; const userRole = user.role;
console.log(" abin user : ", user)
const fetchData = useCallback(async () => { const fetchData = useCallback(async () => {
if (loading) return; if (loading) return;
setLoading(true); setLoading(true);
@@ -44,9 +47,9 @@ const ManagementScreen: FC = () => {
try { try {
if (userRole === "ORGANIZER") { if (userRole === "ORGANIZER") {
// Fetch all data concurrently // Fetch all data concurrently
const [myGroupsRes,joinedGroupsRes,requestsRes] = await Promise.all([ const [myGroupsRes, joinedGroupsRes, requestsRes] = await Promise.all([
apiCall(MY_GROUPS_URL, "GET") as Promise<ApiResponse<MyGroup[]>>, apiCall(MY_GROUPS_URL, "GET") as Promise<ApiResponse<MyGroup[]>>,
apiCall(JOIN_GROUP_URL, "GET") as Promise<ApiResponse<JoinedGroup[]>>, // Assuming this is also needed apiCall(GET_JOINED_GROUP_URL, "GET") as Promise<ApiResponse<JoinedGroup[]>>, // Assuming this is also needed
apiCall(REQUESTS_URL, "GET") as Promise<ApiResponse<JoinRequest[]>>, apiCall(REQUESTS_URL, "GET") as Promise<ApiResponse<JoinRequest[]>>,
]); ]);
@@ -58,8 +61,10 @@ const ManagementScreen: FC = () => {
setRequests(requestsRes.data || []); setRequests(requestsRes.data || []);
} else { } else {
// Fetch only joined groups for non-organizers // Fetch only joined groups for non-organizers
// const joinedGroupsRes = await apiCall(JOINED_GROUPS_URL, "GET") as Promise<ApiResponse<JoinedGroup[]>>; const [joinedGroupsRes] = await Promise.all([
// setJoinedGroups(joinedGroupsRes.data || []); apiCall(GET_JOINED_GROUP_URL, "GET") as Promise<ApiResponse<JoinedGroup[]>>, // Assuming this is also needed
]);
setJoinedGroups(joinedGroupsRes.data || []);
} }
} catch (e) { } catch (e) {
console.error("Fetch Data Error:", e); console.error("Fetch Data Error:", e);
@@ -74,7 +79,7 @@ const ManagementScreen: FC = () => {
const handleAction = async (url: string, body: object, successMessage: string, failureMessage: string, action: string) => { const handleAction = async (url: string, body: object, successMessage: string, failureMessage: string, action: string) => {
// ... (logic remains the same) // ... (logic remains the same)
console.log(url) console.log(url)
try { try {
setLoading(true); // Optionally show a loading state during the action setLoading(true); // Optionally show a loading state during the action
// Make the POST request with an empty body // Make the POST request with an empty body
const res = await apiCall(url, "PUT", {}) as ApiResponse<null> | null; const res = await apiCall(url, "PUT", {}) as ApiResponse<null> | null;
@@ -93,15 +98,15 @@ const ManagementScreen: FC = () => {
} }
}; };
const handleApprove = (req: JoinRequest) => handleAction(APPROVE_REQUEST_URL+req.requestId+"/approve", { requestId: req.requestId }, `${req.userName} has been added.`, "Failed to approve request.","Approve"); const handleApprove = (req: JoinRequest) => handleAction(APPROVE_REQUEST_URL + req.requestId + "/approve", { requestId: req.requestId }, `${req.userName} has been added.`, "Failed to approve request.", "Approve");
const handleDecline = (req: JoinRequest) => handleAction(DECLINE_REQUEST_URL+req.requestId+"/reject?reason=/"+" ", { requestId: req.requestId }, `Request from ${req.userName} has been declined.`, "Failed to decline request.","Decline"); const handleDecline = (req: JoinRequest) => handleAction(DECLINE_REQUEST_URL + req.requestId + "/reject?reason=/" + " ", { requestId: req.requestId }, `Request from ${req.userName} has been declined.`, "Failed to decline request.", "Decline");
const handleManageGroup = (group: MyGroup) => navigation.navigate('EditGroup', { groupId: group.id }); const handleManageGroup = (group: MyGroup) => navigation.navigate('EditGroup', { groupId: group.id });
const renderContent = () => { const renderContent = () => {
if (error) { if (error) {
return <Text style={styles.emptyText}>{error}</Text>; return <Text style={styles.emptyText}>{error}</Text>;
} }
switch (activeTab) { switch (activeTab) {
case "My Groups": case "My Groups":
if (loading) { if (loading) {
@@ -151,9 +156,9 @@ const ManagementScreen: FC = () => {
default: return null; default: return null;
} }
}; };
const tabs: ActiveTab[] = userRole === 'ORGANIZER' ? ["My Groups", "Joined Groups", "Requests"] : ["Joined Groups"]; const tabs: ActiveTab[] = userRole === 'ORGANIZER' ? ["My Groups", "Joined Groups", "Requests"] : ["Joined Groups", "Your Requests"];
return ( return (
<AppView style={styles.container} paddingHorizontal={0}> <AppView style={styles.container} paddingHorizontal={0}>
<View style={styles.header}> <View style={styles.header}>
@@ -176,7 +181,7 @@ const ManagementScreen: FC = () => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
backgroundColor: '#f5f6fa' backgroundColor: '#f5f6fa'
}, },
header: { header: {
paddingHorizontal: 20, paddingHorizontal: 20,
+1 -1
View File
@@ -288,7 +288,7 @@ const Profile = () => {
console.log("user image :", user?.filePath); console.log("user image :", user?.filePath);
const getProfileImage = () => { const getProfileImage = () => {
if (user?.imageBlob) { if (user?.imageBlob) {
return `data:image/jpeg;base64,${user.imageBlob}`; return `data:image/jpeg;base64,${user.imageBlob}`;
} }
if (user?.filePath) { if (user?.filePath) {
return user.filePath; return user.filePath;
+4 -2
View File
@@ -5,10 +5,12 @@ export const ORGRAGISTRATION = `${BASE_URL}organizer/registration/apply`
export const GROUP_CREATION_URL = `${BASE_URL}fund/groups/{organizerId}` export const GROUP_CREATION_URL = `${BASE_URL}fund/groups/{organizerId}`
export const PROFILE_URL =`${BASE_URL}user/profile/view` export const PROFILE_URL =`${BASE_URL}user/profile/view`
export const PUBLIC_GROUPS_URL =`${BASE_URL}fund/user/public` export const PUBLIC_GROUPS_URL =`${BASE_URL}fund/user/public`
export const JOIN_GROUP_URL = `${BASE_URL}fund/my/join/groups`; export const JOIN_GROUP_URL = `${BASE_URL}fund/`;
export const MY_GROUPS_URL =`https://api.godemo.in/FundCollect/api/fund/getAll/my/groups` export const MY_GROUPS_URL =`https://api.godemo.in/FundCollect/api/fund/getAll/my/groups`
export const REQUESTS_URL =`https://api.godemo.in/FundCollect/api/fund/group/pending/request` export const REQUESTS_URL =`https://api.godemo.in/FundCollect/api/fund/group/pending/request`
//https://api.godemo.in/FundCollect/api/fund/groups/2a80cdaa-8532-4b8a-87f0-9c1023d18a7f/join/64 //https://api.godemo.in/FundCollect/api/fund/groups/2a80cdaa-8532-4b8a-87f0-9c1023d18a7f/join/64
export const APPROVE_REQUEST_URL =`https://api.godemo.in/FundCollect/api/fund/requests/` export const APPROVE_REQUEST_URL =`https://api.godemo.in/FundCollect/api/fund/requests/`
export const DECLINE_REQUEST_URL =`https://api.godemo.in/FundCollect/api/fund/user/my/groups` export const DECLINE_REQUEST_URL =`https://api.godemo.in/FundCollect/api/fund/user/my/groups`
// https://api.godemo.in/FundCollect/api/fund/groups/b89f56a1-952d-41b3-ab8d-3c28c91697f5/join/100005
export const GET_JOINED_GROUP_URL = `https://api.godemo.in/FundCollect/api/fund/my/join/groups`
+62
View File
@@ -0,0 +1,62 @@
import { useDispatch } from "react-redux";
import { BASE_URL } from "./ApiUrls";
import * as Keychain from "react-native-keychain";
import { setUserData } from "../redux/userSlice";
import { Alert } from "react-native";
export const fetchUserProfile = async () => {
const dispatch = useDispatch();
try {
const credentials = await Keychain.getGenericPassword();
if (!credentials) {
// Alert.alert("Error", "You are not logged in");
return;
}
const token = credentials.password;
const response = await fetch(
`${BASE_URL}user/profile/view`,
{
method: "GET",
headers: {
Accept: "*/*",
Authorization: `Bearer ${token}`,
},
}
);
const result = await response.json();
if (result.status === "success") {
dispatch(setUserData({
id: result?.data.id,
userId: result?.data.userId,
username: result?.data.username,
firstName: result?.data.firstName,
lastName: result?.data.lastName,
gender: result?.data.gender,
mobile: result?.data.mobile,
email: result?.data.email,
address: result?.data.address,
bankAccountNumber: result?.data.bankAccountNumber,
bankName: result?.data.bankName,
branchCode: result?.data.branchCode,
profilePicture: result?.data.filePath, // or keep as `filePath`
filePath: result?.data.filePath,
imageBlob: result?.data.imageBlob,
role: result?.data.role,
organizerId: result?.data.organizerId,
organizerStatus: result?.data.organizerStatus,
profileStatus: true, // set true once profile is fetched
}));
console.log("splash user data:", result.data);
} else {
Alert.alert("Error", "Failed to fetch user profile");
}
} catch (error) {
console.error("❌ Error fetching profile:", error);
Alert.alert("Error", "Could not connect to server");
}
};