diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx index b2c63a8..87c9b6d 100644 --- a/src/components/common/Header.tsx +++ b/src/components/common/Header.tsx @@ -1,18 +1,24 @@ import React from "react"; import { View, Text, TouchableOpacity, StyleSheet } from "react-native"; import { useNavigation } from "@react-navigation/native"; -import { Ionicons } from "@react-native-vector-icons/ionicons" +import {Ionicons} from "@react-native-vector-icons/ionicons"; type HeaderProps = { title: string; showBack?: boolean; // optional, default true onBackPress?: () => void; // optional custom back action + rightAction?: React.ReactNode; // allow passing JSX for right side + titleColor?:string; + arrowColor?:string }; const Header: React.FC = ({ title, showBack = true, onBackPress, + rightAction, + titleColor = 'pink', + arrowColor='pink' }) => { const navigation = useNavigation(); @@ -26,12 +32,15 @@ const Header: React.FC = ({ return ( - {showBack && ( - - + {showBack ? ( + + + ) : ( + // placeholder to keep spacing )} - {title} + {title} + {rightAction} ); }; @@ -42,21 +51,11 @@ const styles = StyleSheet.create({ header: { flexDirection: "row", alignItems: "center", - paddingHorizontal: 16, + justifyContent: "space-between", paddingVertical: 12, - backgroundColor: "#fff", - shadowColor: "#000", - shadowOpacity: 0.05, - shadowOffset: { width: 0, height: 2 }, - shadowRadius: 6, - elevation: 3, - }, - backButton: { - marginRight: 12, }, headerTitle: { fontSize: 18, fontWeight: "600", - color: "#000", }, }); diff --git a/src/screens/GroupScreen.tsx b/src/screens/GroupScreen.tsx index 9154aa2..e46dd4d 100644 --- a/src/screens/GroupScreen.tsx +++ b/src/screens/GroupScreen.tsx @@ -21,12 +21,12 @@ import { StackNavigationProp } from "@react-navigation/stack"; import { RootStackParamList } from "../navigation/AppNavigator"; import AppView from "../components/AppView"; import { apiCall } from "../utils/ApiHelper"; -import { BASE_URL, JOIN_GROUP_URL, PUT_USER_STATUS } from "../utils/ApiUrls"; +import { BASE_URL, GET_JOINED_GROUP_URL, JOIN_GROUP_URL, PUBLIC_GROUPS_URL, PUT_USER_STATUS } from "../utils/ApiUrls"; import colors from "../components/colors"; import { ApiResponse } from "./home/UserProfileStatus"; import * as Keychain from "react-native-keychain"; const { width } = Dimensions.get('window'); -const PUBLIC_GROUPS_URL = "https://api.godemo.in/FundCollect/api/fund/user/public"; + // --- Types --- @@ -114,7 +114,31 @@ export default function GroupScreen() { } }; - useFocusEffect(React.useCallback(() => { fetchGroups(); }, [])); + const fetchJoinedGroups = async () => { + if (loading) return; + setLoading(true); + setError(null); + try { + console.log("first-????-") + const res = await apiCall(GET_JOINED_GROUP_URL, "GET") as { data?: Group[] }; + console.log("first---", res) + if (res && res.data) { + setJoinedGroups(res.data); + + } else { + throw new Error("No data received"); + } + } catch (e) { + setError("Failed to fetch groups. Please try again."); + } finally { + setLoading(false); + } + }; + + useFocusEffect(React.useCallback(() => { + fetchGroups() + fetchJoinedGroups() + }, [])); const filterData = (data: Group[]) => { if (!searchQuery.trim()) return data; @@ -142,6 +166,7 @@ export default function GroupScreen() { Alert.alert("Error", "An unexpected error occurred."); } }; + const userStatus = async () => { try { const credentials = await Keychain.getGenericPassword(); @@ -239,7 +264,7 @@ export default function GroupScreen() { refreshControl={ } - ListFooterComponent={} + ListFooterComponent={} /> ); }; @@ -265,7 +290,7 @@ export default function GroupScreen() { keyExtractor={(item) => item.id} renderItem={({ item }) => } style={styles.featuredList} - ListEmptyComponent={!loading && featuredGroups.length === 0 ? No featured groups. : null} + // ListEmptyComponent={!loading && featuredGroups.length === 0 ? No featured groups. : null} /> diff --git a/src/screens/LoginScreen.tsx b/src/screens/LoginScreen.tsx index e37fdeb..5b37f77 100644 --- a/src/screens/LoginScreen.tsx +++ b/src/screens/LoginScreen.tsx @@ -19,7 +19,6 @@ import type { StackNavigationProp } from "@react-navigation/stack"; import { RootStackParamList } from "../navigation/AppNavigator"; import { CustomButton, CustomInput } from "../components/common/Custom"; import colors from "../components/colors"; -import { useSelector } from "react-redux"; const theme = { primary: "#111111", @@ -42,8 +41,7 @@ export default function LoginScreen() { const fade = useRef(new Animated.Value(0)).current; const slide = useRef(new Animated.Value(24)).current; const ctaScale = useRef(new Animated.Value(1)).current; - const user = useSelector((state: any) => state.user) - console.log("current user ----", user) + useEffect(() => { Animated.parallel([ Animated.timing(fade, { toValue: 1, duration: 600, useNativeDriver: true }), @@ -55,8 +53,6 @@ export default function LoginScreen() { if (mobile.length === 10) { setLoading(true); try { - console.log("LOGIN_URL console:", LOGIN_URL, "mobile:", mobile); - const res = await axios.get(LOGIN_URL, { params: { mobile }, }); @@ -89,23 +85,6 @@ export default function LoginScreen() { } }; - -const onPressIn = () => - Animated.spring(ctaScale, { - toValue: 0.97, - useNativeDriver: true, - speed: 20, - bounciness: 0, - }).start(); - -const onPressOut = () => - Animated.spring(ctaScale, { - toValue: 1, - useNativeDriver: true, - speed: 20, - bounciness: 6, - }).start(); - return ( >([]); const route = useRoute(); const { mobile } = route.params; - const navigation = useNavigation>(); + const navigation = useNavigation(); // animations const fade = useRef(new Animated.Value(0)).current; const slide = useRef(new Animated.Value(24)).current; @@ -92,7 +89,7 @@ export default function OtpVerificationScreen() { if (res.data.statusCode === 200) { saveToken(res.data.data); fetchUserProfile() - navigation.navigate('Home') + navigation.navigate("Home", { refreshProfile: true }); ToastAndroid.show("OTP Verified Successfully!", ToastAndroid.SHORT); } } catch (err: any) { diff --git a/src/screens/SplashScreen.tsx b/src/screens/SplashScreen.tsx index fb715ab..cde9f53 100644 --- a/src/screens/SplashScreen.tsx +++ b/src/screens/SplashScreen.tsx @@ -13,6 +13,8 @@ const SplashScreen = () => { const navigation = useNavigation>(); const fadeAnim = useRef(new Animated.Value(1)).current; const dispatch = useDispatch(); + + const fetchUserProfile = async (dispatch: any, navigation: any) => { try { @@ -100,7 +102,7 @@ const SplashScreen = () => { }; startSplash(); - }, [navigation, fadeAnim, dispatch]); + }, [navigation, fadeAnim, dispatch, ]); return ( diff --git a/src/screens/home/Home.tsx b/src/screens/home/Home.tsx index 9abb27d..231f8b7 100644 --- a/src/screens/home/Home.tsx +++ b/src/screens/home/Home.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { StyleSheet, Text, @@ -13,27 +13,77 @@ import UserProfileStatus from "./UserProfileStatus"; import colors from "../../components/colors"; import GroupsTabs from "./GroupsTabs"; import { useSelector } from "react-redux"; -import { useNavigation } from "@react-navigation/native"; +import { useFocusEffect, useNavigation, useRoute } from "@react-navigation/native"; import UserHome from "./user-home/UserHome"; +import { PROFILE_URL } from "../../utils/ApiUrls"; +import *as Keychain from "react-native-keychain" // Enable animation for Android if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) { UIManager.setLayoutAnimationEnabledExperimental(true); } + +type UserProfile = { + id: string; + userId: number; + firstName: string | null; + lastName: string | null; + imageBlob: string | null; + role?: string; +} const Home = () => { const user = useSelector((state: any) => state.user); const navigation = useNavigation(); - console.log("redux user data in Home:", user); + const route = useRoute(); + + console.log("route asf : ", route?.params) + const [apiUser, setApiUser] = useState(null) + + const fetchUserProfile = async () => { + try { + const credentials = await Keychain.getGenericPassword(); + if (!credentials) { + return; + } + const token = credentials.password; + const response = await fetch( + PROFILE_URL, + { + method: "GET", + headers: { + Accept: "*/*", + Authorization: `Bearer ${token}`, + }, + } + ); + + const result = await response.json(); + console.log("👤 Profile Response:", result); + if (result.status === "success") { + setApiUser(result.data); + + } + } catch (error) { + console.error("❌ Error fetching profile:", error); + } + }; + + useEffect(() => { + if (route.params) { + fetchUserProfile(); + } + }, [route.params]); + return ( {/* Header */} - Hello, {user.firstName} {user.lastName}👋 + Hello, {user.firstName || apiUser?.firstName} {user.lastName || apiUser?.lastName}👋 navigation.navigate("Profile" as never)}> - + @@ -41,8 +91,8 @@ const Home = () => { {/* Status */} - {user?.role === "USER" ? : - } + {user?.role === "USER" || apiUser?.role === "USER" ? : + } ); }; diff --git a/src/screens/home/RequestList.tsx b/src/screens/home/RequestList.tsx index 5ec2f47..4dba2b6 100644 --- a/src/screens/home/RequestList.tsx +++ b/src/screens/home/RequestList.tsx @@ -13,6 +13,7 @@ import { import { Ionicons } from '@react-native-vector-icons/ionicons'; import colors from '../../components/colors'; import DeclineModal from './DeclineModal'; +import * as keychain from 'react-native-keychain' // Define TypeScript interfaces interface GroupRequest { @@ -40,11 +41,18 @@ const RequestList: React.FC = () => { const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [processingRequest, setProcessingRequest] = useState(null); - const [showDeclineModal, setShowDeclineModal] = useState<{modal: boolean, requestId: string | null}>({modal: false, requestId: null}); + const [showDeclineModal, setShowDeclineModal] = useState<{ modal: boolean, requestId: string | null }>({ modal: false, requestId: null }); const [selectedRequestId, setSelectedRequestId] = useState(null); // Fetch data from API const fetchRequests = async (): Promise => { + + const credentials = await keychain.getGenericPassword(); + if (!credentials) { + Alert.alert("Error", "You are not logged in"); + return; + } + const token = credentials.password; try { setRefreshing(true); setError(null); @@ -55,7 +63,7 @@ const RequestList: React.FC = () => { method: "GET", headers: { accept: "*/*", - Authorization: "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2MjY1MjI5MzcxIiwidXNlcklkIjoiMTAwMDAxIiwiaWF0IjoxNzU4MDkxMjEzLCJleHAiOjE3NTgxNzc2MTN9.T3BvU7nl8fnjwwVobIWRmSFFxQzOjtyIct1Ba0i9lIU" + Authorization: `Bearer ${token}` } } ); @@ -156,7 +164,7 @@ const RequestList: React.FC = () => { }; const handleDeclineConfirm = async (requestedId: string | null, reason: string) => { - setShowDeclineModal({modal: false, requestId: null}); + setShowDeclineModal({ modal: false, requestId: null }); try { setProcessingRequest(requestedId); @@ -197,7 +205,7 @@ const RequestList: React.FC = () => { }// call your API }; - + const onRefresh = (): void => { fetchRequests(); @@ -332,7 +340,7 @@ const RequestList: React.FC = () => { setShowDeclineModal({modal:true, requestId: request.requestId})} + onPress={() => setShowDeclineModal({ modal: true, requestId: request.requestId })} disabled={processingRequest === request.requestId} > {processingRequest === request.requestId ? ( @@ -361,7 +369,7 @@ const RequestList: React.FC = () => { {showDeclineModal.modal && ( setShowDeclineModal({modal: false, requestId: null})} + onClose={() => setShowDeclineModal({ modal: false, requestId: null })} onSubmit={(reason) => handleDeclineConfirm(showDeclineModal.requestId, reason)} /> )} diff --git a/src/screens/home/UserProfileStatus.tsx b/src/screens/home/UserProfileStatus.tsx index 3da10fd..b26987e 100644 --- a/src/screens/home/UserProfileStatus.tsx +++ b/src/screens/home/UserProfileStatus.tsx @@ -10,7 +10,7 @@ import { } from "react-native"; import * as Keychain from "react-native-keychain"; import { useFocusEffect, useNavigation } from "@react-navigation/native"; -import { BASE_URL, PUT_USER_STATUS } from "../../utils/ApiUrls"; +import { PUT_USER_STATUS } from "../../utils/ApiUrls"; import colors from "../../components/colors"; export type ApiResponse = { diff --git a/src/screens/home/user-home/GroupDetails.tsx b/src/screens/home/user-home/GroupDetails.tsx index c25acf3..4082231 100644 --- a/src/screens/home/user-home/GroupDetails.tsx +++ b/src/screens/home/user-home/GroupDetails.tsx @@ -11,6 +11,7 @@ import { useRoute } from "@react-navigation/native"; import AppView from "../../../components/AppView"; import colors from "../../../components/colors"; import Ionicons from "@react-native-vector-icons/ionicons"; +import Header from "../../../components/common/Header"; const GroupDetails = () => { const route = useRoute(); @@ -88,7 +89,8 @@ const GroupDetails = () => { return ( {/* Group Title */} - {group.name} +
+ {/* {group.name} */} {group.description} {/* Progress Bar */} diff --git a/src/screens/home/user-home/UserHome.tsx b/src/screens/home/user-home/UserHome.tsx index c0acf8a..5c4ad2f 100644 --- a/src/screens/home/user-home/UserHome.tsx +++ b/src/screens/home/user-home/UserHome.tsx @@ -1,65 +1,108 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { StyleSheet, Text, View, FlatList, TouchableOpacity, + Alert, } from "react-native"; import { useNavigation } from "@react-navigation/native"; import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; -import type { RootStackParamList } from "../../../navigation/AppNavigator"; // adjust path +import type { RootStackParamList } from "../../../navigation/AppNavigator"; import colors from "../../../components/colors"; -import Ionicons from "@react-native-vector-icons/ionicons"; +import { Ionicons } from "@react-native-vector-icons/ionicons"; +import { GET_JOINED_GROUP_URL } from "../../../utils/ApiUrls"; +import * as Keychain from "react-native-keychain"; -type NavigationProp = NativeStackNavigationProp; +type NavigationProp = NativeStackNavigationProp< + RootStackParamList, + "GroupDetails" +>; type Group = { id: string; - name: string; - members: number; - collected: number; - goal: number; + groupName: string; + description: string; + currentParticipants: number; + maxParticipants: number; + contributionAmount: number; + status: string; }; -const groups: Group[] = [ - { id: "1", name: "Wedding Fund", members: 25, collected: 12000, goal: 20000 }, - { id: "2", name: "Birthday Gift", members: 10, collected: 4500, goal: 6000 }, - { id: "3", name: "Community Event", members: 50, collected: 22000, goal: 30000 }, -]; - const UserHome = () => { const navigation = useNavigation(); + const [groups, setGroups] = useState([]); + const [loading, setLoading] = useState(false) const totalGroups = groups.length; - const totalCollected = groups.reduce((sum, g) => sum + g.collected, 0); + const totalContribution = groups.reduce( + (sum, g) => sum + g.contributionAmount, + 0 + ); + + const handleGroup = async () => { + try { + setLoading(true) + const credentials = await Keychain.getGenericPassword(); + if (!credentials) { + Alert.alert("Error", "You are not logged in"); + return; + } + const token = credentials.password; + + const response = await fetch(GET_JOINED_GROUP_URL, { + method: "GET", + headers: { + Accept: "*/*", + Authorization: `Bearer ${token}`, + }, + }); + + const result = await response.json(); + if (result.status === "success") { + console.log("GET_JOINED_GROUP_URL res : ", result); + setGroups(result.data); // save API data + setLoading(false) + } else { + Alert.alert("Error", "Failed to fetch groups"); + } + } catch (error) { + console.error("❌ Error fetching groups:", error); + Alert.alert("Error", "Could not connect to server"); + }finally{ + setLoading(false) + } + }; + + useEffect(() => { + handleGroup(); + }, []); const renderItem = ({ item }: { item: Group }) => { - const progress = Math.min(item.collected / item.goal, 1) * 100; - return ( navigation.navigate("GroupDetails", { groupId: item.id })} > - {item.name} + {item.groupName} + {item.description} + - {item.members} members - - - {/* Progress Bar */} - - + + {item.currentParticipants}/{item.maxParticipants} members + - ₹{item.collected} / ₹{item.goal} + Contribution: ₹{item.contributionAmount} + Status: {item.status} ); }; @@ -67,14 +110,14 @@ const UserHome = () => { return ( {/* Stats */} - + {totalGroups} Groups Joined - ₹{totalCollected} - Total Contributed + ₹{totalContribution} + Total Contribution @@ -93,16 +136,13 @@ const UserHome = () => { export default UserHome; const styles = StyleSheet.create({ - - statsRow: { flexDirection: "row", - gap: 10 + gap: 10, }, statBox: { flex: 1, backgroundColor: "#fff", - // marginHorizontal: 6, padding: 16, borderRadius: 12, alignItems: "center", @@ -143,26 +183,27 @@ const styles = StyleSheet.create({ fontSize: 16, fontWeight: "600", color: colors.Home, - marginBottom: 6, + marginBottom: 4, + }, + description: { + fontSize: 13, + color: "#475569", + marginBottom: 8, }, detail: { fontSize: 13, color: "#475569", marginBottom: 8, }, - progressBar: { - height: 6, - borderRadius: 3, - backgroundColor: "#e2e8f0", - marginBottom: 6, - overflow: "hidden", - }, - progressFill: { - backgroundColor: colors.primary, - height: "100%", - }, amount: { fontSize: 13, color: "#64748b", + marginTop: 4, + }, + status: { + fontSize: 12, + fontWeight: "500", + color: colors.primary, + marginTop: 2, }, }); diff --git a/src/screens/profile/Profile.tsx b/src/screens/profile/Profile.tsx index bdbbace..7dd5b3b 100644 --- a/src/screens/profile/Profile.tsx +++ b/src/screens/profile/Profile.tsx @@ -299,9 +299,6 @@ const Profile = () => { ); }; - console.log("statusData?.role :", statusData?.role); - console.log("statusData?.organizerStatus :", statusData?.organizerStatus); - useFocusEffect( useCallback(() => { userStatus(); diff --git a/src/utils/ApiUrls.tsx b/src/utils/ApiUrls.tsx index f210530..ac4d6b3 100644 --- a/src/utils/ApiUrls.tsx +++ b/src/utils/ApiUrls.tsx @@ -14,4 +14,4 @@ export const DECLINE_REQUEST_URL = `${BASE_URL}fund/user/my/groups` export const GET_JOINED_GROUP_URL = `${BASE_URL}fund/my/join/groups` export const PUT_USER_PROFILE_IMG = `${BASE_URL}user/update/userImg` export const PUT_USER_PROFILE = `${BASE_URL}user/profile/update` -export const PUT_USER_STATUS = `${BASE_URL}user/profile/status` \ No newline at end of file +export const PUT_USER_STATUS = `${BASE_URL}user/profile/status`