From bcd41f81e646861a279db0323c51366dba775666 Mon Sep 17 00:00:00 2001 From: Jaanu423 Date: Mon, 22 Sep 2025 18:50:06 +0530 Subject: [PATCH 1/2] api integration for joined user --- src/components/common/Header.tsx | 31 +++-- src/screens/GroupScreen.tsx | 35 +++++- src/screens/LoginScreen.tsx | 23 +--- src/screens/OtpVarificationScreen.tsx | 7 +- src/screens/SplashScreen.tsx | 4 +- src/screens/home/Home.tsx | 66 ++++++++-- src/screens/home/RequestList.tsx | 20 ++- src/screens/home/UserProfileStatus.tsx | 2 +- src/screens/home/user-home/GroupDetails.tsx | 4 +- src/screens/home/user-home/UserHome.tsx | 129 +++++++++++++------- src/screens/profile/Profile.tsx | 3 - src/utils/ApiUrls.tsx | 2 +- 12 files changed, 213 insertions(+), 113 deletions(-) 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` From 64282654f0d8ff3427b6239973b562dc266ab601 Mon Sep 17 00:00:00 2001 From: Jaanu423 Date: Tue, 23 Sep 2025 13:25:09 +0530 Subject: [PATCH 2/2] manage user credancial --- src/components/JoinedGroupCard.tsx | 5 +- src/components/RequestCard.tsx | 11 +- src/components/UserRequested.tsx | 154 ++++++++++++++++++++ src/screens/GroupScreen.tsx | 16 +- src/screens/OtpVarificationScreen.tsx | 49 ++++--- src/screens/home/Home.tsx | 46 ++++-- src/screens/home/UserProfileStatus.tsx | 2 +- src/screens/management/ManagementScreen.tsx | 87 +++++------ src/types.ts | 11 ++ src/utils/ApiUrls.tsx | 2 + src/utils/HelperFunction.tsx | 21 ++- 11 files changed, 299 insertions(+), 105 deletions(-) create mode 100644 src/components/UserRequested.tsx diff --git a/src/components/JoinedGroupCard.tsx b/src/components/JoinedGroupCard.tsx index e2f9a52..dcb9768 100644 --- a/src/components/JoinedGroupCard.tsx +++ b/src/components/JoinedGroupCard.tsx @@ -2,6 +2,7 @@ import React, { FC } from 'react'; import { View, Text, StyleSheet } from 'react-native'; +import colors from './colors'; // Update the type to match the full data structure interface JoinedGroup { @@ -151,7 +152,7 @@ const styles = StyleSheet.create({ paddingVertical: 16, borderTopWidth: 1, borderBottomWidth: 1, - borderColor: '#F3F4F6', + borderColor: colors.pastelOrange, marginBottom: 16, }, infoItem: { @@ -193,7 +194,7 @@ const styles = StyleSheet.create({ }, progressBarFill: { height: '100%', - backgroundColor: '#4F46E5', + backgroundColor: colors.Settings, borderRadius: 4, }, footer: { diff --git a/src/components/RequestCard.tsx b/src/components/RequestCard.tsx index b0ac2fd..ff8a7a2 100644 --- a/src/components/RequestCard.tsx +++ b/src/components/RequestCard.tsx @@ -50,16 +50,7 @@ export const RequestCard: FC = ({ item, onApprove, onDecline } ); }; -// Assuming a colors object like this for context: -// const colors = { -// white: '#FFFFFF', -// textPrimary: '#212121', -// textSecondary: '#757575', -// border: '#EEEEEE', -// danger: '#C62828', -// lightDanger: '#FFEBEE', -// pastelOrange: '#FFDAB9', // The requested color -// }; + const styles = StyleSheet.create({ card: { diff --git a/src/components/UserRequested.tsx b/src/components/UserRequested.tsx new file mode 100644 index 0000000..2b4999a --- /dev/null +++ b/src/components/UserRequested.tsx @@ -0,0 +1,154 @@ +// src/components/UserRequested.tsx + +import React, { FC } from "react"; +import { View, Text, StyleSheet } from "react-native"; +import {Ionicons} from "@react-native-vector-icons/ionicons"; +import colors from "./colors"; +import { UserJoinRequest } from "../types"; +import { formatDate } from "../utils/HelperFunction"; + +interface UserRequestedProps { + item: UserJoinRequest; +} + +const getStatusStyle = (status: UserJoinRequest["status"]) => { + switch (status) { + case "APPROVED": + return { + backgroundColor: "#D1FAE5", // light green + textColor: "#065F46", // dark green + label: "Approved", + icon: "checkmark-circle-outline", + + }; + case "PENDING": + return { + backgroundColor: "#FEF3C7", // light yellow + textColor: "#92400E", // dark yellow/brown + label: "Pending", + icon: "time-outline", + }; + case "REJECT": + return { + backgroundColor: "#E5E7EB", // light gray + textColor: "#4B5563", // dark gray + label: "Reject", + icon: "close-circle-outline", + }; + default: + return { + backgroundColor: "#E5E7EB", + textColor: "#4B5563", + label: "Unknown", + icon: "help-outline", + }; + } +}; + +export const UserRequested: FC = ({ item }) => { + const { backgroundColor, textColor, label, icon } = getStatusStyle(item.status); + + return ( + + {/* Top row: User info + Status */} + + + + {item.groupName} + + + {" "} + {formatDate(item.requestDate)} + + + + {/* Status Badge */} + + + {label} + + + + {/* Group Info */} + + + Group Created By :{" "} + + {item.createdBy} + + + {item.reason ? ( + + Reason: {item.reason} + + ) : null} + + + ); +}; + +const styles = StyleSheet.create({ + card: { + backgroundColor: colors.white, + borderRadius: 16, + marginVertical: 8, + elevation: 3, + shadowColor: "#000", + shadowOpacity: 0.1, + shadowRadius: 6, + shadowOffset: { width: 0, height: 3 }, + padding: 12, + }, + topRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "flex-start", + borderBottomWidth: 1, + borderBottomColor: colors.Settings, + paddingBottom: 8, + }, + userInfoSection: { + flex: 1, + paddingRight: 8, + }, + userName: { + // fontSize: 16, + fontWeight: "bold", + color: colors.textDark, + }, + userMeta: { + fontSize: 13, + color: colors.textLight, + marginTop: 2, + }, + groupInfoSection: { + paddingVertical: 12, + }, + requestText: { + // fontSize: 15, + color: colors.textDark, + lineHeight: 20, + }, + groupName: { + fontWeight: "600", + color: colors.Settings, + fontSize:18 + }, + reasonText: { + fontSize: 13, + color: colors.textLight, + marginTop: 4, + }, + statusBadge: { + flexDirection: "row", + alignItems: "center", + borderRadius: 12, + paddingHorizontal: 8, + paddingVertical: 4, + }, + statusText: { + fontSize: 13, + fontWeight: "600", + marginLeft: 4, + }, +}); diff --git a/src/screens/GroupScreen.tsx b/src/screens/GroupScreen.tsx index e46dd4d..34036bc 100644 --- a/src/screens/GroupScreen.tsx +++ b/src/screens/GroupScreen.tsx @@ -150,17 +150,17 @@ export default function GroupScreen() { const handleJoinGroup = async (groupId: string, userId: string) => { console.log("Joining group:", groupId, "User:", userId); try { - const url = `${JOIN_GROUP_URL}/groups/${groupId}/join/0`; + const url = `${JOIN_GROUP_URL}groups/${groupId}/join/10`; console.log("Joining group URL:", url); - const res = await apiCall(url, "POST") as { statusCode?: number; message?: string }; + const res = await apiCall(url, "POST"); 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."); @@ -264,7 +264,7 @@ export default function GroupScreen() { refreshControl={ } - ListFooterComponent={} + // ListFooterComponent={} /> ); }; diff --git a/src/screens/OtpVarificationScreen.tsx b/src/screens/OtpVarificationScreen.tsx index 1b07871..0bd381c 100644 --- a/src/screens/OtpVarificationScreen.tsx +++ b/src/screens/OtpVarificationScreen.tsx @@ -77,26 +77,39 @@ export default function OtpVerificationScreen() { const onSubmit = async (otp: string, mobile: string) => { - try { - const payload = { mobile, otp }; - const res = await axios.post( - VARIFY_OTP_URL, - payload, - { headers: { "Content-Type": "application/json", Accept: "*/*" } } - ); - - console.log("✅ OTP Verified:", res.data); - if (res.data.statusCode === 200) { - saveToken(res.data.data); - fetchUserProfile() - navigation.navigate("Home", { refreshProfile: true }); - ToastAndroid.show("OTP Verified Successfully!", ToastAndroid.SHORT); + try { + const payload = { mobile, otp }; + const res = await axios.post( + VARIFY_OTP_URL, + payload, + { + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, } - } catch (err: any) { - console.error("❌ OTP Verification Error:", err?.response?.data || err.message); - ToastAndroid.show(err.message || "Invalid OTP. Please try again.", ToastAndroid.SHORT); + ); + + console.log("✅ OTP Verified:", res.data); + + if (res.data?.statusCode === 200) { + const { jwt, role } = res.data?.data || {}; + + saveToken(jwt); + + navigation.navigate("Home"); + + ToastAndroid.show("OTP Verified Successfully!", ToastAndroid.SHORT); } - }; + } catch (err: any) { + console.error("❌ OTP Verification Error:", err?.response?.data || err.message); + ToastAndroid.show( + err?.response?.data?.message || "Invalid OTP. Please try again.", + ToastAndroid.SHORT + ); + } +}; + const resendOtp = async () => { try { diff --git a/src/screens/home/Home.tsx b/src/screens/home/Home.tsx index 231f8b7..551c946 100644 --- a/src/screens/home/Home.tsx +++ b/src/screens/home/Home.tsx @@ -7,21 +7,27 @@ import { UIManager, Platform, Image, + Alert, } from "react-native"; import AppView from "../../components/AppView"; import UserProfileStatus from "./UserProfileStatus"; import colors from "../../components/colors"; import GroupsTabs from "./GroupsTabs"; -import { useSelector } from "react-redux"; -import { useFocusEffect, useNavigation, useRoute } from "@react-navigation/native"; +import { useDispatch, useSelector } from "react-redux"; +import { RouteProp, 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" +import { setUserData } from "../../redux/userSlice"; // Enable animation for Android if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) { UIManager.setLayoutAnimationEnabledExperimental(true); } +type RootStackParamList = { + Home: { refreshProfile: boolean; role: string }; +}; + type UserProfile = { id: string; userId: number; @@ -33,9 +39,8 @@ type UserProfile = { const Home = () => { const user = useSelector((state: any) => state.user); const navigation = useNavigation(); - const route = useRoute(); + const dispatch = useDispatch() - console.log("route asf : ", route?.params) const [apiUser, setApiUser] = useState(null) const fetchUserProfile = async () => { @@ -60,7 +65,29 @@ const Home = () => { console.log("👤 Profile Response:", result); if (result.status === "success") { setApiUser(result.data); - + 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, + filePath: result.data.filePath, + imageBlob: result.data.imageBlob, + role: result.data.role, + organizerId: result.data.organizerId, + organizerStatus: result.data.organizerStatus, + profileStatus: true, + }) + ); } } catch (error) { console.error("❌ Error fetching profile:", error); @@ -68,10 +95,8 @@ const Home = () => { }; useEffect(() => { - if (route.params) { - fetchUserProfile(); - } - }, [route.params]); + fetchUserProfile(); + }, []); return ( @@ -91,6 +116,7 @@ const Home = () => { {/* Status */} + {/* */} {user?.role === "USER" || apiUser?.role === "USER" ? : } @@ -110,6 +136,4 @@ const styles = StyleSheet.create({ marginBottom: 12, }, greeting: { fontSize: 22, fontWeight: "600", color: colors.Home }, - - }); \ No newline at end of file diff --git a/src/screens/home/UserProfileStatus.tsx b/src/screens/home/UserProfileStatus.tsx index b26987e..8cc2f58 100644 --- a/src/screens/home/UserProfileStatus.tsx +++ b/src/screens/home/UserProfileStatus.tsx @@ -54,7 +54,7 @@ const UserProfileStatus: React.FC = () => { const json: ApiResponse = await response.json(); - console.log("👤 Profile Status Response:", json); + console.log("Profile Status :", json); if (json.status === "success") { setStatusData(json.data); diff --git a/src/screens/management/ManagementScreen.tsx b/src/screens/management/ManagementScreen.tsx index f3421ca..9e2f5df 100644 --- a/src/screens/management/ManagementScreen.tsx +++ b/src/screens/management/ManagementScreen.tsx @@ -14,86 +14,45 @@ import { import { useNavigation, useFocusEffect } from "@react-navigation/native"; import { StackNavigationProp } from "@react-navigation/stack"; import { RootStackParamList } from "../../navigation/AppNavigator"; -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 { APPROVE_REQUEST_URL, BASE_URL, DECLINE_REQUEST_URL, GET_JOINED_GROUP_URL, JOIN_GROUP_URL, MY_GROUPS_URL, REQUESTS_URL, USER_REQUEST } from "../../utils/ApiUrls"; import colors from "../../components/colors"; import AppView from "../../components/AppView"; import { apiCall } from "../../utils/ApiHelper"; import * as Keychain from "react-native-keychain"; -import { MyGroup, JoinedGroup, JoinRequest, ApiResponse } from '../../types'; +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 Ionicons from "@react-native-vector-icons/ionicons"; +import { UserRequested } from "../../components/UserRequested"; type GroupManagementNavProp = StackNavigationProp; type ActiveTab = "My Groups" | "Joined Groups" | "Requests" | "Your Requests"; const ManagementScreen: FC = () => { - const user = useSelector((state:any) => state.user) - const [activeTab, setActiveTab] = useState("My Groups"); + const userRole = useSelector((state: any) => state.user) + const [activeTab, setActiveTab] = useState(userRole.role === "USER" ? "Joined Groups" : "My Groups"); const navigation = useNavigation(); const [myGroups, setMyGroups] = useState([]); const [joinedGroups, setJoinedGroups] = useState([]); const [requests, setRequests] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [userRole,setUserRole] = useState(null) - const userStatus = async () => { - try { - const credentials = await Keychain.getGenericPassword(); - if (!credentials) { - Alert.alert("Error", "You are not logged in"); - return; - } - const token = credentials.password; - - setLoading(true); - setError(null); - const response = await fetch(`${BASE_URL}user/profile/status`, { - method: "GET", - headers: { - Accept: "*/*", - Authorization: `Bearer ${token}`, - }, - }); - - const json = await response.json(); - - console.log("👤 Profile Status Response:", json.data.role); - if (json.status === "success") { - setUserRole(json.data.role); - - if (json.data.profileStatus === false) { - - } - } else { - setError(json.message || "Failed to fetch status"); - } - } catch (err) { - setError("Something went wrong. Please try again."); - } finally { - setLoading(false); - } - }; - - useFocusEffect( - useCallback(() => { - userStatus(); - }, []) - ); + const [userRequest, setUserRequest] = useState([]) + const fetchData = useCallback(async () => { if (loading) return; setLoading(true); setError(null); try { - console.log(" abin user : ", userRole) - if (userRole === "ORGANIZER") { + console.log(" abin user : ", userRole) + if (userRole.role === "ORGANIZER") { // Fetch all data concurrently const [myGroupsRes, joinedGroupsRes, requestsRes] = await Promise.all([ apiCall(MY_GROUPS_URL, "GET") as Promise>, - apiCall(GET_JOINED_GROUP_URL, "GET") as Promise>, // Assuming this is also needed + apiCall(GET_JOINED_GROUP_URL, "GET") as Promise>, apiCall(REQUESTS_URL, "GET") as Promise>, ]); @@ -105,10 +64,14 @@ const ManagementScreen: FC = () => { setRequests(requestsRes.data || []); } else { // Fetch only joined groups for non-organizers - const [joinedGroupsRes] = await Promise.all([ + const [joinedGroupsRes, userRequest] = await Promise.all([ apiCall(GET_JOINED_GROUP_URL, "GET") as Promise>, // Assuming this is also needed + apiCall(USER_REQUEST, "GET") as Promise>, // Assuming this is also needed ]); - setJoinedGroups(joinedGroupsRes.data || []); + setJoinedGroups(joinedGroupsRes.data || []); + setUserRequest(userRequest?.data || []); + console.log("joinedGroupsRes data : ", joinedGroupsRes) + console.log("user request data : ", userRequest) } } catch (e) { console.error("Fetch Data Error:", e); @@ -197,12 +160,28 @@ const ManagementScreen: FC = () => { contentContainerStyle={{ paddingBottom: 100 }} /> ); + case "Your Requests": + if (loading && requests.length === 0) { + return ; + } + return ( + ) => } + keyExtractor={(item) => item.requestId} + ListEmptyComponent={No pending join requests.} + onRefresh={fetchData} + refreshing={loading} // This handles the "continuing loading" on pull-to-refresh + contentContainerStyle={{ paddingBottom: 100 }} + /> + ); default: return null; } }; - const tabs: ActiveTab[] = userRole === 'ORGANIZER' ? ["My Groups", "Joined Groups", "Requests"] : ["Joined Groups", "Your Requests"]; + const tabs: ActiveTab[] = userRole.role === 'ORGANIZER' ? ["My Groups", "Joined Groups", "Requests"] : ["Joined Groups", "Your Requests"]; + console.log("user request : ", userRequest) return ( diff --git a/src/types.ts b/src/types.ts index 5ed5a76..fe88e58 100644 --- a/src/types.ts +++ b/src/types.ts @@ -37,6 +37,17 @@ export interface JoinRequest { userAvatar?: string; } +export interface UserJoinRequest { + createdBy: string; + groupId: string; + groupName: string; + reason: string | null; + requestDate: string; + requestId: any; + status: string; +} + + // src/types.ts diff --git a/src/utils/ApiUrls.tsx b/src/utils/ApiUrls.tsx index ac4d6b3..37b43a7 100644 --- a/src/utils/ApiUrls.tsx +++ b/src/utils/ApiUrls.tsx @@ -15,3 +15,5 @@ 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` +export const USER_REQUEST = `${BASE_URL}fund/my-requests` +export const GROUP_ALL_MEMBER = `${BASE_URL}fund/group/all/member` diff --git a/src/utils/HelperFunction.tsx b/src/utils/HelperFunction.tsx index 1588b17..83c59b2 100644 --- a/src/utils/HelperFunction.tsx +++ b/src/utils/HelperFunction.tsx @@ -58,4 +58,23 @@ export const fetchUserProfile = async () => { console.error("❌ Error fetching profile:", error); Alert.alert("Error", "Could not connect to server"); } -}; \ No newline at end of file +}; + +// src/utils/dateFormatter.ts + +export const formatDate = (dateString: string): string => { + const date = new Date(dateString); + + if (isNaN(date.getTime())) return "Invalid Date"; + + const options: Intl.DateTimeFormatOptions = { + day: "2-digit", + month: "short", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + hour12: true, + }; + + return new Intl.DateTimeFormat("en-GB", options).format(date); +};