This commit is contained in:
Jaanu423
2025-10-16 15:23:46 +05:30
parent be6d465a28
commit 79cf7470d4
3 changed files with 292 additions and 236 deletions
+6 -6
View File
@@ -18,8 +18,8 @@ const Header: React.FC<HeaderProps> = ({
showBack = true, showBack = true,
onBackPress, onBackPress,
rightAction, rightAction,
titleColor = colors.primary, titleColor = '#000',
arrowColor = colors.primary arrowColor = '#000'
}) => { }) => {
const navigation = useNavigation(); const navigation = useNavigation();
@@ -33,12 +33,12 @@ const Header: React.FC<HeaderProps> = ({
return ( return (
<View style={styles.header}> <View style={styles.header}>
{/* {showBack && ( {showBack && (
<TouchableOpacity onPress={handleBack} style={{padding:6}}> <TouchableOpacity onPress={handleBack} style={{padding:6}}>
<Ionicons name="arrow-back" size={24} color={arrowColor} /> <Ionicons name="arrow-back" size={24} color={arrowColor} />
</TouchableOpacity> </TouchableOpacity>
)} */} )}
<Text style={[styles.headerTitle, {color:colors.black}]}>{title}</Text> <Text style={[styles.headerTitle, {color:titleColor}]}>{title}</Text>
<View>{rightAction}</View> <View>{rightAction}</View>
</View> </View>
); );
@@ -57,6 +57,6 @@ const styles = StyleSheet.create({
headerTitle: { headerTitle: {
fontSize: 18, fontSize: 18,
fontWeight: "600", fontWeight: "600",
textTransform:'uppercase' textTransform: "capitalize",
}, },
}); });
+4 -13
View File
@@ -66,8 +66,8 @@ const userActions: ActionItem[] = [
const Home: React.FC = () => { const Home: React.FC = () => {
const user = useSelector((state: any) => state.user); const user = useSelector((state: any) => state.user);
const notifications = useSelector( const notifications = useSelector(
(state: any) => state.notifications.notifications (state: any) => state.notifications.notifications
); );
const navigation = useNavigation<TabNavigationProp>(); const navigation = useNavigation<TabNavigationProp>();
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -97,7 +97,6 @@ const Home: React.FC = () => {
} else if (item.action === "TRANSACTION") { } else if (item.action === "TRANSACTION") {
navigation.navigate("TransactionList" as never); navigation.navigate("TransactionList" as never);
} else if (item.action === "NAVIGATE_CREATE_GROUP") { } else if (item.action === "NAVIGATE_CREATE_GROUP") {
navigation.navigate("GroupCreation" as never); navigation.navigate("GroupCreation" as never);
} else { } else {
ToastAndroid.show(`Unknown action: ${item.title}`, ToastAndroid.SHORT); ToastAndroid.show(`Unknown action: ${item.title}`, ToastAndroid.SHORT);
@@ -133,13 +132,12 @@ const Home: React.FC = () => {
<Text style={styles.userName}>{user.firstName || apiUser?.firstName || 'User'}</Text> <Text style={styles.userName}>{user.firstName || apiUser?.firstName || 'User'}</Text>
</View> </View>
<TouchableOpacity onPress={() => navigation.navigate("Notification" as never)}> <TouchableOpacity onPress={() => navigation.navigate("Notification" as never)}>
{/* <Image source={getProfileImageUri()} style={styles.profileImage} /> */} <Text style={{ position: 'absolute', right: -6, top: -4, backgroundColor: colors.danger, color: colors.white, borderRadius: 8, width: 16, height: 16, textAlign: 'center', fontSize: 10, fontWeight: 'bold', lineHeight: 16, zIndex: 1 }}>{unreadCount > 0 ? unreadCount : ''}</Text>
<Text style={{position: 'absolute', right: -6, top: -4, backgroundColor: colors.danger, color: colors.white, borderRadius: 8, width: 16, height: 16, textAlign: 'center', fontSize: 10, fontWeight: 'bold', lineHeight: 16, zIndex: 1}}>{unreadCount > 0 ? unreadCount : ''}</Text>
<Ionicons name="notifications-outline" size={30} color={colors.textDark} /> <Ionicons name="notifications-outline" size={30} color={colors.textDark} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<UserProfileStatus /> <UserProfileStatus profileImage={getProfileImageUri()} />
<Text style={styles.sectionTitle}>Quick Actions</Text> <Text style={styles.sectionTitle}>Quick Actions</Text>
@@ -178,13 +176,6 @@ const styles = StyleSheet.create({
fontWeight: 'bold', fontWeight: 'bold',
color: colors.textDark, color: colors.textDark,
}, },
profileImage: {
width: 50,
height: 50,
borderRadius: 25,
borderColor: colors.pastelPink, // Using pastelPink for border
borderWidth: 2,
},
sectionTitle: { sectionTitle: {
fontSize: 20, fontSize: 20,
fontWeight: '600', fontWeight: '600',
+282 -217
View File
@@ -1,249 +1,314 @@
import React, { use, useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { import {
StyleSheet, StyleSheet,
Text, Text,
View, View,
ActivityIndicator, TouchableOpacity,
TouchableOpacity, Modal,
Modal, ToastAndroid,
Alert, Image,
Animated,
} 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 { 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";
export type ApiResponse = { export type ApiResponse = {
message: string; message: string;
status: string; status: string;
statusCode: number; statusCode: number;
data: { data: {
role: string; role: string;
organizerStatus: string; organizerStatus: string;
profileStatus: boolean; profileStatus: boolean;
}; };
}; };
type UserProfileStatusProps = {
profileImage?: { uri: string };
};
const UserProfileStatus: React.FC = () => { const UserProfileStatus: React.FC<UserProfileStatusProps> = ({ profileImage }) => {
const navigation = useNavigation(); const navigation = useNavigation();
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 [error, setError] = useState<string | null>(null); const [showModal, setShowModal] = useState(false);
const [showModal, setShowModal] = useState(false); const [intervalId, setIntervalId] = useState<null | number>(null);
const [intervalId, setIntervalId] = useState<null | number>(null); const fadeAnim = useState(new Animated.Value(0))[0];
const userStatus = async () => { const userStatus = 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; const token = credentials.password;
setLoading(true); setLoading(true);
setError(null); const response = await fetch(PUT_USER_STATUS, {
const response = await fetch(PUT_USER_STATUS, { method: "GET",
method: "GET", headers: {
headers: { Accept: "*/*",
Accept: "*/*", Authorization: `Bearer ${token}`,
Authorization: `Bearer ${token}`, },
}, });
});
const json: ApiResponse = await response.json(); const json: ApiResponse = await response.json();
if (json.status === "success") {
console.log("Profile Status :", json); setStatusData(json.data);
if (json.status === "success") { if (json.data.profileStatus === false) {
setStatusData(json.data); setShowModal(true);
if (json.data.profileStatus === false) {
setShowModal(true);
}
} else {
setError(json.message || "Failed to fetch status");
}
} catch (err) {
setError("Something went wrong. Please try again.");
} finally {
setLoading(false);
} }
}; } else {
ToastAndroid.show("Failed to fetch status", ToastAndroid.SHORT);
}
} catch {
ToastAndroid.show("Something went wrong", ToastAndroid.SHORT);
} finally {
setLoading(false);
}
};
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
userStatus(); userStatus();
}, []) }, [])
); );
useEffect(() => { useEffect(() => {
if (statusData && statusData.profileStatus === false) { Animated.timing(fadeAnim, {
const id = setInterval(() => { toValue: 1,
setShowModal(true); duration: 600,
}, 5000); useNativeDriver: true,
setIntervalId(id); }).start();
return () => clearInterval(id); }, []);
}
}, [statusData]);
const handleGoToProfile = () => { const handleGoToProfile = () => {
// Stop modal repetition if (intervalId) {
if (intervalId) { clearInterval(intervalId);
clearInterval(intervalId); setIntervalId(null);
setIntervalId(null); }
} setShowModal(false);
navigation.navigate("Profile" as never);
};
setShowModal(false); const handlePress = (label: string) => {
ToastAndroid.show(`${label} clicked`, ToastAndroid.SHORT);
};
// Option 1: Mark profile as completed temporarily (so condition stops) return (
setStatusData(prev => (prev ? { ...prev, profileStatus: true } : prev)); <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>
// Navigate to Profile screen <View style={styles.infoRow}>
navigation.navigate("Profile" as never); <View>
}; {[
"Role",
return ( "Total Active Groups",
<View style={styles.container}> "Total Joined Groups",
{/* <Text style={styles.title}>Welcome Back 👋</Text> */} "Total Created Groups",
"Total Due Payment",
<LinearGradient "Total Completed Groups",
colors={[colors.pastelPink, colors.pastelPink ].map((label, i) => (
]} <TouchableOpacity
start={{ x: 0, y: 0 }} key={i}
end={{ x: 1, y: 1 }} style={styles.infoLabelBox}
style={styles.card} onPress={() => handlePress(label)}
>
<View>
<Text style={styles.cardTitle}>Profile Status</Text>
<Text style={{ color: '#fff' }}>Role: {statusData?.role}</Text>
<Text style={{ color: '#fff' }}>Organizer: {statusData?.organizerStatus}</Text>
<Text style={{ color: '#fff' }}>
Profile Completed: {statusData?.profileStatus ? "Yes ✅" : "No ❌"}
</Text>
</View>
<Ionicons name="person-circle" color={'#fff'} size={100}/>
</LinearGradient>
{/* Custom Modal */}
{showModal && (
<Modal
visible={showModal}
animationType="slide"
transparent
onRequestClose={() => setShowModal(false)}
> >
<View style={styles.modalOverlay}> <Text style={styles.infoLabel}>{label}</Text>
<View style={styles.modalBox}> </TouchableOpacity>
<Text style={styles.modalTitle}>Complete Your Profile</Text> ))}
<Text style={styles.modalText}> </View>
Your profile is incomplete. Please update your details to continue.
</Text>
<TouchableOpacity style={styles.button} onPress={handleGoToProfile}> <View>
<Text style={styles.buttonText}>Go to Profile</Text> {[
</TouchableOpacity> 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>
<TouchableOpacity <View style={styles.statusBox}>
style={[styles.button, { backgroundColor: "#ccc", marginTop: 8 }]} <Text style={styles.statusText}>
onPress={() => { Organizer: {statusData?.organizerStatus || "-"}
if (intervalId) { </Text>
clearInterval(intervalId); <Text style={styles.statusText}>
setIntervalId(null); Profile Completed:{" "}
} {statusData?.profileStatus ? "Yes ✅" : "No ❌"}
</Text>
setShowModal(false); </View>
}}
>
<Text style={[styles.buttonText, { color: "#333" }]}>Close</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
)}
</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; export default UserProfileStatus;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
// flex: 1, margin: 12,
// padding: 16, },
// backgroundColor: "#f5f7fb", card: {
}, flexDirection: "row",
centered: { justifyContent: "space-between",
flex: 1, alignItems: "center",
justifyContent: "center", borderRadius: 16,
alignItems: "center", padding: 18,
}, elevation: 5,
title: { shadowColor: "#000",
fontSize: 22, shadowOpacity: 0.2,
fontWeight: "700", shadowRadius: 6,
marginBottom: 20, },
}, header: {
card: { fontSize: 20,
backgroundColor: "#fff", fontWeight: "700",
padding: 16, color: "#fff",
borderRadius: 12, marginBottom: 10,
elevation: 3, },
shadowOpacity: 0.1, leftSection: {
shadowRadius: 6, flex: 1,
marginBottom: 10, },
justifyContent:'space-between', infoRow: {
flexDirection:'row' flexDirection: "row",
}, justifyContent: "space-between",
cardTitle: { marginBottom: 10,
fontSize: 18, },
fontWeight: "600", infoLabelBox: {
marginBottom: 10, marginBottom: 6,
color: '#fff' },
}, infoLabel: {
button: { color: "rgba(255,255,255,0.8)",
backgroundColor: "#007AFF", fontSize: 13,
paddingVertical: 10, },
paddingHorizontal: 20, infoValueBox: {
borderRadius: 8, marginBottom: 6,
alignItems: "center", alignItems: "flex-end",
}, },
buttonText: { infoValue: {
color: "#fff", color: "#fff",
fontWeight: "600", fontWeight: "600",
}, fontSize: 13,
errorText: { },
color: "red", statusBox: {
fontSize: 16, backgroundColor: "rgba(255,255,255,0.15)",
marginBottom: 12, padding: 10,
}, borderRadius: 8,
modalOverlay: { },
flex: 1, statusText: {
backgroundColor: "rgba(0,0,0,0.4)", color: "#fff",
justifyContent: "center", fontSize: 13,
alignItems: "center", },
}, avatar: {
modalBox: { width: 80,
width: "85%", height: 80,
backgroundColor: "#fff", borderRadius: 40,
borderRadius: 16, borderWidth: 2,
padding: 20, borderColor: "#fff",
alignItems: "center", },
elevation: 5, modalOverlay: {
}, flex: 1,
modalTitle: { backgroundColor: "rgba(0,0,0,0.5)",
fontSize: 20, justifyContent: "center",
fontWeight: "700", alignItems: "center",
marginBottom: 10, },
color: colors.primary, modalBox: {
}, width: "85%",
modalText: { backgroundColor: "#fff",
fontSize: 14, borderRadius: 18,
color: "#444", padding: 20,
textAlign: "center", alignItems: "center",
marginBottom: 20, 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",
},
}); });