This commit is contained in:
Abin Toppo
2025-12-10 10:15:38 +05:30
parent 3468de396c
commit bd7dbd864c
51 changed files with 1168 additions and 1132 deletions
+87 -74
View File
@@ -19,17 +19,27 @@ import type { StackNavigationProp } from '@react-navigation/stack';
import { RootStackParamList } from '../navigation/AppNavigator';
import LinearGradient from 'react-native-linear-gradient';
import Ionicons from '@react-native-vector-icons/ionicons';
import MaterialRipple from 'react-native-material-ripple';
import MaterialRipple from 'react-native-material-ripple';
import CustomAlert from '../components/CostomAlart';
import colors from '../components/colors';
import { responsiveFontSize } from 'react-native-responsive-dimensions';
import {
responsiveHeight,
responsiveWidth,
responsiveFontSize
} from "react-native-responsive-dimensions";
import { useTheme } from '../theme/ThemeProvider';
import FontsFamily from '../constents/FontsFamily';
const { width } = Dimensions.get('window');
const BLOCK_SIZE = width / 3;
// Note: You will need to install react-native-material-ripple
// npm install react-native-material-ripple
export default function LoginScreen() {
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
const [mobile, setMobile] = useState('');
@@ -42,9 +52,12 @@ export default function LoginScreen() {
alertType: 'info' as 'info' | 'success' | 'error',
});
const fade = useRef(new Animated.Value(0)).current;
const slide = useRef(new Animated.Value(30)).current;
const ctaScale = useRef(new Animated.Value(1)).current;
const { theme } = useTheme();
useEffect(() => {
Animated.parallel([
@@ -53,6 +66,7 @@ export default function LoginScreen() {
]).start();
}, []);
const showAlert = (
title: string,
message: string,
@@ -62,10 +76,12 @@ export default function LoginScreen() {
setAlertInfo({ visible: true, title, message, buttons, alertType });
};
const hideAlert = () => {
setAlertInfo({ ...alertInfo, visible: false });
};
const handleContinue = async () => {
if (mobile.length === 10) {
setLoading(true);
@@ -87,58 +103,64 @@ export default function LoginScreen() {
}
};
const colorsBlocks = [
colors.pastelPink,
colors.pastelBlue,
colors.pastelGreen,
colors.pastelOrange,
colors.pastelBlue,
colors.pastelPink,
colors.pastelGreen,
colors.pastelOrange,
colors.pastelPink,
theme.colors.logincard,
theme.colors.logincard1,
theme.colors.logincard1,
theme.colors.logincard,
theme.colors.logincard,
theme.colors.logincard1,
theme.colors.logincard1,
theme.colors.logincard,
theme.colors.logincard,
];
return (
<View style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor={colors.pastelOrange} />
{/* Funky grid blocks background (Unchanged) */}
<View style={styles.gridContainer}>
{colorsBlocks.map((bgColor, index) => (
<View
key={index}
style={[
styles.gridBlock,
{ backgroundColor: bgColor },
index % 2 === 0 ? styles.elevatedBlock : {},
]}
/>
))}
</View>
return (
<View style={[ styles.container, {backgroundColor: theme.colors.background }]}>
<StatusBar barStyle={theme.mode === "dark" ? "light-content" : "dark-content"} backgroundColor={colors.pastelOrange} />
{/* Funky grid blocks background (Unchanged) */}
<View style={styles.gridContainer}>
{colorsBlocks.map((bgColor, index) => (
<View
key={index}
style={[
styles.gridBlock,
{ backgroundColor: bgColor ,borderRadius:16 },
index % 2 === 0 ? styles.elevatedBlock : {},
]}
/>
))}
</View>
<KeyboardAvoidingView
style={styles.keyboardAvoidingContainer}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<View style={styles.contentContainer}>
<View style={styles.topSection}>
<View style={[ styles.topSection, {marginTop:60}]}>
<Animated.View style={{ opacity: fade, transform: [{ translateY: slide }] }}>
<Text style={styles.brand}>CircleCollect</Text>
<Text style={styles.title}>Welcome Back!</Text>
<Text style={styles.subtitle}>
<Text style={[styles.brand, { color: theme.colors.textPrimary }]}>CircleCollect</Text>
<Text style={[styles.title, { color: theme.colors.textPrimary }]}>Welcome Back!</Text>
<Text style={[styles.subtitle, { color: theme.colors.textSecondary }]}>
Let's get you signed in to your community.
</Text>
</Animated.View>
</View>
<Animated.View style={{ opacity: fade, transform: [{ translateY: slide }] }}>
<View style={styles.inputWrapper}>
<Ionicons name="call-outline" size={24} color={colors.textLight} style={styles.inputIcon} />
<Animated.View style={{ opacity: fade, transform: [{ translateY: slide }] ,marginTop: 150}}>
<View style={[styles.inputWrapper, { backgroundColor: theme.colors.reverseBackground }]}>
<Ionicons name="call-outline" size={24} color={theme.colors.reverseText} style={styles.inputIcon} />
<TextInput
style={styles.input}
style={[ styles.input, { color: theme.colors.reverseText, fontFamily: FontsFamily.nunitoRegular }]}
placeholder="Enter your mobile number"
placeholderTextColor={colors.textLight}
placeholderTextColor={theme.colors.reverseText}
value={mobile}
onChangeText={setMobile}
keyboardType="phone-pad"
@@ -148,7 +170,8 @@ export default function LoginScreen() {
</View>
</Animated.View>
<Animated.View style={[styles.bottomSection, { transform: [{ scale: ctaScale }] }]}>
<Animated.View style={[styles.bottomSection, { transform: [{ scale: ctaScale }] }]}>
<MaterialRipple
onPress={handleContinue}
disabled={loading}
@@ -157,21 +180,16 @@ export default function LoginScreen() {
rippleCentered
rippleContainerBorderRadius={30}
>
<LinearGradient
colors={[colors.pastelPink, colors.pastelOrange]}
style={styles.buttonGradient}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
>
<View style={[styles.buttonGradient, { backgroundColor: theme.colors.reverseBackground }]} >
{loading ? (
<ActivityIndicator color={colors.white} />
<ActivityIndicator color={theme.colors.reverseText} />
) : (
<View style={styles.buttonContent}>
<Text style={styles.buttonText}>Continue</Text>
<Ionicons name="arrow-forward-circle-outline" size={22} color={colors.white} style={{marginLeft: 8}} />
<Text style={[styles.buttonText, { color: theme.colors.reverseText }]}>Continue</Text>
<Ionicons name="arrow-forward-circle-outline" size={22} color={theme.colors.reverseText} style={{marginLeft: 8}} />
</View>
)}
</LinearGradient>
</View>
</MaterialRipple>
<Text style={styles.footerText}>
Your privacy is our priority. Your information is always secure.
@@ -180,6 +198,7 @@ export default function LoginScreen() {
</View>
</KeyboardAvoidingView>
<CustomAlert
visible={alertInfo.visible}
title={alertInfo.title}
@@ -191,6 +210,7 @@ export default function LoginScreen() {
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
@@ -207,11 +227,11 @@ const styles = StyleSheet.create({
borderRadius: 16,
},
elevatedBlock: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 5 },
shadowOpacity: 0.15,
shadowRadius: 10,
elevation: 7,
shadowColor: '#ffffffff',
shadowOffset: { width: 1, height: 5 },
shadowOpacity: 1,
shadowRadius: 15,
elevation: 10,
},
keyboardAvoidingContainer: {
flex: 1,
@@ -227,8 +247,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
brand: {
fontSize: responsiveFontSize(2.8),
fontWeight: '700',
fontSize: responsiveFontSize(3),
color: colors.primary,
textAlign: 'center',
letterSpacing: 2,
@@ -236,33 +255,29 @@ const styles = StyleSheet.create({
textShadowColor: 'rgba(0, 0, 0, 0.1)',
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 2,
fontFamily: FontsFamily.nunitoExtraBold,
},
title: {
fontSize: responsiveFontSize(4),
fontWeight: 'bold',
color: colors.textDark,
fontSize: responsiveFontSize(5),
fontFamily: FontsFamily.nunitoBold,
textAlign: 'center',
marginBottom: 14,
},
subtitle: {
fontSize: responsiveFontSize(1.6),
color: colors.textDark,
fontFamily: FontsFamily.nunitoRegular,
textAlign: 'center',
lineHeight: 26,
maxWidth: 320,
},
inputWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.85)',
borderRadius: 36,
borderWidth: 2,
borderColor: colors.pastelPink,
paddingHorizontal: 20,
shadowColor: colors.primary,
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.2,
shadowRadius: 14,
paddingHorizontal: responsiveHeight(2),
elevation: 8,
},
inputIcon: {
@@ -271,9 +286,9 @@ const styles = StyleSheet.create({
input: {
flex: 1,
fontSize: responsiveFontSize(1.8),
fontWeight: '600',
fontFamily: FontsFamily.nunitoSemiBold,
color: colors.textLight,
height: 58,
height: responsiveHeight(5),
},
bottomSection: {
alignItems: 'center',
@@ -282,16 +297,13 @@ const styles = StyleSheet.create({
button: {
width: '100%',
borderRadius: 30,
shadowColor: colors.primary,
shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.4,
shadowRadius: 20,
elevation: 14,
marginTop: 20,
},
buttonGradient: {
borderRadius: 30,
paddingVertical: 18,
borderRadius:42,
paddingVertical: 14,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
@@ -302,8 +314,8 @@ const styles = StyleSheet.create({
},
buttonText: {
color: colors.white,
fontWeight: '800',
fontSize: responsiveFontSize(1.8),
fontFamily: FontsFamily.nunitoBold,
},
footerText: {
fontSize: responsiveFontSize(1.4),
@@ -311,5 +323,6 @@ const styles = StyleSheet.create({
marginTop: 28,
textAlign: 'center',
maxWidth: 300,
fontFamily: FontsFamily.nunitoRegular,
},
});
+87 -57
View File
@@ -21,6 +21,8 @@ import { LOGIN_URL, VARIFY_OTP_URL } from "../utils/ApiUrls";
import colors from "../components/colors";
import CustomAlert from "../components/CostomAlart";
import { getFcmToken } from "../utils/firebase/firebaseServices";
import { useTheme } from "../theme/ThemeProvider";
import FontsFamily from "../constents/FontsFamily";
const CELL_COUNT = 6;
const { width } = Dimensions.get('window');
@@ -50,6 +52,7 @@ export default function OtpVerificationScreen() {
buttons: [],
alertType: "info",
});
const { theme } = useTheme();
const inputs = useRef<Array<TextInput | null>>([]);
const route = useRoute<OtpVerifyRouteProp>();
@@ -133,17 +136,17 @@ export default function OtpVerificationScreen() {
await getFcmToken(res.data?.data?.userId);
let hasActionRun = false;
const successAction = () => {
if (hasActionRun) return;
hasActionRun = true;
hideAlert();
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'Home' }],
})
);
};
const successAction = () => {
if (hasActionRun) return;
hasActionRun = true;
hideAlert();
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'Home' }],
})
);
};
triggerAlert(
'Success!',
@@ -238,56 +241,64 @@ export default function OtpVerificationScreen() {
}
};
const colorsBlocks = [
colors.pastelPink,
colors.pastelBlue,
colors.pastelGreen,
colors.pastelOrange,
colors.pastelBlue,
colors.pastelPink,
colors.pastelGreen,
colors.pastelOrange,
colors.pastelPink,
const colorsBlocks = [
theme.colors.logincard,
theme.colors.logincard1,
theme.colors.logincard1,
theme.colors.logincard,
theme.colors.logincard,
theme.colors.logincard1,
theme.colors.logincard1,
theme.colors.logincard,
theme.colors.logincard,
];
return (
<View style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor={colors.pastelOrange} />
<View style={[styles.container, {backgroundColor: theme.colors.background }]}>
<StatusBar barStyle={theme.mode === "dark" ? "light-content" : "dark-content"} backgroundColor={colors.pastelOrange} />
<View style={styles.gridContainer}>
{colorsBlocks.map((bgColor, index) => (
<View
key={index}
style={[
styles.gridBlock,
{ backgroundColor: bgColor },
index % 2 === 0 ? styles.elevatedBlock : {},
]}
/>
))}
<View
key={index}
style={[
styles.gridBlock,
{ backgroundColor: bgColor, borderRadius: 16 },
index % 2 === 0 ? styles.elevatedBlock : {},
]}
/>
))}
</View>
<KeyboardAvoidingView style={styles.keyboardAvoidingContainer} behavior={Platform.OS === "ios" ? "padding" : undefined}>
<CustomAlert {...alertConfig} />
<View style={styles.contentPanel}>
<View style={[styles.contentPanel, { backgroundColor: theme.colors.reverseBackground }]}>
<View style={styles.topSection}>
<Text style={styles.title}>Verify Your Code</Text>
<Text style={styles.subtitle}>We’ve sent a 6-digit verification code to +91 {mobile}.</Text>
<Text style={[styles.title, { color: theme.colors.reverseText }]}>Verify Your Code</Text>
<Text style={[styles.subtitle, { color: theme.colors.reverseText }]}>We’ve sent a 6-digit verification code to +91 {mobile}.</Text>
<Animated.View style={[styles.otpRow, { opacity: fade, transform: [{ translateY: slide }] }]}>
<Animated.View style={[styles.otpRow, { transform: [{ translateY: slide }] }]}>
{Array.from({ length: CELL_COUNT }).map((_, i) => (
<TextInput
key={`otp-${i}`}
ref={(r) => { inputs.current[i] = r; }}
style={[styles.cell, code[i] ? styles.cellFilled : null]}
// UPDATED CELL STYLE
style={[
styles.cell,
code[i] ? styles.cellFilled : null,
{
backgroundColor: theme.colors.optCard,
color: theme.colors.reverseText,
}
]}
keyboardType="number-pad"
maxLength={i === 0 ? CELL_COUNT : 1}
value={code[i]}
onChangeText={(t) => (i === 0 ? handleFirstCellChange(t) : updateIndex(i, t))}
onKeyPress={(e) => handleKeyPress(i, e)}
placeholder="•"
placeholder="*"
placeholderTextColor="#BDBDBD"
textContentType="oneTimeCode"
/>
@@ -300,16 +311,16 @@ export default function OtpVerificationScreen() {
onPressIn={() => Animated.spring(ctaScale, { toValue: 0.97, useNativeDriver: true }).start()}
onPressOut={() => Animated.spring(ctaScale, { toValue: 1, useNativeDriver: true }).start()}
onPress={() => onSubmit(code.join(""), mobile)}
style={styles.button}
style={[styles.button, { backgroundColor: theme.colors.background }]}
>
<Text style={styles.buttonText}>Verify & Continue</Text>
<Text style={[styles.buttonText, { color: theme.colors.textPrimary }]}>Verify & Continue</Text>
</Pressable>
{timer > 0 ? (
<Text style={styles.footerText}>Resend code in 00:{timer.toString().padStart(2, "0")}</Text>
<Text style={[styles.footerText, { color: theme.colors.reverseText }]}>Resend code in 00:{timer.toString().padStart(2, "0")}</Text>
) : (
<Pressable onPress={resendOtp} disabled={isResending}>
<Text style={[styles.footerText, { color: colors.pastelPink, fontWeight: '600' }]}>
<Text style={[styles.footerText, { color: theme.colors.reverseText, fontWeight: '600' }]}>
{isResending ? "Resending..." : "Resend Code"}
</Text>
</Pressable>
@@ -335,16 +346,15 @@ const styles = StyleSheet.create({
borderRadius: 16,
},
elevatedBlock: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 5 },
shadowOpacity: 0.15,
shadowRadius: 10,
elevation: 7,
shadowColor: '#ffffffff',
shadowOffset: { width: 1, height: 5 },
shadowOpacity: 1,
shadowRadius: 15,
elevation: 10,
},
keyboardAvoidingContainer: { flex: 1, justifyContent: 'center' },
contentPanel: {
marginHorizontal: 24,
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderRadius: 32,
padding: 28,
shadowColor: '#000',
@@ -354,13 +364,33 @@ const styles = StyleSheet.create({
elevation: 12,
},
topSection: { alignItems: 'center', marginBottom: 24 },
title: { fontSize: 28, fontWeight: "bold", color: colors.textDark, textAlign: 'center', marginBottom: 12 },
subtitle: { fontSize: 16, color: colors.textLight, textAlign: 'center', marginBottom: 32, lineHeight: 24, maxWidth: 300 },
otpRow: { flexDirection: "row", justifyContent: "space-between", width: '100%', paddingHorizontal: 10 },
cell: { width: 48, height: 56, borderRadius: 12, borderWidth: 1.5, borderColor: colors.inputBorder, backgroundColor: colors.white, textAlign: "center", fontSize: 22, fontWeight: '700', color: colors.textDark, shadowColor: "#000", shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 8, elevation: 3 },
cellFilled: { borderColor: colors.primary, backgroundColor: '#F7F2FF' },
title: { fontSize: 28, color: colors.textDark, textAlign: 'center', marginBottom: 12 ,fontFamily: FontsFamily.nunitoExtraBold},
subtitle: { fontSize: 16, color: colors.textLight, textAlign: 'center', marginBottom: 32, lineHeight: 24, maxWidth: 300 ,fontFamily: FontsFamily.nunitoMedium},
otpRow: {
flexDirection: "row",
justifyContent: "space-between",
width: '100%',
paddingHorizontal: 0, // Removed padding to use full width
},
// FIX: Updated Cell Styling for clean smooth radius
cell: {
width: 45,
height: 52,
borderRadius: 14, // Smoother radius
textAlign: "center",
fontSize: 20,
fontWeight: "600",
elevation: 0, // Removed elevation for flat look (or keep if you want shadow)
overflow: 'hidden',
},
cellFilled: {
// Optional: Add specific style when filled if needed, handled inline above
},
bottomSection: { paddingBottom: Platform.OS === "ios" ? 20 : 10, paddingTop: 10 },
button: { backgroundColor: colors.pastelPink, paddingVertical: 18, borderRadius: 30, alignItems: "center", justifyContent: "center", shadowColor: colors.primary, shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.3, shadowRadius: 15, elevation: 8 ,borderWidth:2, borderColor:colors.pastelPink},
buttonText: { color: colors.white, fontWeight: "700", fontSize: 16 },
footerText: { textAlign: "center", color: colors.textLight, fontSize: 14, marginTop: 20 },
button: { backgroundColor: colors.pastelPink, paddingVertical: 18, borderRadius: 30, alignItems: "center", justifyContent: "center",},
buttonText: { color: colors.white, fontSize: 16, fontFamily: FontsFamily.nunitoBold },
footerText: { textAlign: "center", fontFamily: FontsFamily.nunitoBold, fontSize: 14, marginTop: 20 },
});
+19 -11
View File
@@ -10,6 +10,7 @@ import {
ListRenderItem,
Alert,
RefreshControl,
StatusBar,
} from "react-native";
import AppView from "../../components/AppView";
import colors from "../../components/colors";
@@ -23,6 +24,10 @@ import AnimatedCard from "../../components/common/AnimatedCard";
import { Ionicons } from "@react-native-vector-icons/ionicons";
import { apiCall } from "../../utils/ApiHelper";
import CustomAlert, { CustomAlertProps } from "../../components/CostomAlart";
import { useTheme } from "../../theme/ThemeProvider";
import FontsFamily from "../../constents/FontsFamily";
import { responsiveFontSize, responsiveHeight } from "react-native-responsive-dimensions";
import { useSafeAreaInsets } from "react-native-safe-area-context";
if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
@@ -80,6 +85,8 @@ const Home: React.FC = () => {
const [userDashboard, setUserDashboard] = useState<any>(null);
const [refreshing, setRefreshing] = useState(false);
const [statusData, setStatusData] = useState<any>(null);
const insets = useSafeAreaInsets();
const {theme} = useTheme();
const getDashboardData = async () => {
try {
const result = (await apiCall(DASHBOARD_URL, "GET")) as any;
@@ -186,7 +193,8 @@ const Home: React.FC = () => {
};
return (
<AppView barStyle="dark-content" style={styles.container}>
<View style={[ styles.container, {backgroundColor: theme.colors.background, paddingBottom: insets.bottom + responsiveHeight(10) }]}>
<StatusBar barStyle={theme.mode === "dark" ? "light-content" : "dark-content"}/>
<FlatList
data={actions}
numColumns={2}
@@ -195,18 +203,19 @@ const Home: React.FC = () => {
ListHeaderComponent={
<>
<View style={styles.header}>
<View>
<Text style={styles.greeting}>
Hello, <Text style={{ fontWeight: 'bold' }}>{user.firstName || apiUser?.firstName || "User"}</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 ,marginTop: responsiveHeight(2) }} >
<Text style={[styles.greeting, { color: theme.colors.textPrimary }]}>
Hello,
</Text>
<Text style={{ fontFamily:FontsFamily.nunitoExtraBold,color: theme.colors.textPrimary,fontSize:responsiveFontSize(2.5) }}>{user.firstName || apiUser?.firstName || "User"}</Text>
</View>
<TouchableOpacity onPress={() => navigation.navigate("Notification" as never)}>
<TouchableOpacity onPress={() => navigation.navigate("Notification" as never)} style={{ marginTop: responsiveHeight(2) ,}}>
{unreadCount > 0 && (
<View style={styles.badge}>
<Text style={styles.badgeText}>{unreadCount}</Text>
</View>
)}
<Ionicons name="notifications-outline" size={30} color={colors.textDark} />
<Ionicons name="notifications-outline" size={30} color={theme.colors.textPrimary} />
</TouchableOpacity>
</View>
@@ -217,7 +226,7 @@ const Home: React.FC = () => {
/>
<View style={styles.actionHeader}>
<Text style={styles.sectionTitle}>Quick Actions</Text>
<Text style={[styles.sectionTitle,{ color: theme.colors.textPrimary }]}>Quick Actions </Text>
</View>
</>
@@ -243,7 +252,7 @@ const Home: React.FC = () => {
onClose={() => setAlertVisible(false)}
/>
)}
</AppView>
</View>
);
};
@@ -252,7 +261,6 @@ export default Home;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.white,
},
header: {
flexDirection: "row",
@@ -262,8 +270,8 @@ const styles = StyleSheet.create({
paddingTop: 24,
paddingBottom: 16,
},
greeting: { fontSize: 22, color: colors.textDark },
sectionTitle: { fontSize: 20, fontWeight: "600", color: colors.textDark, flex: 1 },
greeting: { fontSize: 22,fontFamily: FontsFamily.nunitoLight },
sectionTitle: { fontSize: 20, fontWeight: "600", color: colors.textDark, flex: 1 ,fontFamily: FontsFamily.nunitoSemiBold},
actionHeader: {
flexDirection: "row",
justifyContent: "space-between",
+87 -101
View File
@@ -21,6 +21,8 @@ import {
responsiveWidth,
responsiveFontSize
} from "react-native-responsive-dimensions";
import { useTheme } from "../../theme/ThemeProvider";
import FontsFamily from "../../constents/FontsFamily";
type StatusData = {
role?: string;
@@ -73,7 +75,7 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({
const [showPayDialog, setShowPayDialog] = useState(false);
const [selectedGroupIds, setSelectedGroupIds] = useState<string[]>([]);
const [submitting, setSubmitting] = useState(false);
const {theme} = useTheme();
// console.log("user me : ", userDashboard)
// Preselect all due groups at mount and on change
useEffect(() => {
@@ -92,12 +94,12 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({
// Top grid info items
const infoItems = useMemo(
() => [
{ icon: <Ionicons name="person-outline" size={20} color={colors.primary} />, label: "Role", value: statusData?.role || "-" },
{ icon: <Ionicons name="pie-chart-outline" size={20} color={colors.primary} />, label: "Active Groups", value: userDashboard?.activeStatusMyGroupCount?.toString() || "0" },
{ icon: <Ionicons name="book-outline" size={20} color={colors.primary} />, label: "Joined Groups", value: userDashboard?.myJoinedGroups?.length?.toString() || "0" },
{ icon: <Ionicons name="people-outline" size={20} color={colors.primary} />, label: "My Groups", value: userDashboard?.myCreatedGroups?.length?.toString() || "0" },
{ icon: <Ionicons name="document-text-outline" size={20} color={colors.primary} />, label: "Total Due", value: `₹${userDashboard?.multiplePayAmount || 0}` },
{ icon: <Ionicons name="checkmark-done-outline" size={20} color={colors.primary} />, label: "Completed", value: userDashboard?.completeStatusMyGroupCount?.toString() || "0" },
{ icon: <Ionicons name="person-outline" size={20} color={theme.colors.black} />, label: "Role", value: statusData?.role || "-" },
{ icon: <Ionicons name="pie-chart-outline" size={20} color={theme.colors.black} />, label: "Active Groups", value: userDashboard?.activeStatusMyGroupCount?.toString() || "0" },
{ icon: <Ionicons name="book-outline" size={20} color={theme.colors.black} />, label: "Joined Groups", value: userDashboard?.myJoinedGroups?.length?.toString() || "0" },
{ icon: <Ionicons name="people-outline" size={20} color={theme.colors.black} />, label: "My Groups", value: userDashboard?.myCreatedGroups?.length?.toString() || "0" },
{ icon: <Ionicons name="document-text-outline" size={20} color={theme.colors.black} />, label: "Total Due", value: `₹${userDashboard?.multiplePayAmount || 0}` },
{ icon: <Ionicons name="checkmark-done-outline" size={20} color={theme.colors.black} />, label: "Completed", value: userDashboard?.completeStatusMyGroupCount?.toString() || "0" },
],
[userDashboard, statusData]
);
@@ -153,18 +155,18 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({
// Bottom bar
const renderDueBar = () => (
<View style={styles.dueBar}>
<Text style={styles.dueBarText}>
Total Due: <Text style={{ color: "#FFDE03", fontWeight: "bold" }}>₹{totalDue}</Text>
<View style={[ styles.dueBar, { borderTopColor: theme.colors.border, backgroundColor: theme.colors.gray }]}>
<Text style={[styles.dueBarText, { color: theme.colors.black }]}>
Total Due: <Text style={{ color: theme.colors.black, fontWeight: "bold" }}>₹{totalDue}</Text>
</Text>
<TouchableOpacity
style={styles.payAllButton}
style={[styles.payAllButton, { backgroundColor: theme.colors.black }]}
onPress={() => setShowPayDialog(true)}
disabled={dueGroups.length === 0}
activeOpacity={0.8}
>
<Ionicons name="wallet-outline" size={20} color="#000000ff" />
<Text style={styles.payAllButtonText}>Pay All</Text>
<Ionicons name="wallet-outline" size={20} color={theme.colors.white} />
<Text style={[styles.payAllButtonText, { color: theme.colors.white }]}>Pay All</Text>
</TouchableOpacity>
</View>
);
@@ -173,17 +175,14 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({
const renderPayDialog = () => (
<Modal visible={showPayDialog} transparent animationType="fade" onRequestClose={() => setShowPayDialog(false)}>
<View style={styles.payModalOverlay}>
<LinearGradient
colors={[colors.white, colors.white]}
start={{ x: 0.2, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.payModalBox}
<View
style={[styles.payModalBox, { backgroundColor: theme.colors.reverseBackground }]}
>
<View style={styles.payDialogHeader}>
<Ionicons name="card-outline" color="#000000ff" size={22} />
<Text style={styles.payDialogTitle}>Select Groups to Pay</Text>
<Ionicons name="card-outline" color={theme.colors.reverseText} size={22} />
<Text style={[styles.payDialogTitle, { color: theme.colors.reverseText }]}>Select Groups to Pay</Text>
<TouchableOpacity onPress={() => setShowPayDialog(false)}>
<Ionicons name="close-circle" color="#000000ff" size={24} />
<Ionicons name="close-circle" color={theme.colors.reverseText} size={24} />
</TouchableOpacity>
</View>
<ScrollView
@@ -193,70 +192,73 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({
{dueGroups.map((group) => (
<TouchableOpacity
key={group.groupId}
style={[
styles.payGroupOption,
selectedGroupIds.includes(group.groupId) && styles.payOptionSelected,
]}
style={[
styles.payGroupOption,
{ borderColor: theme.colors.background, borderWidth: 1 },
selectedGroupIds.includes(group.groupId) && {
...styles.payOptionSelected,
borderColor: theme.colors.reverseBackground,
},
]}
onPress={() => handleToggleGroup(group.groupId)}
activeOpacity={0.85}
>
<Ionicons
name={selectedGroupIds.includes(group.groupId) ? "checkbox" : "square-outline"}
size={22}
color={selectedGroupIds.includes(group.groupId) ? colors.primary : "#000000ff"}
color={selectedGroupIds.includes(group.groupId) ? theme.colors.reverseText : theme.colors.reverseText}
/>
<View style={{ flex: 1 }}>
<Text style={[styles.payGroupName, { color: "#000000ff" }]}>{group.groupName}</Text>
<Text style={styles.payDueText}>
<Text style={[styles.payGroupName, {color: theme.colors.reverseText}]}>{group.groupName}</Text>
<Text style={[styles.payDueText, {color: theme.colors.reverseText}]}>
₹{group.baseAmount}
{group.lateFee > 0 ? ` (Late: ₹${group.lateFee})` : ""}
</Text>
</View>
<Text style={[styles.payAmount, { color: "#4CAF50" }]}>₹{group.totalDue}</Text>
{/* <Text style={[styles.payAmount, { color: "#4CAF50" }]}>₹{group.totalDue}</Text> */}
</TouchableOpacity>
))}
</ScrollView>
<View style={styles.payDialogFooter}>
<Text style={styles.payDialogTotal}>
Total: <Text style={{ fontWeight: "bold", color: "#ff5f03ff" }}>₹{totalDueSelected}</Text>
<Text style={[styles.payDialogTotal, { color: theme.colors.reverseText }]}>
Total : <Text style={{ fontFamily: FontsFamily.nunitoBold, color: theme.colors.red }}>₹{totalDueSelected}</Text>
</Text>
<TouchableOpacity
style={[styles.payConfirmButton, { opacity: submitting ? 0.7 : 1 }]}
style={[styles.payConfirmButton, { opacity: submitting ? 0.7 : 1 , backgroundColor: theme.colors.background}]}
onPress={handlePayAll}
disabled={submitting || selectedGroupIds.length === 0}
activeOpacity={0.8}
>
{submitting ? (
<ActivityIndicator color="#fff" />
<ActivityIndicator color={theme.colors.textPrimary} />
) : (
<>
<Ionicons name="checkmark-circle" size={22} color="#ffffffff" />
<Text style={styles.payButtonText}>Pay Selected</Text>
<Ionicons name="checkmark-circle" size={22} color={theme.colors.textPrimary} />
<Text style={[styles.payButtonText, { fontFamily: FontsFamily.nunitoSemiBold ,color: theme.colors.textPrimary}]}>Pay Selected</Text>
</>
)}
</TouchableOpacity>
</View>
</LinearGradient>
</View>
</View>
</Modal>
);
return (
<View style={styles.container}>
<LinearGradient
colors={[colors.primary, colors.pastelPink]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.card}
<View
style={[styles.card, { backgroundColor: theme.colors.reverseBackground }]}
>
<View style={styles.headerSection}>
<View style={styles.titleWithIcon}>
<Ionicons name="stats-chart" size={24} color="#fff" style={styles.headerIcon} />
<Text style={styles.header}>Profile Overview</Text>
<Ionicons name="stats-chart" size={24} color={theme.colors.reverseText} style={styles.headerIcon} />
<Text style={[styles.header, { color: theme.colors.reverseText} ]}>Profile Overview</Text>
</View>
<View style={styles.statusBadge}>
<Ionicons name="shield-checkmark" size={16} color="#fff" />
<Text style={styles.statusBadgeText}>
<View style={[styles.statusBadge, { backgroundColor: theme.colors.background}]}>
<Ionicons name="shield-checkmark" size={16} color={theme.colors.textPrimary} />
<Text style={[styles.statusBadgeText, { color: theme.colors.textPrimary }]}>
{statusData?.profileStatus ? "Verified" : "Pending"}
</Text>
</View>
@@ -265,16 +267,16 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({
<View style={styles.leftSection}>
<View style={styles.infoGrid}>
{infoItems.map((item, idx) => (
<View key={item.label} style={styles.infoCard}>
<View key={item.label} style={[styles.infoCard,{backgroundColor: theme.colors.background}]}>
<View style={styles.infoCardHeader}>
<View style={styles.iconContainer}>
<View style={[styles.iconContainer, {backgroundColor: theme.colors.optCard || theme.colors.card}]}>
{item.icon}
</View>
<Text style={styles.infoLabel} >
<Text style={[styles.infoLabel,{color: theme.colors.textPrimary}]} >
{item.label}
</Text>
</View>
<Text style={[styles.infoValue, { color: getValueColor(item.value) }]} numberOfLines={1}>
<Text style={[styles.infoValue, { color: theme.colors.textPrimary }]} numberOfLines={1}>
{item.value}
</Text>
</View>
@@ -296,16 +298,16 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({
</View>
)}
</View>
<View style={styles.infoCardOne}>
<Text style={{ color: "#fff", fontSize: responsiveWidth(2.3)}}>Total Paid Amount <Text style={{ fontWeight: "900"}}>{userDashboard?.totalPaidAmountMonth}</Text></Text>
{/* <Text style={{ color: "#fff", fontWeight: "400"}}>Total Paid Amount {userDashboard?.totalPaidAmountMonth}</Text> */}
<View style={[styles.infoCardOne,{backgroundColor: theme.colors.background}]}>
<Text style={{ color: theme.colors.textPrimary, fontSize: responsiveWidth(3), fontFamily: FontsFamily.nunitoSemiBold,}}>Total Paid Amount</Text>
<Text style={{ color: theme.colors.textPrimary, fontWeight: "400", fontFamily: FontsFamily.nunitoRegular}}>{userDashboard?.totalPaidAmountMonth}</Text>
</View>
</TouchableOpacity>
</View>
</View>
{renderDueBar()}
</LinearGradient>
</View>
{renderPayDialog()}
@@ -377,8 +379,8 @@ const styles = StyleSheet.create({
marginRight: 8
},
header: {
fontSize: responsiveWidth(3.5),
fontWeight: "700",
fontSize: responsiveWidth(4.5),
fontFamily: FontsFamily.nunitoBold,
color: "#fff"
},
statusBadge: {
@@ -411,14 +413,11 @@ const styles = StyleSheet.create({
gap: 10
},
infoCard: {
backgroundColor: 'rgba(255,255,255,0.15)',
borderRadius: 14,
padding: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.2)',
flex: 1,
minWidth: '45%',
marginBottom: 8,
borderRadius:12
},
infoCardOne: {
backgroundColor: 'rgba(255,255,255,0.15)',
@@ -436,25 +435,22 @@ const styles = StyleSheet.create({
marginBottom: 6
},
iconContainer: {
width: 28,
height: 28,
backgroundColor: '#fff',
borderRadius: 8,
width: responsiveWidth(8),
height: responsiveHeight(3.5),
borderRadius: responsiveWidth(2),
justifyContent: 'center',
alignItems: 'center',
marginRight: 8,
},
infoLabel: {
color: "rgba(255,255,255,0.9)",
fontSize: responsiveWidth(2),
fontWeight: '500',
fontSize: responsiveWidth(3),
fontFamily: FontsFamily.nunitoSemiBold,
flexShrink: 1,
},
infoValue: {
fontWeight: "700",
fontSize: responsiveWidth(2.5),
fontFamily: FontsFamily.nunitoBold,
fontSize: responsiveWidth(3),
textAlign: 'left',
color: "#fff"
},
profileSection: {
width: 90,
@@ -498,16 +494,13 @@ const styles = StyleSheet.create({
alignItems: "center",
padding: 16,
borderTopWidth: 1,
borderColor: "rgba(255,255,255,0.15)",
backgroundColor: "rgba(0,0,0,0.15)",
},
dueBarText: {
color: "#fff",
fontSize: responsiveWidth(2.5),
fontWeight: "600"
fontFamily: FontsFamily.nunitoExtraBold,
fontSize: responsiveWidth(4),
},
payAllButton: {
backgroundColor: "#ffffffff",
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 16,
@@ -516,8 +509,7 @@ const styles = StyleSheet.create({
gap: 6,
},
payAllButtonText: {
color: "#000000ff",
fontWeight: "bold",
fontFamily: FontsFamily.nunitoBold,
fontSize: responsiveWidth(2.5)
},
payModalOverlay: {
@@ -527,7 +519,7 @@ const styles = StyleSheet.create({
alignItems: "center",
},
payModalBox: {
width: "92%",
width: "90%",
borderRadius: 24,
overflow: "hidden",
alignItems: "center",
@@ -547,9 +539,10 @@ const styles = StyleSheet.create({
borderColor: "rgba(255,255,255,0.1)",
},
payDialogTitle: {
color: "#000000ff",
fontSize: responsiveWidth(1.8),
fontWeight: "700"
fontSize: responsiveWidth(4),
fontFamily: FontsFamily.nunitoBold,
},
payGroupOption: {
flexDirection: "row",
@@ -557,24 +550,20 @@ const styles = StyleSheet.create({
gap: 16,
paddingVertical: 12,
paddingHorizontal: 14,
borderColor: colors.primary,
borderWidth: 1,
borderRadius: 12,
marginBottom: 8,
},
payOptionSelected: {
backgroundColor: "rgba(166, 49, 255, 0.2)",
borderColor: colors.primary,
borderWidth: 1,
},
payGroupName: {
color: "#000000ff",
fontWeight: "700",
fontSize: responsiveWidth(2.5)
fontFamily: FontsFamily.nunitoSemiBold,
fontSize: responsiveWidth(3)
},
payDueText: {
color: "rgba(0, 0, 0, 0.8)",
fontSize: responsiveWidth(1.3),
fontSize: responsiveWidth(3),
fontFamily: FontsFamily.nunitoRegular,
marginTop: 2
},
payAmount: {
@@ -591,26 +580,23 @@ const styles = StyleSheet.create({
paddingHorizontal: 18,
},
payDialogTotal: {
color: "#000000ff",
fontWeight: "600",
fontSize: responsiveWidth(1.7)
fontFamily: FontsFamily.nunitoBold,
fontSize: responsiveWidth(3.5)
},
payConfirmButton: {
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.primary,
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 20,
paddingVertical: 8,
paddingHorizontal: 18,
borderRadius: 12,
elevation: 4,
borderColor: colors.primary,
borderWidth: 1,
gap: 8,
},
payButtonText: {
color: "#ffffffff",
fontWeight: "bold",
fontSize: responsiveWidth(2.5)
fontSize: responsiveWidth(3),
},
modalOverlay: {
flex: 1,
+1 -1
View File
@@ -1,4 +1,4 @@
// src/screens/ManagementScreen.tsx
// src/screens/ManagementScreen.tsx
import React, { useState, useCallback, FC, useEffect } from "react";
import {
View,