implement dashboard api
This commit is contained in:
@@ -1,322 +1,4 @@
|
|||||||
// import React, { useCallback, useEffect, useState } from "react";
|
import React, { use, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
// import {
|
|
||||||
// StyleSheet,
|
|
||||||
// Text,
|
|
||||||
// View,
|
|
||||||
// TouchableOpacity,
|
|
||||||
// Modal,
|
|
||||||
// ToastAndroid,
|
|
||||||
// Image,
|
|
||||||
// Animated,
|
|
||||||
// } from "react-native";
|
|
||||||
// import * as Keychain from "react-native-keychain";
|
|
||||||
// import { useFocusEffect, useNavigation } from "@react-navigation/native";
|
|
||||||
// import { PUT_USER_STATUS } from "../../utils/ApiUrls";
|
|
||||||
// import colors from "../../components/colors";
|
|
||||||
// import LinearGradient from "react-native-linear-gradient";
|
|
||||||
// import {Ionicons} from "@react-native-vector-icons/ionicons";
|
|
||||||
|
|
||||||
// export type ApiResponse = {
|
|
||||||
// message: string;
|
|
||||||
// status: string;
|
|
||||||
// statusCode: number;
|
|
||||||
// data: {
|
|
||||||
// role: string;
|
|
||||||
// organizerStatus: string;
|
|
||||||
// profileStatus: boolean;
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// type UserProfileStatusProps = {
|
|
||||||
// profileImage?: { uri: string };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const UserProfileStatus: React.FC<UserProfileStatusProps> = ({ profileImage }) => {
|
|
||||||
// const navigation = useNavigation();
|
|
||||||
// const [loading, setLoading] = useState(true);
|
|
||||||
// const [statusData, setStatusData] = useState<ApiResponse["data"] | null>(null);
|
|
||||||
// const [showModal, setShowModal] = useState(false);
|
|
||||||
// const [intervalId, setIntervalId] = useState<null | number>(null);
|
|
||||||
// const fadeAnim = useState(new Animated.Value(0))[0];
|
|
||||||
|
|
||||||
// const userStatus = async () => {
|
|
||||||
// try {
|
|
||||||
// const credentials = await Keychain.getGenericPassword();
|
|
||||||
// if (!credentials) {
|
|
||||||
// ToastAndroid.show("You are not logged in", ToastAndroid.SHORT);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// const token = credentials.password;
|
|
||||||
|
|
||||||
// setLoading(true);
|
|
||||||
// const response = await fetch(PUT_USER_STATUS, {
|
|
||||||
// method: "GET",
|
|
||||||
// headers: {
|
|
||||||
// Accept: "*/*",
|
|
||||||
// Authorization: `Bearer ${token}`,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const json: ApiResponse = await response.json();
|
|
||||||
// if (json.status === "success") {
|
|
||||||
// setStatusData(json.data);
|
|
||||||
// if (json.data.profileStatus === false) {
|
|
||||||
// setShowModal(true);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// ToastAndroid.show("Failed to fetch status", ToastAndroid.SHORT);
|
|
||||||
// }
|
|
||||||
// } catch {
|
|
||||||
// ToastAndroid.show("Something went wrong", ToastAndroid.SHORT);
|
|
||||||
// } finally {
|
|
||||||
// setLoading(false);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// useFocusEffect(
|
|
||||||
// useCallback(() => {
|
|
||||||
// userStatus();
|
|
||||||
// }, [])
|
|
||||||
// );
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// Animated.timing(fadeAnim, {
|
|
||||||
// toValue: 1,
|
|
||||||
// duration: 600,
|
|
||||||
// useNativeDriver: true,
|
|
||||||
// }).start();
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
// const handleGoToProfile = () => {
|
|
||||||
// if (intervalId) {
|
|
||||||
// clearInterval(intervalId);
|
|
||||||
// setIntervalId(null);
|
|
||||||
// }
|
|
||||||
// setShowModal(false);
|
|
||||||
// navigation.navigate("Profile" as never);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handlePress = (label: string) => {
|
|
||||||
// ToastAndroid.show(`${label} clicked`, ToastAndroid.SHORT);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <Animated.View style={[styles.container, { opacity: fadeAnim }]}>
|
|
||||||
// <LinearGradient
|
|
||||||
// colors={[colors.primary, colors.pastelPink]}
|
|
||||||
// start={{ x: 0, y: 0 }}
|
|
||||||
// end={{ x: 1, y: 1 }}
|
|
||||||
// style={styles.card}
|
|
||||||
// >
|
|
||||||
// {/* LEFT SECTION */}
|
|
||||||
// <View style={styles.leftSection}>
|
|
||||||
// <Text style={styles.header}>Profile Overview</Text>
|
|
||||||
|
|
||||||
// <View style={styles.infoRow}>
|
|
||||||
// <View>
|
|
||||||
// {[
|
|
||||||
// "Role",
|
|
||||||
// "Total Active Groups",
|
|
||||||
// "Total Joined Groups",
|
|
||||||
// "Total Created Groups",
|
|
||||||
// "Total Due Payment",
|
|
||||||
// "Total Completed Groups",
|
|
||||||
// ].map((label, i) => (
|
|
||||||
// <TouchableOpacity
|
|
||||||
// key={i}
|
|
||||||
// style={styles.infoLabelBox}
|
|
||||||
// onPress={() => handlePress(label)}
|
|
||||||
// >
|
|
||||||
// <Text style={styles.infoLabel}>{label}</Text>
|
|
||||||
// </TouchableOpacity>
|
|
||||||
// ))}
|
|
||||||
// </View>
|
|
||||||
|
|
||||||
// <View>
|
|
||||||
// {[
|
|
||||||
// statusData?.role || "-",
|
|
||||||
// "5",
|
|
||||||
// "3",
|
|
||||||
// "2",
|
|
||||||
// "₹1500",
|
|
||||||
// "4",
|
|
||||||
// ].map((value, i) => (
|
|
||||||
// <TouchableOpacity
|
|
||||||
// key={i}
|
|
||||||
// style={styles.infoValueBox}
|
|
||||||
// onPress={() => handlePress(value)}
|
|
||||||
// >
|
|
||||||
// <Text style={styles.infoValue}>{value}</Text>
|
|
||||||
// </TouchableOpacity>
|
|
||||||
// ))}
|
|
||||||
// </View>
|
|
||||||
// </View>
|
|
||||||
|
|
||||||
// <View style={styles.statusBox}>
|
|
||||||
// <Text style={styles.statusText}>
|
|
||||||
// Organizer: {statusData?.organizerStatus || "-"}
|
|
||||||
// </Text>
|
|
||||||
// <Text style={styles.statusText}>
|
|
||||||
// Profile Completed:{" "}
|
|
||||||
// {statusData?.profileStatus ? "Yes ✅" : "No ❌"}
|
|
||||||
// </Text>
|
|
||||||
// </View>
|
|
||||||
// </View>
|
|
||||||
|
|
||||||
// {/* RIGHT SECTION - IMAGE */}
|
|
||||||
// {/* <TouchableOpacity
|
|
||||||
// onPress={() => navigation.navigate("Profile" as never)}
|
|
||||||
// activeOpacity={0.8}
|
|
||||||
// >
|
|
||||||
// {profileImage ? (
|
|
||||||
// <Image source={{ uri: profileImage.uri }} style={styles.avatar} />
|
|
||||||
// ) : (
|
|
||||||
// <Ionicons name="person-circle-outline" size={80} color="#fff" />
|
|
||||||
// )}
|
|
||||||
// </TouchableOpacity> */}
|
|
||||||
// </LinearGradient>
|
|
||||||
|
|
||||||
// {/* Modal */}
|
|
||||||
// {showModal && (
|
|
||||||
// <Modal visible transparent animationType="fade">
|
|
||||||
// <View style={styles.modalOverlay}>
|
|
||||||
// <View style={styles.modalBox}>
|
|
||||||
// <Ionicons name="alert-circle" size={50} color={colors.primary} />
|
|
||||||
// <Text style={styles.modalTitle}>Complete Your Profile</Text>
|
|
||||||
// <Text style={styles.modalText}>
|
|
||||||
// Your profile is incomplete. Please update your details to continue.
|
|
||||||
// </Text>
|
|
||||||
|
|
||||||
// <TouchableOpacity style={styles.button} onPress={handleGoToProfile}>
|
|
||||||
// <Text style={styles.buttonText}>Go to Profile</Text>
|
|
||||||
// </TouchableOpacity>
|
|
||||||
|
|
||||||
// <TouchableOpacity
|
|
||||||
// style={[styles.button, styles.secondaryButton]}
|
|
||||||
// onPress={() => setShowModal(false)}
|
|
||||||
// >
|
|
||||||
// <Text style={[styles.buttonText, styles.secondaryText]}>Later</Text>
|
|
||||||
// </TouchableOpacity>
|
|
||||||
// </View>
|
|
||||||
// </View>
|
|
||||||
// </Modal>
|
|
||||||
// )}
|
|
||||||
// </Animated.View>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export default UserProfileStatus;
|
|
||||||
|
|
||||||
// const styles = StyleSheet.create({
|
|
||||||
// container: {
|
|
||||||
// margin: 12,
|
|
||||||
// },
|
|
||||||
// card: {
|
|
||||||
// flexDirection: "row",
|
|
||||||
// justifyContent: "space-between",
|
|
||||||
// alignItems: "center",
|
|
||||||
// borderRadius: 16,
|
|
||||||
// padding: 18,
|
|
||||||
// elevation: 5,
|
|
||||||
// shadowColor: "#000",
|
|
||||||
// shadowOpacity: 0.2,
|
|
||||||
// shadowRadius: 6,
|
|
||||||
// },
|
|
||||||
// header: {
|
|
||||||
// fontSize: 20,
|
|
||||||
// fontWeight: "700",
|
|
||||||
// color: "#fff",
|
|
||||||
// marginBottom: 10,
|
|
||||||
// },
|
|
||||||
// leftSection: {
|
|
||||||
// flex: 1,
|
|
||||||
// },
|
|
||||||
// infoRow: {
|
|
||||||
// flexDirection: "row",
|
|
||||||
// justifyContent: "space-between",
|
|
||||||
// marginBottom: 10,
|
|
||||||
// },
|
|
||||||
// infoLabelBox: {
|
|
||||||
// marginBottom: 6,
|
|
||||||
// },
|
|
||||||
// infoLabel: {
|
|
||||||
// color: "rgba(255,255,255,0.8)",
|
|
||||||
// fontSize: 13,
|
|
||||||
// },
|
|
||||||
// infoValueBox: {
|
|
||||||
// marginBottom: 6,
|
|
||||||
// alignItems: "flex-end",
|
|
||||||
// },
|
|
||||||
// infoValue: {
|
|
||||||
// color: "#fff",
|
|
||||||
// fontWeight: "600",
|
|
||||||
// fontSize: 13,
|
|
||||||
// },
|
|
||||||
// statusBox: {
|
|
||||||
// backgroundColor: "rgba(255,255,255,0.15)",
|
|
||||||
// padding: 10,
|
|
||||||
// borderRadius: 8,
|
|
||||||
// },
|
|
||||||
// statusText: {
|
|
||||||
// color: "#fff",
|
|
||||||
// fontSize: 13,
|
|
||||||
// },
|
|
||||||
// avatar: {
|
|
||||||
// width: 80,
|
|
||||||
// height: 80,
|
|
||||||
// borderRadius: 40,
|
|
||||||
// borderWidth: 2,
|
|
||||||
// borderColor: "#fff",
|
|
||||||
// },
|
|
||||||
// modalOverlay: {
|
|
||||||
// flex: 1,
|
|
||||||
// backgroundColor: "rgba(0,0,0,0.5)",
|
|
||||||
// justifyContent: "center",
|
|
||||||
// alignItems: "center",
|
|
||||||
// },
|
|
||||||
// modalBox: {
|
|
||||||
// width: "85%",
|
|
||||||
// backgroundColor: "#fff",
|
|
||||||
// borderRadius: 18,
|
|
||||||
// padding: 20,
|
|
||||||
// alignItems: "center",
|
|
||||||
// elevation: 6,
|
|
||||||
// },
|
|
||||||
// modalTitle: {
|
|
||||||
// fontSize: 20,
|
|
||||||
// fontWeight: "700",
|
|
||||||
// color: colors.primary,
|
|
||||||
// marginVertical: 8,
|
|
||||||
// },
|
|
||||||
// modalText: {
|
|
||||||
// textAlign: "center",
|
|
||||||
// color: "#444",
|
|
||||||
// fontSize: 14,
|
|
||||||
// marginBottom: 18,
|
|
||||||
// },
|
|
||||||
// button: {
|
|
||||||
// backgroundColor: colors.primary,
|
|
||||||
// paddingVertical: 10,
|
|
||||||
// paddingHorizontal: 30,
|
|
||||||
// borderRadius: 8,
|
|
||||||
// marginTop: 8,
|
|
||||||
// },
|
|
||||||
// buttonText: {
|
|
||||||
// color: "#fff",
|
|
||||||
// fontWeight: "600",
|
|
||||||
// },
|
|
||||||
// secondaryButton: {
|
|
||||||
// backgroundColor: "#eee",
|
|
||||||
// },
|
|
||||||
// secondaryText: {
|
|
||||||
// color: "#333",
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
@@ -329,10 +11,11 @@ import {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
import * as Keychain from "react-native-keychain";
|
import * as Keychain from "react-native-keychain";
|
||||||
import { useFocusEffect, useNavigation } from "@react-navigation/native";
|
import { useFocusEffect, useNavigation } from "@react-navigation/native";
|
||||||
import { PUT_USER_STATUS } from "../../utils/ApiUrls";
|
import { DASHBOARD_URL, PUT_USER_STATUS } from "../../utils/ApiUrls";
|
||||||
import colors from "../../components/colors";
|
import colors from "../../components/colors";
|
||||||
import LinearGradient from "react-native-linear-gradient";
|
import LinearGradient from "react-native-linear-gradient";
|
||||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||||
|
import { apiCall } from "../../utils/ApiHelper";
|
||||||
|
|
||||||
export type ApiResponse = {
|
export type ApiResponse = {
|
||||||
message: string;
|
message: string;
|
||||||
@@ -345,6 +28,49 @@ export type ApiResponse = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type DashboardResponse = {
|
||||||
|
message: string;
|
||||||
|
status: string;
|
||||||
|
statusCode: number;
|
||||||
|
data: {
|
||||||
|
totalPaidAmountMonth: number;
|
||||||
|
multiplePayAmount: number;
|
||||||
|
completeStatusMyGroupCount: number;
|
||||||
|
activeStatusMyGroupCount: number;
|
||||||
|
myCreatedGroups: any[];
|
||||||
|
myJoinedGroups: Array<{
|
||||||
|
groupId: string;
|
||||||
|
groupName: string;
|
||||||
|
contributionAmount: number;
|
||||||
|
currentCycle: number;
|
||||||
|
createdDate: string;
|
||||||
|
status: string;
|
||||||
|
}>;
|
||||||
|
requestList: any[];
|
||||||
|
myRequests: Array<{
|
||||||
|
requestId: string;
|
||||||
|
groupName: string;
|
||||||
|
type: string;
|
||||||
|
status: string;
|
||||||
|
requestDate: string;
|
||||||
|
}>;
|
||||||
|
dueGroupDetails: Array<{
|
||||||
|
groupId: string;
|
||||||
|
groupName: string;
|
||||||
|
baseAmount: number;
|
||||||
|
lateFee: number;
|
||||||
|
waivedAmount: number;
|
||||||
|
totalDue: number;
|
||||||
|
dueDate: string;
|
||||||
|
isLate: boolean;
|
||||||
|
currentCycle: number;
|
||||||
|
lateFeeMode: string;
|
||||||
|
lateFeeValue: number;
|
||||||
|
hasPaid: boolean;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type UserProfileStatusProps = {
|
type UserProfileStatusProps = {
|
||||||
profileImage?: { uri: string };
|
profileImage?: { uri: string };
|
||||||
};
|
};
|
||||||
@@ -354,21 +80,31 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({ profileImage }) =
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [statusData, setStatusData] = useState<ApiResponse["data"] | null>(null);
|
const [statusData, setStatusData] = useState<ApiResponse["data"] | null>(null);
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [intervalId, setIntervalId] = useState<null | number>(null);
|
const [moreItem, setMoreItem] = useState(false);
|
||||||
const [moreItem, setMoreItem] = useState(false)
|
const [userDashboard, setUserDashboard] = useState<DashboardResponse["data"] | null>(null);
|
||||||
const fadeAnim = useState(new Animated.Value(0))[0];
|
|
||||||
const scaleAnim = useState(new Animated.Value(0.9))[0];
|
|
||||||
|
|
||||||
const userStatus = async () => {
|
// Animation refs to prevent re-creation on re-render
|
||||||
|
const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||||
|
const scaleAnim = useRef(new Animated.Value(0.9)).current;
|
||||||
|
|
||||||
|
// Memoized icons list
|
||||||
|
const icons = useMemo(
|
||||||
|
() => ["person", "people", "person-add", "create", "card", "checkmark-done"],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetch user status
|
||||||
|
const userStatus = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const credentials = await Keychain.getGenericPassword();
|
const credentials = await Keychain.getGenericPassword();
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
ToastAndroid.show("You are not logged in", ToastAndroid.SHORT);
|
ToastAndroid.show("You are not logged in", ToastAndroid.SHORT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const token = credentials.password;
|
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
const token = credentials.password;
|
||||||
|
|
||||||
const response = await fetch(PUT_USER_STATUS, {
|
const response = await fetch(PUT_USER_STATUS, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -378,11 +114,10 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({ profileImage }) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
const json: ApiResponse = await response.json();
|
const json: ApiResponse = await response.json();
|
||||||
|
|
||||||
if (json.status === "success") {
|
if (json.status === "success") {
|
||||||
setStatusData(json.data);
|
setStatusData(json.data);
|
||||||
if (json.data.profileStatus === false) {
|
if (!json.data.profileStatus) setShowModal(true);
|
||||||
setShowModal(true);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ToastAndroid.show("Failed to fetch status", ToastAndroid.SHORT);
|
ToastAndroid.show("Failed to fetch status", ToastAndroid.SHORT);
|
||||||
}
|
}
|
||||||
@@ -391,62 +126,118 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({ profileImage }) =
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
userStatus();
|
userStatus();
|
||||||
}, [])
|
}, [userStatus])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Animation start
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Animated.parallel([
|
Animated.parallel([
|
||||||
Animated.timing(fadeAnim, {
|
Animated.timing(fadeAnim, { toValue: 1, duration: 600, useNativeDriver: true }),
|
||||||
toValue: 1,
|
Animated.timing(scaleAnim, { toValue: 1, duration: 600, useNativeDriver: true }),
|
||||||
duration: 600,
|
|
||||||
useNativeDriver: true,
|
|
||||||
}),
|
|
||||||
Animated.timing(scaleAnim, {
|
|
||||||
toValue: 1,
|
|
||||||
duration: 600,
|
|
||||||
useNativeDriver: true,
|
|
||||||
}),
|
|
||||||
]).start();
|
]).start();
|
||||||
}, []);
|
}, [fadeAnim, scaleAnim]);
|
||||||
|
|
||||||
const handleGoToProfile = () => {
|
const handleGoToProfile = useCallback(() => {
|
||||||
if (intervalId) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
setIntervalId(null);
|
|
||||||
}
|
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
navigation.navigate("Profile" as never);
|
navigation.navigate("Profile" as never);
|
||||||
};
|
}, [navigation]);
|
||||||
|
|
||||||
const handlePress = (label: string) => {
|
const handlePress = useCallback((label: string) => {
|
||||||
ToastAndroid.show(`${label} clicked`, ToastAndroid.SHORT);
|
ToastAndroid.show(`${label} clicked`, ToastAndroid.SHORT);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
// Graphics and icons for different status items
|
|
||||||
const getStatusIcon = (index: number) => {
|
|
||||||
const icons = ["person", "people", "person-add", "create", "card", "checkmark-done"];
|
|
||||||
return icons[index];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getValueColor = (value: string) => {
|
const getValueColor = (value: string) => {
|
||||||
if (value.includes("₹")) return "#4CAF50"; // Green for money
|
if (value.includes("₹") || value === "Active" || value === "Yes ✅") return "#4CAF50";
|
||||||
if (value === "Active" || value === "Yes ✅") return "#4CAF50";
|
|
||||||
if (value === "Inactive" || value === "No ❌") return "#FF6B6B";
|
if (value === "Inactive" || value === "No ❌") return "#FF6B6B";
|
||||||
return "#fff";
|
return "#fff";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Dynamic info items based on dashboard data
|
||||||
|
const infoItems = useMemo(() => {
|
||||||
|
if (!userDashboard) {
|
||||||
|
return [
|
||||||
|
{ label: "Role", value: statusData?.role || "-" },
|
||||||
|
{ label: "Active", value: "0" },
|
||||||
|
{ label: "Joined", value: "0" },
|
||||||
|
{ label: "Created", value: "0" },
|
||||||
|
{ label: "Due Pay", value: "₹0" },
|
||||||
|
{ label: "Completed", value: "0" },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: "Role",
|
||||||
|
value: statusData?.role || "-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Active",
|
||||||
|
value: userDashboard.activeStatusMyGroupCount.toString()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Joined",
|
||||||
|
value: userDashboard.myJoinedGroups.length.toString()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Created",
|
||||||
|
value: userDashboard.myCreatedGroups.length.toString()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Due Pay",
|
||||||
|
value: `₹${userDashboard.multiplePayAmount}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Completed",
|
||||||
|
value: userDashboard.completeStatusMyGroupCount.toString()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [userDashboard, statusData]);
|
||||||
|
|
||||||
|
// Calculate total due amount
|
||||||
|
const getTotalDueAmount = useCallback(() => {
|
||||||
|
if (!userDashboard?.dueGroupDetails) return 0;
|
||||||
|
return userDashboard.dueGroupDetails.reduce((total, due) => total + due.totalDue, 0);
|
||||||
|
}, [userDashboard]);
|
||||||
|
|
||||||
|
// Calculate pending requests count
|
||||||
|
const getPendingRequestsCount = useCallback(() => {
|
||||||
|
if (!userDashboard?.myRequests) return 0;
|
||||||
|
return userDashboard.myRequests.filter(request => request.status === "PENDING").length;
|
||||||
|
}, [userDashboard]);
|
||||||
|
|
||||||
|
const getDashboardData = async () => {
|
||||||
|
try {
|
||||||
|
const result = (await apiCall(DASHBOARD_URL, "GET")) as DashboardResponse;
|
||||||
|
console.log("DASHBOARD_URL-------", result)
|
||||||
|
if (result.status === "success") {
|
||||||
|
setUserDashboard(result.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Error fetching dashboard data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getDashboardData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Format currency
|
||||||
|
const formatCurrency = (amount: number) => {
|
||||||
|
return `₹${amount.toLocaleString('en-IN')}`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[styles.container, { opacity: fadeAnim, transform: [{ scale: scaleAnim }] }]}>
|
<Animated.View style={[styles.container, { opacity: fadeAnim, transform: [{ scale: scaleAnim }] }]}>
|
||||||
{/* Background Decorative Elements */}
|
{/* Decorative Background */}
|
||||||
<View style={styles.backgroundGraphics}>
|
<View style={styles.backgroundGraphics}>
|
||||||
<View style={[styles.floatingCircle, styles.circle1]} />
|
{["circle1", "circle2", "circle3"].map((c, i) => (
|
||||||
<View style={[styles.floatingCircle, styles.circle2]} />
|
<View key={i} style={[styles.floatingCircle, styles[c as keyof typeof styles]]} />
|
||||||
<View style={[styles.floatingCircle, styles.circle3]} />
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
@@ -455,11 +246,11 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({ profileImage }) =
|
|||||||
end={{ x: 1, y: 1 }}
|
end={{ x: 1, y: 1 }}
|
||||||
style={styles.card}
|
style={styles.card}
|
||||||
>
|
>
|
||||||
{/* Header with Graphics */}
|
{/* Header */}
|
||||||
<View style={styles.headerSection}>
|
<View style={styles.headerSection}>
|
||||||
<View style={styles.headerContent}>
|
<View style={styles.headerContent}>
|
||||||
<View style={styles.titleWithIcon}>
|
<View style={styles.titleWithIcon}>
|
||||||
<Ionicons name="stats-chart" size={24} color="#fff" style={styles.headerIcon} />
|
<Ionicons name="stats-chart" size={22} color="#fff" style={styles.headerIcon} />
|
||||||
<Text style={styles.header}>Profile Overview</Text>
|
<Text style={styles.header}>Profile Overview</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.statusBadge}>
|
<View style={styles.statusBadge}>
|
||||||
@@ -471,31 +262,20 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({ profileImage }) =
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Content Section */}
|
||||||
<View style={styles.contentSection}>
|
<View style={styles.contentSection}>
|
||||||
{/* LEFT SECTION - Stats */}
|
{/* LEFT SIDE */}
|
||||||
<View style={styles.leftSection}>
|
<View style={styles.leftSection}>
|
||||||
<View style={styles.infoGrid}>
|
<View style={styles.infoGrid}>
|
||||||
{[
|
{infoItems.map((item, index) => (
|
||||||
{ label: "Role", value: statusData?.role || "-" },
|
|
||||||
{ label: "Active Groups", value: "5" },
|
|
||||||
{ label: "Joined Groups", value: "3" },
|
|
||||||
{ label: "Created Groups", value: "2" },
|
|
||||||
{ label: "Due Payment", value: "₹1500" },
|
|
||||||
{ label: "Completed Groups", value: "4" },
|
|
||||||
].map((item, index) => (
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={index}
|
key={item.label}
|
||||||
style={styles.infoCard}
|
style={styles.infoCard}
|
||||||
onPress={() => handlePress(item.label)}
|
onPress={() => handlePress(item.label)}
|
||||||
>
|
>
|
||||||
<View style={styles.infoCardHeader}>
|
<View style={styles.infoCardHeader}>
|
||||||
<View style={styles.iconContainer}>
|
<View style={styles.iconContainer}>
|
||||||
<Ionicons
|
<Ionicons name={icons[index]} size={16} color={colors.primary} />
|
||||||
name={getStatusIcon(index)}
|
|
||||||
size={16}
|
|
||||||
color={colors.primary}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.infoLabel}>{item.label}</Text>
|
<Text style={styles.infoLabel}>{item.label}</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -504,133 +284,100 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({ profileImage }) =
|
|||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
))}
|
))}
|
||||||
<TouchableOpacity onPress={()=>setMoreItem(!moreItem)}>
|
</View>
|
||||||
<Ionicons name={"arrow-down"} color="#ffff" size={22}/>
|
</View>
|
||||||
</TouchableOpacity>
|
{/* RIGHT SIDE */}
|
||||||
|
<View style={styles.profileSection}>
|
||||||
|
<TouchableOpacity onPress={() => navigation.navigate("Profile" as never)} activeOpacity={0.8}>
|
||||||
|
<LinearGradient
|
||||||
|
colors={["rgba(255,255,255,0.2)", "rgba(255,255,255,0.1)"]}
|
||||||
|
style={styles.profileContainer}
|
||||||
|
>
|
||||||
|
{profileImage ? (
|
||||||
|
<Image source={{ uri: profileImage.uri }} style={styles.avatar} />
|
||||||
|
) : (
|
||||||
|
<View style={styles.avatarPlaceholder}>
|
||||||
|
<Ionicons name="person" size={40} color="#fff" />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View style={styles.profileBadge}>
|
||||||
|
<Ionicons name="camera" size={16} color={colors.primary} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.viewProfileText}>View Profile</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity style={[styles.profileContainer, {marginVertical:15}]} onPress={() => setMoreItem(!moreItem)}>
|
||||||
|
<Ionicons name={moreItem ? "arrow-up" : "arrow-down"} color="#fff" size={22} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{moreItem && (
|
||||||
|
<>
|
||||||
|
<View style={styles.statusOverview}>
|
||||||
|
<View style={styles.statusHeader}>
|
||||||
|
<Ionicons name="information-circle" size={20} color="#fff" />
|
||||||
|
<Text style={styles.statusOverviewTitle}>Status Overview</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.statusItems}>
|
||||||
|
<Text style={styles.statusItemText}>
|
||||||
|
Organizer: {statusData?.organizerStatus || "-"}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.statusItemText}>
|
||||||
|
Profile: {statusData?.profileStatus ? "Complete ✅" : "Incomplete ❌"}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.statusItemText}>
|
||||||
|
Pending Requests: {getPendingRequestsCount()}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.statusItemText}>
|
||||||
|
Total Due: {formatCurrency(getTotalDueAmount())}
|
||||||
|
</Text>
|
||||||
|
{userDashboard?.dueGroupDetails?.map((due, index) => (
|
||||||
|
<Text key={index} style={[styles.statusItemText, { fontSize: 12, opacity: 0.8 }]}>
|
||||||
|
• {due.groupName}: {formatCurrency(due.totalDue)} (Due: {due.dueDate})
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Status Overview Card */}
|
<View style={styles.progressSection}>
|
||||||
{
|
<View style={styles.progressHeader}>
|
||||||
moreItem && (
|
<Text style={styles.progressText}>Profile Completion</Text>
|
||||||
<View style={styles.statusOverview}>
|
<Text style={styles.progressPercent}>
|
||||||
<View style={styles.statusHeader}>
|
{statusData?.profileStatus ? "100%" : "60%"}
|
||||||
<Ionicons name="information-circle" size={20} color="#fff" />
|
</Text>
|
||||||
<Text style={styles.statusOverviewTitle}>Status Overview</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.statusItems}>
|
|
||||||
<View style={styles.statusItem}>
|
|
||||||
<View style={styles.statusIndicator}>
|
|
||||||
<Ionicons
|
|
||||||
name={statusData?.organizerStatus === "Active" ? "checkmark-circle" : "time"}
|
|
||||||
size={16}
|
|
||||||
color={statusData?.organizerStatus === "Active" ? "#4CAF50" : "#FFA500"}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Text style={styles.statusItemText}>
|
|
||||||
Organizer: {statusData?.organizerStatus || "-"}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.statusItem}>
|
|
||||||
<View style={styles.statusIndicator}>
|
|
||||||
<Ionicons
|
|
||||||
name={statusData?.profileStatus ? "checkmark-circle" : "close-circle"}
|
|
||||||
size={16}
|
|
||||||
color={statusData?.profileStatus ? "#4CAF50" : "#FF6B6B"}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Text style={styles.statusItemText}>
|
|
||||||
Profile: {statusData?.profileStatus ? "Complete" : "Incomplete"}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* RIGHT SECTION - Profile Graphic */}
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => navigation.navigate("Profile" as never)}
|
|
||||||
activeOpacity={0.8}
|
|
||||||
style={styles.profileSection}
|
|
||||||
>
|
|
||||||
<LinearGradient
|
|
||||||
colors={['rgba(255,255,255,0.2)', 'rgba(255,255,255,0.1)']}
|
|
||||||
style={styles.profileContainer}
|
|
||||||
>
|
|
||||||
{profileImage ? (
|
|
||||||
<Image source={{ uri: profileImage.uri }} style={styles.avatar} />
|
|
||||||
) : (
|
|
||||||
<View style={styles.avatarPlaceholder}>
|
|
||||||
<Ionicons name="person" size={40} color="#fff" />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<View style={styles.profileBadge}>
|
|
||||||
<Ionicons name="camera" size={16} color={colors.primary} />
|
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.viewProfileText}>View Profile</Text>
|
<View style={styles.progressBar}>
|
||||||
</LinearGradient>
|
<Animated.View
|
||||||
</TouchableOpacity>
|
style={[
|
||||||
</View>
|
styles.progressFill,
|
||||||
|
{
|
||||||
{/* Progress Bar for Profile Completion */}
|
width: statusData?.profileStatus ? "100%" : "60%",
|
||||||
<View style={styles.progressSection}>
|
backgroundColor: statusData?.profileStatus ? "#4CAF50" : "#FFA500",
|
||||||
<View style={styles.progressHeader}>
|
},
|
||||||
<Text style={styles.progressText}>Profile Completion</Text>
|
]}
|
||||||
<Text style={styles.progressPercent}>
|
/>
|
||||||
{statusData?.profileStatus ? "100%" : "60%"}
|
</View>
|
||||||
</Text>
|
</View>
|
||||||
</View>
|
</>
|
||||||
<View style={styles.progressBar}>
|
)}
|
||||||
<Animated.View
|
|
||||||
style={[
|
|
||||||
styles.progressFill,
|
|
||||||
{
|
|
||||||
width: statusData?.profileStatus ? '100%' : '60%',
|
|
||||||
backgroundColor: statusData?.profileStatus ? '#4CAF50' : '#FFA500'
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
|
|
||||||
{/* Enhanced Modal */}
|
{/* Modal */}
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<Modal visible transparent animationType="fade">
|
<Modal visible transparent animationType="fade">
|
||||||
<View style={styles.modalOverlay}>
|
<View style={styles.modalOverlay}>
|
||||||
<Animated.View
|
<Animated.View style={[styles.modalBox, { opacity: fadeAnim, transform: [{ scale: scaleAnim }] }]}>
|
||||||
style={[
|
<Ionicons name="alert-circle" size={60} color={colors.primary} />
|
||||||
styles.modalBox,
|
|
||||||
{
|
|
||||||
opacity: fadeAnim,
|
|
||||||
transform: [{ scale: scaleAnim }]
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<View style={styles.modalGraphic}>
|
|
||||||
<Ionicons name="alert-circle" size={60} color={colors.primary} />
|
|
||||||
<View style={styles.modalPulse} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Text style={styles.modalTitle}>Complete Your Profile</Text>
|
<Text style={styles.modalTitle}>Complete Your Profile</Text>
|
||||||
<Text style={styles.modalText}>
|
<Text style={styles.modalText}>
|
||||||
Your profile is incomplete. Please update your details to unlock all features and improve your experience.
|
Your profile is incomplete. Update your details to unlock all features.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<View style={styles.modalProgress}>
|
|
||||||
<Text style={styles.modalProgressText}>Current Progress: 60%</Text>
|
|
||||||
<View style={styles.modalProgressBar}>
|
|
||||||
<View style={[styles.modalProgressFill, { width: '60%' }]} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.modalButtons}>
|
<View style={styles.modalButtons}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity style={[styles.button, styles.primaryButton]} onPress={handleGoToProfile}>
|
||||||
style={[styles.button, styles.primaryButton]}
|
|
||||||
onPress={handleGoToProfile}
|
|
||||||
>
|
|
||||||
<Ionicons name="person" size={18} color="#fff" />
|
<Ionicons name="person" size={18} color="#fff" />
|
||||||
<Text style={styles.buttonText}>Complete Profile</Text>
|
<Text style={styles.buttonText}>Complete Profile</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -655,7 +402,7 @@ export default UserProfileStatus;
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
margin: 16,
|
// margin: 16,
|
||||||
},
|
},
|
||||||
backgroundGraphics: {
|
backgroundGraphics: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -742,13 +489,12 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
marginBottom: 16,
|
|
||||||
},
|
},
|
||||||
infoCard: {
|
infoCard: {
|
||||||
width: '48%',
|
width: '48%',
|
||||||
backgroundColor: 'rgba(255,255,255,0.15)',
|
backgroundColor: 'rgba(255,255,255,0.15)',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
padding: 12,
|
padding: 5,
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'rgba(255,255,255,0.2)',
|
borderColor: 'rgba(255,255,255,0.2)',
|
||||||
@@ -782,6 +528,7 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'rgba(255,255,255,0.2)',
|
borderColor: 'rgba(255,255,255,0.2)',
|
||||||
|
marginTop: 16,
|
||||||
},
|
},
|
||||||
statusHeader: {
|
statusHeader: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -797,13 +544,6 @@ const styles = StyleSheet.create({
|
|||||||
statusItems: {
|
statusItems: {
|
||||||
gap: 8,
|
gap: 8,
|
||||||
},
|
},
|
||||||
statusItem: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
statusIndicator: {
|
|
||||||
marginRight: 8,
|
|
||||||
},
|
|
||||||
statusItemText: {
|
statusItemText: {
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -902,19 +642,6 @@ const styles = StyleSheet.create({
|
|||||||
shadowRadius: 15,
|
shadowRadius: 15,
|
||||||
shadowOffset: { width: 0, height: 6 },
|
shadowOffset: { width: 0, height: 6 },
|
||||||
},
|
},
|
||||||
modalGraphic: {
|
|
||||||
position: 'relative',
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
|
||||||
modalPulse: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: -10,
|
|
||||||
left: -10,
|
|
||||||
right: -10,
|
|
||||||
bottom: -10,
|
|
||||||
borderRadius: 40,
|
|
||||||
backgroundColor: 'rgba(76, 175, 80, 0.2)',
|
|
||||||
},
|
|
||||||
modalTitle: {
|
modalTitle: {
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
fontWeight: "700",
|
fontWeight: "700",
|
||||||
@@ -929,28 +656,6 @@ const styles = StyleSheet.create({
|
|||||||
lineHeight: 20,
|
lineHeight: 20,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
modalProgress: {
|
|
||||||
width: '100%',
|
|
||||||
marginBottom: 20,
|
|
||||||
},
|
|
||||||
modalProgressText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
color: "#333",
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: '600',
|
|
||||||
marginBottom: 6,
|
|
||||||
},
|
|
||||||
modalProgressBar: {
|
|
||||||
height: 4,
|
|
||||||
backgroundColor: '#f0f0f0',
|
|
||||||
borderRadius: 2,
|
|
||||||
overflow: 'hidden',
|
|
||||||
},
|
|
||||||
modalProgressFill: {
|
|
||||||
height: '100%',
|
|
||||||
backgroundColor: colors.primary,
|
|
||||||
borderRadius: 2,
|
|
||||||
},
|
|
||||||
modalButtons: {
|
modalButtons: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
export const BASE_URL = "https://api.godemo.in/FundCollect/api/"
|
export const BASE_URL = "http://192.168.1.18:9060/api/"
|
||||||
// export const BASE_URL = "http://192.168.1.5:9060/api/"
|
// export const BASE_URL = "http://192.168.1.5:9060/api/"
|
||||||
export const LOGIN_URL = `${BASE_URL}user/generate/otp?mobiles=`
|
export const LOGIN_URL = `${BASE_URL}user/generate/otp?mobiles=`
|
||||||
export const VARIFY_OTP_URL = `${BASE_URL}user/otp/verify`
|
export const VARIFY_OTP_URL = `${BASE_URL}user/otp/verify`
|
||||||
@@ -23,4 +23,5 @@ export const CONTRIBUTE = `${BASE_URL}payments/groups/`;
|
|||||||
export const TRANSACTION = `${BASE_URL}payments/my-transactions`;
|
export const TRANSACTION = `${BASE_URL}payments/my-transactions`;
|
||||||
export const UPLOAD_GROUP_IMAGE = `${BASE_URL}fund/groups/`;
|
export const UPLOAD_GROUP_IMAGE = `${BASE_URL}fund/groups/`;
|
||||||
export const STORE_FCM_TOKEN = `${BASE_URL}token/store?fcmToken=`
|
export const STORE_FCM_TOKEN = `${BASE_URL}token/store?fcmToken=`
|
||||||
export const REMOVE_FCM_TOKEN = `${BASE_URL}token/remove?fcmToken=`
|
export const REMOVE_FCM_TOKEN = `${BASE_URL}token/remove?fcmToken=`
|
||||||
|
export const DASHBOARD_URL = `${BASE_URL}fund/dashboard`
|
||||||
Reference in New Issue
Block a user