late fee added
This commit is contained in:
Generated
+148
@@ -34,6 +34,7 @@
|
|||||||
"react-native-paper": "^5.14.5",
|
"react-native-paper": "^5.14.5",
|
||||||
"react-native-safe-area-context": "^5.6.1",
|
"react-native-safe-area-context": "^5.6.1",
|
||||||
"react-native-screens": "^4.16.0",
|
"react-native-screens": "^4.16.0",
|
||||||
|
"react-native-svg": "^15.13.0",
|
||||||
"react-native-walkthrough-tooltip": "^1.6.0",
|
"react-native-walkthrough-tooltip": "^1.6.0",
|
||||||
"react-redux": "^9.2.0"
|
"react-redux": "^9.2.0"
|
||||||
},
|
},
|
||||||
@@ -4811,6 +4812,12 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
@@ -5465,6 +5472,47 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"domutils": "^3.0.1",
|
||||||
|
"nth-check": "^2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-tree": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mdn-data": "2.0.14",
|
||||||
|
"source-map": "^0.6.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-what": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
@@ -5722,6 +5770,61 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -5776,6 +5879,18 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/env-paths": {
|
"node_modules/env-paths": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
||||||
@@ -9282,6 +9397,12 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mdn-data": {
|
||||||
|
"version": "2.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||||
|
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
|
||||||
|
"license": "CC0-1.0"
|
||||||
|
},
|
||||||
"node_modules/media-typer": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
@@ -12377,6 +12498,18 @@
|
|||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nullthrows": {
|
"node_modules/nullthrows": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
||||||
@@ -13457,6 +13590,21 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-svg": {
|
||||||
|
"version": "15.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.13.0.tgz",
|
||||||
|
"integrity": "sha512-/YPK+PAAXg4T0x2d2vYPvqqAhOYid2bRKxUVT7STIyd1p2JxWmsGQkfZxXCkEFN7TwLfIyVlT5RimT91Pj/qXw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"css-select": "^5.1.0",
|
||||||
|
"css-tree": "^1.1.3",
|
||||||
|
"warn-once": "0.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native-swipe-gestures": {
|
"node_modules/react-native-swipe-gestures": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"react-native-paper": "^5.14.5",
|
"react-native-paper": "^5.14.5",
|
||||||
"react-native-safe-area-context": "^5.6.1",
|
"react-native-safe-area-context": "^5.6.1",
|
||||||
"react-native-screens": "^4.16.0",
|
"react-native-screens": "^4.16.0",
|
||||||
|
"react-native-svg": "^15.13.0",
|
||||||
"react-native-walkthrough-tooltip": "^1.6.0",
|
"react-native-walkthrough-tooltip": "^1.6.0",
|
||||||
"react-redux": "^9.2.0"
|
"react-redux": "^9.2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import React, { useRef } from "react";
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
Animated,
|
||||||
|
} from "react-native";
|
||||||
|
import LinearGradient from "react-native-linear-gradient";
|
||||||
|
import { Ionicons, IoniconsIconName } from "@react-native-vector-icons/ionicons";
|
||||||
|
import colors from "../colors";
|
||||||
|
|
||||||
|
type CardProps = {
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
iconName?: IoniconsIconName;
|
||||||
|
onPress?: () => void;
|
||||||
|
backOne?: string;
|
||||||
|
backTwo?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
const AnimatedCard: React.FC<CardProps> = ({
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
iconName = "ellipse",
|
||||||
|
onPress,
|
||||||
|
backOne = '#4facfe',
|
||||||
|
backTwo = '#00f2fe'
|
||||||
|
}) => {
|
||||||
|
const scale = useRef(new Animated.Value(1)).current;
|
||||||
|
|
||||||
|
const handlePressIn = () => {
|
||||||
|
Animated.spring(scale, {
|
||||||
|
toValue: 0.96,
|
||||||
|
useNativeDriver: true,
|
||||||
|
friction: 4,
|
||||||
|
}).start();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePressOut = () => {
|
||||||
|
Animated.spring(scale, {
|
||||||
|
toValue: 1,
|
||||||
|
useNativeDriver: true,
|
||||||
|
friction: 4,
|
||||||
|
}).start(() => onPress && onPress());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableWithoutFeedback
|
||||||
|
onPressIn={handlePressIn}
|
||||||
|
onPressOut={handlePressOut}
|
||||||
|
>
|
||||||
|
<Animated.View style={[styles.card, { transform: [{ scale }] }]}>
|
||||||
|
<LinearGradient
|
||||||
|
colors={[backOne, backTwo]}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
style={styles.gradient}
|
||||||
|
>
|
||||||
|
{/* Decorative Graphics */}
|
||||||
|
<View style={styles.graphicCircleLarge} />
|
||||||
|
<View style={styles.graphicCircleMedium} />
|
||||||
|
<View style={styles.graphicCircleSmall} />
|
||||||
|
<View style={styles.graphicTriangleTop} />
|
||||||
|
<View style={styles.graphicTriangleBottom} />
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<View style={styles.content}>
|
||||||
|
{iconName && (
|
||||||
|
<Ionicons
|
||||||
|
name={iconName}
|
||||||
|
size={32}
|
||||||
|
color="#fff"
|
||||||
|
style={styles.icon}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<Text style={styles.title}>{title}</Text>
|
||||||
|
{subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</LinearGradient>
|
||||||
|
</Animated.View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
card: {
|
||||||
|
// marginHorizontal: 16,
|
||||||
|
marginVertical: 10,
|
||||||
|
borderRadius: 20,
|
||||||
|
overflow: "hidden",
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.25,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 8,
|
||||||
|
},
|
||||||
|
gradient: {
|
||||||
|
minHeight: 120,
|
||||||
|
position: "relative",
|
||||||
|
},
|
||||||
|
// Graphics
|
||||||
|
graphicCircleLarge: {
|
||||||
|
position: "absolute",
|
||||||
|
top: -20,
|
||||||
|
right: -20,
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: 40,
|
||||||
|
backgroundColor: "rgba(124, 80, 80, 0.42)",
|
||||||
|
},
|
||||||
|
graphicCircleMedium: {
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 30,
|
||||||
|
left: -15,
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
borderRadius: 30,
|
||||||
|
backgroundColor: "rgba(122, 41, 41, 0.39)",
|
||||||
|
},
|
||||||
|
graphicCircleSmall: {
|
||||||
|
position: "absolute",
|
||||||
|
bottom: -10,
|
||||||
|
right: 40,
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: "rgba(207, 110, 80, 0.25)",
|
||||||
|
},
|
||||||
|
graphicTriangleTop: {
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderRightWidth: 60,
|
||||||
|
borderTopWidth: 60,
|
||||||
|
borderRightColor: "transparent",
|
||||||
|
borderTopColor: "rgba(143, 62, 62, 0.31)",
|
||||||
|
},
|
||||||
|
graphicTriangleBottom: {
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderLeftWidth: 40,
|
||||||
|
borderBottomWidth: 40,
|
||||||
|
borderLeftColor: "transparent",
|
||||||
|
borderBottomColor: "rgba(172, 51, 51, 0.27)",
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flexDirection: "row", // stack vertically
|
||||||
|
alignItems: "center", // center horizontally
|
||||||
|
justifyContent: "center", // center vertically
|
||||||
|
padding: 20,
|
||||||
|
position: "relative",
|
||||||
|
zIndex: 1,
|
||||||
|
alignSelf: "center",
|
||||||
|
flex:1
|
||||||
|
}
|
||||||
|
,
|
||||||
|
icon: {
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
// alignItems: "center", // centers title and subtitle
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: "700",
|
||||||
|
color: "#fff",
|
||||||
|
marginBottom: 4,
|
||||||
|
textTransform:'uppercase'
|
||||||
|
// textAlign: "center",
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: colors.primary,
|
||||||
|
fontWeight: "500",
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AnimatedCard;
|
||||||
@@ -18,6 +18,7 @@ import EditBankDetails from "../screens/profile/EditBankDetails";
|
|||||||
import ManageJoinedGroup from "../screens/management/ManageJoinedGroup";
|
import ManageJoinedGroup from "../screens/management/ManageJoinedGroup";
|
||||||
import InviteScreen from "../screens/InviteScreen";
|
import InviteScreen from "../screens/InviteScreen";
|
||||||
import ManagementTabNavigation from "../screens/management/ManagementTabNavigation";
|
import ManagementTabNavigation from "../screens/management/ManagementTabNavigation";
|
||||||
|
import TransactionsList from "../screens/transaction/TransactionList";
|
||||||
|
|
||||||
export type RootStackParamList = {
|
export type RootStackParamList = {
|
||||||
Splash: undefined;
|
Splash: undefined;
|
||||||
@@ -37,7 +38,7 @@ export type RootStackParamList = {
|
|||||||
EditBankDetails: { currentBankName: string | null; currentAccountNumber: string | null; currentBranchCode: string | null };
|
EditBankDetails: { currentBankName: string | null; currentAccountNumber: string | null; currentBranchCode: string | null };
|
||||||
ManageJoinedGroup: { groupId: string | number };
|
ManageJoinedGroup: { groupId: string | number };
|
||||||
Invite: { groupId: string | number; code: string | number };
|
Invite: { groupId: string | number; code: string | number };
|
||||||
ManagementTabs:any
|
ManagementTabs:{ groupId: string | number;}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator<RootStackParamList>();
|
const Stack = createNativeStackNavigator<RootStackParamList>();
|
||||||
@@ -89,6 +90,7 @@ const linking = {
|
|||||||
<Stack.Screen name="ManageJoinedGroup" component={ManageJoinedGroup} options={{ headerShown: false }} />
|
<Stack.Screen name="ManageJoinedGroup" component={ManageJoinedGroup} options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="Invite" component={InviteScreen} options={{ headerShown: false }} />
|
<Stack.Screen name="Invite" component={InviteScreen} options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="ManagementTabs" component={ManagementTabNavigation} options={{ headerShown: false }} />
|
<Stack.Screen name="ManagementTabs" component={ManagementTabNavigation} options={{ headerShown: false }} />
|
||||||
|
<Stack.Screen name="TransactionList" component={TransactionsList} options={{ headerShown: false }} />
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import colors from "../../../components/colors";
|
|||||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||||
import Header from "../../../components/common/Header";
|
import Header from "../../../components/common/Header";
|
||||||
import { apiCall } from "../../../utils/ApiHelper";
|
import { apiCall } from "../../../utils/ApiHelper";
|
||||||
import { CONTRIBUTE, GROUP_ALL_MEMBER } from "../../../utils/ApiUrls";
|
import { BASE_URL, CONTRIBUTE, GROUP_ALL_MEMBER } from "../../../utils/ApiUrls";
|
||||||
import ContributionScreen from "../../ContributionScreen";
|
import ContributionScreen from "../../ContributionScreen";
|
||||||
|
|
||||||
// Define type for a member
|
// Define type for a member
|
||||||
@@ -108,10 +108,20 @@ const GroupDetails = () => {
|
|||||||
|
|
||||||
const confirmPayment = async () => {
|
const confirmPayment = async () => {
|
||||||
try {
|
try {
|
||||||
|
const res = await apiCall(`${BASE_URL}payments/contribute/${group?.id}?amount=${group?.collected}`, 'POST') as any
|
||||||
|
console.log("payment response : ", res)
|
||||||
|
if (res.status) {
|
||||||
|
ToastAndroid.show('Payment done!', ToastAndroid.SHORT)
|
||||||
|
}
|
||||||
|
if (res.status === 400) {
|
||||||
|
ToastAndroid.show(res?.data?.message || 'Payment done!', ToastAndroid.SHORT)
|
||||||
|
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log("payment error : ", error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
const renderMember = ({ item, index }: { item: any; index: number }) => (
|
const renderMember = ({ item, index }: { item: any; index: number }) => (
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Alert,
|
Alert,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
|
ScrollView,
|
||||||
|
Button,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useNavigation } from "@react-navigation/native";
|
import { useNavigation } from "@react-navigation/native";
|
||||||
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||||
@@ -15,6 +17,7 @@ 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 { GET_JOINED_GROUP_URL } from "../../../utils/ApiUrls";
|
||||||
import * as Keychain from "react-native-keychain";
|
import * as Keychain from "react-native-keychain";
|
||||||
|
import AnimatedCard from "../../../components/common/AnimatedCard";
|
||||||
|
|
||||||
type NavigationProp = NativeStackNavigationProp<
|
type NavigationProp = NativeStackNavigationProp<
|
||||||
RootStackParamList,
|
RootStackParamList,
|
||||||
@@ -47,13 +50,12 @@ const UserHome = () => {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const onRefresh = async () => {
|
const onRefresh = async () => {
|
||||||
setRefreshing(true)
|
setRefreshing(true)
|
||||||
await handleGroup()
|
await handleGroup()
|
||||||
setRefreshing(false)
|
setRefreshing(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleGroup = async () => {
|
const handleGroup = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -120,9 +122,9 @@ const UserHome = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<>
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
<View style={[styles.statsRow, { marginVertical: 24 }]}>
|
<View style={[styles.statsRow, { marginTop:12 }]}>
|
||||||
<View style={styles.statBox}>
|
<View style={styles.statBox}>
|
||||||
<Text style={styles.statValue}>{totalGroups}</Text>
|
<Text style={styles.statValue}>{totalGroups}</Text>
|
||||||
<Text style={styles.statLabel}>Groups Joined</Text>
|
<Text style={styles.statLabel}>Groups Joined</Text>
|
||||||
@@ -132,6 +134,10 @@ const UserHome = () => {
|
|||||||
<Text style={styles.statLabel}>Cycle Contribution</Text>
|
<Text style={styles.statLabel}>Cycle Contribution</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
{/* <Button title="Transaction List" onPress={()=>navigation.navigate('TransactionList')}/> */}
|
||||||
|
<AnimatedCard
|
||||||
|
|
||||||
|
onPress={() => navigation.navigate("TransactionList")} title={"transaction list"} iconName="calculator-outline" subtitle="View your transaction history" />
|
||||||
|
|
||||||
{/* Groups List */}
|
{/* Groups List */}
|
||||||
<Text style={styles.sectionTitle}>Your Groups</Text>
|
<Text style={styles.sectionTitle}>Your Groups</Text>
|
||||||
@@ -143,7 +149,7 @@ const UserHome = () => {
|
|||||||
onRefresh={onRefresh}
|
onRefresh={onRefresh}
|
||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
/>
|
/>
|
||||||
</View>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ import { RequestCard } from "../../components/RequestCard";
|
|||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { UserRequested } from "../../components/UserRequested";
|
import { UserRequested } from "../../components/UserRequested";
|
||||||
import { RootTabParamList } from "../../navigation/BottomTabs"; // Adjust import path
|
import { RootTabParamList } from "../../navigation/BottomTabs"; // Adjust import path
|
||||||
|
import { RootStackParamList } from "../../navigation/AppNavigator";
|
||||||
|
|
||||||
// --- TYPE DEFINITIONS ---
|
// --- TYPE DEFINITIONS ---
|
||||||
type GroupManagementNavProp = StackNavigationProp<RootTabParamList, 'Management'>;
|
type GroupManagementNavProp = StackNavigationProp<RootStackParamList, 'ManagementTabs'>;
|
||||||
type ManagementRouteProp = RouteProp<RootTabParamList, 'Management'>;
|
type ManagementRouteProp = RouteProp<RootTabParamList, 'Management'>;
|
||||||
type ActiveTab = "My Groups" | "Joined Groups" | "Requests" | "Your Requests";
|
type ActiveTab = "My Groups" | "Joined Groups" | "Requests" | "Your Requests";
|
||||||
|
|
||||||
|
|||||||
+171
-122
@@ -1,61 +1,91 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { View, Text, StyleSheet, ActivityIndicator, ScrollView, RefreshControl, Alert, TouchableOpacity } from 'react-native';
|
import { View, Text, StyleSheet, ActivityIndicator, ScrollView, RefreshControl, Alert, TouchableOpacity } from 'react-native';
|
||||||
import { useRoute, useNavigation } from '@react-navigation/native';
|
import { useRoute, useNavigation, RouteProp } from '@react-navigation/native';
|
||||||
import * as Keychain from 'react-native-keychain';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||||
import colors from '../../components/colors'; // Assuming you have a colors file
|
import colors from '../../components/colors';
|
||||||
import AppView from '../../components/AppView';
|
import AppView from '../../components/AppView';
|
||||||
import { apiCall } from '../../utils/ApiHelper';
|
import { apiCall } from '../../utils/ApiHelper';
|
||||||
|
|
||||||
|
// --- TYPE DEFINITIONS ---
|
||||||
|
|
||||||
|
// Generic API Response Wrapper
|
||||||
interface ApiResponse<T> {
|
interface ApiResponse<T> {
|
||||||
status: 'success' | 'error';
|
status: 'success' | 'error';
|
||||||
data: T;
|
data: T;
|
||||||
message?: string;
|
message?: string;
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the shape of your payment details data
|
// Type for an individual participant in the payment details
|
||||||
interface PaymentDetails {
|
interface Participant {
|
||||||
// Add properties based on your API response, for example:
|
userId: string;
|
||||||
amount: number;
|
userName: string;
|
||||||
currency: string;
|
firstName: string;
|
||||||
dueDate: string;
|
lastName: string;
|
||||||
paidBy: string[];
|
mobile: string;
|
||||||
|
contributionAmount: number;
|
||||||
|
paidAmount: number;
|
||||||
|
status: 'SUCCESS' | 'PENDING';
|
||||||
|
lateFee: number;
|
||||||
|
daysLate: number;
|
||||||
|
lateFeeWaived: boolean;
|
||||||
}
|
}
|
||||||
const Payments = () => {
|
|
||||||
const route = useRoute<any>();
|
// Fully typed payment details based on your JSON response
|
||||||
const navigation = useNavigation<any>();
|
interface PaymentDetails {
|
||||||
|
groupId: string;
|
||||||
|
groupName: string;
|
||||||
|
currentCycle: number;
|
||||||
|
cycleType: string;
|
||||||
|
contributionAmount: number;
|
||||||
|
totalContributedAmount: number;
|
||||||
|
lateFeeMode: string;
|
||||||
|
lateFeeValue: number;
|
||||||
|
notReceivedAmount: number;
|
||||||
|
totalLateFee: number;
|
||||||
|
totalDueWithLateFee: number;
|
||||||
|
participants: Participant[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming your navigation stack params look something like this
|
||||||
|
type PaymentStackParamList = {
|
||||||
|
Payments: { groupId: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
type PaymentsRouteProp = RouteProp<PaymentStackParamList, 'Payments'>;
|
||||||
|
type PaymentsNavigationProp = StackNavigationProp<PaymentStackParamList>;
|
||||||
|
|
||||||
|
const Payments: React.FC = () => {
|
||||||
|
const route = useRoute<PaymentsRouteProp>();
|
||||||
|
const navigation = useNavigation<PaymentsNavigationProp>();
|
||||||
const { groupId } = route.params;
|
const { groupId } = route.params;
|
||||||
|
|
||||||
const [paymentDetails, setPaymentDetails] = useState<any>(null);
|
const [paymentDetails, setPaymentDetails] = useState<PaymentDetails | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
const fetchPaymentDetails = useCallback(async () => {
|
const fetchPaymentDetails = useCallback(async () => {
|
||||||
setLoading(true);
|
if (!refreshing) setLoading(true);
|
||||||
try {
|
try {
|
||||||
// The apiCall function likely handles tokens internally, so we call it directly.
|
const response = await apiCall(
|
||||||
const response = await apiCall(
|
`https://exotic-humpback-mistakenly.ngrok-free.app/api/payments/current/payment/details/${groupId}`,
|
||||||
`https://exotic-humpback-mistakenly.ngrok-free.app/api/payments/current/payment/details/${groupId}`,
|
'GET'
|
||||||
'GET'
|
) as ApiResponse<PaymentDetails>;
|
||||||
) as ApiResponse<PaymentDetails>; // Cast the response to the correct type
|
|
||||||
|
|
||||||
console.log("Payment Details Response:", response);
|
if (response && response.statusCode === 200) {
|
||||||
|
setPaymentDetails(response.data);
|
||||||
// Check the response and status property for success
|
} else {
|
||||||
if (response && response.statusCode === 200) {
|
Alert.alert("Error", response?.message || "Failed to fetch payment details.");
|
||||||
setPaymentDetails(response.data);
|
}
|
||||||
} else {
|
} catch (error: any) {
|
||||||
// Use the message from the response object
|
console.error("Fetch payment details error:", error);
|
||||||
Alert.alert("Error", response?.message || "Failed to fetch payment details.");
|
Alert.alert("Error", error.message || "An unexpected error occurred.");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
setRefreshing(false);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
}, [groupId, refreshing]);
|
||||||
console.error("Fetch payment details error:", error);
|
|
||||||
Alert.alert("Error", error.message || "An unexpected error occurred while fetching payment details.");
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
setRefreshing(false);
|
|
||||||
}
|
|
||||||
}, [groupId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchPaymentDetails();
|
fetchPaymentDetails();
|
||||||
@@ -63,35 +93,41 @@ const fetchPaymentDetails = useCallback(async () => {
|
|||||||
|
|
||||||
const onRefresh = () => {
|
const onRefresh = () => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
fetchPaymentDetails();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderParticipant = (participant: any) => (
|
// --- ENHANCED: renderParticipant now shows late fee details ---
|
||||||
<View key={participant.userId} style={styles.participantCard}>
|
const renderParticipant = (participant: Participant) => {
|
||||||
<View style={styles.participantInfo}>
|
const isPending = participant.status === 'PENDING';
|
||||||
<Text style={styles.participantName}>{participant.firstName} {participant.lastName}</Text>
|
return (
|
||||||
<Text style={styles.participantMobile}>{participant.mobile}</Text>
|
<View key={participant.userId} style={styles.participantCard}>
|
||||||
</View>
|
<View style={styles.participantInfo}>
|
||||||
<View style={styles.paymentStatus}>
|
<Text style={styles.participantName}>{participant.firstName} {participant.lastName}</Text>
|
||||||
<Text style={[
|
<Text style={styles.participantMobile}>{participant.mobile}</Text>
|
||||||
styles.statusText,
|
</View>
|
||||||
{ color: participant.status === 'PENDING' ? colors.warning : colors.success }
|
<View style={styles.paymentStatus}>
|
||||||
]}>
|
<Text style={[
|
||||||
{participant.status}
|
styles.statusText,
|
||||||
</Text>
|
{ color: isPending ? '#e0d626ff' : '#18b107ff' }
|
||||||
<Text style={styles.amountText}>
|
]}>
|
||||||
₹{participant.paidAmount} / ₹{participant.contributionAmount}
|
{participant.status}
|
||||||
</Text>
|
</Text>
|
||||||
{/* {participant.status === 'PENDING' && (
|
<Text style={styles.amountText}>
|
||||||
<TouchableOpacity style={styles.payButton}>
|
{isPending ?`₹${participant.payAmount}`:`₹${participant.paidAmount}`}
|
||||||
<Text style={styles.payButtonText}>Pay Now</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
|
||||||
)} */}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (loading) {
|
{/* --- NEW: Conditionally render late payment stats --- */}
|
||||||
|
{isPending && participant.daysLate > 0 && (
|
||||||
|
<View style={styles.lateFeeContainer}>
|
||||||
|
<Text style={styles.lateFeeText}>Late Fee: ₹{participant.lateFee}</Text>
|
||||||
|
<Text style={styles.lateFeeText}>Days Late: {participant.daysLate}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading && !refreshing) {
|
||||||
return <ActivityIndicator style={styles.centered} size="large" color={colors.primary} />;
|
return <ActivityIndicator style={styles.centered} size="large" color={colors.primary} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,34 +143,34 @@ const fetchPaymentDetails = useCallback(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppView>
|
<AppView paddingHorizontal={0}>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.container}
|
style={styles.container}
|
||||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={[colors.primary]} tintColor={colors.primary} />}
|
||||||
>
|
>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={styles.headerTitle}>{paymentDetails.groupName}</Text>
|
<Text style={styles.headerTitle}>{paymentDetails.groupName}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.summaryContainer}>
|
<View style={styles.summaryContainer}>
|
||||||
<View style={styles.summaryBox}>
|
<View style={styles.summaryBox}>
|
||||||
<Text style={styles.summaryLabel}>Current Cycle</Text>
|
<Text style={styles.summaryLabel}>Current Cycle</Text>
|
||||||
<Text style={styles.summaryValue}>{paymentDetails.currentCycle}</Text>
|
<Text style={styles.summaryValue}>{paymentDetails.currentCycle}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.summaryBox}>
|
||||||
|
<Text style={styles.summaryLabel}>Contribution</Text>
|
||||||
|
<Text style={styles.summaryValue}>₹{paymentDetails.contributionAmount}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.summaryBox}>
|
||||||
|
<Text style={styles.summaryLabel}>Total Due</Text>
|
||||||
|
<Text style={[styles.summaryValue, { color: colors.danger }]}>₹{paymentDetails.totalDueWithLateFee}</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.summaryBox}>
|
|
||||||
<Text style={styles.summaryLabel}>Contribution</Text>
|
|
||||||
<Text style={styles.summaryValue}>₹{paymentDetails.contributionAmount}</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.summaryBox}>
|
|
||||||
<Text style={styles.summaryLabel}>Total Due</Text>
|
|
||||||
<Text style={[styles.summaryValue, { color: colors.danger }]}>₹{paymentDetails.totalDueWithLateFee}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Text style={styles.sectionTitle}>Participants</Text>
|
<Text style={styles.sectionTitle}>Participants</Text>
|
||||||
{paymentDetails.participants.map(renderParticipant)}
|
{paymentDetails.participants.map(renderParticipant)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</AppView>
|
</AppView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,25 +179,19 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#f4f6f8',
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#f4f6f8',
|
backgroundColor: '#f4f6f8',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
|
||||||
padding: 20,
|
padding: 20,
|
||||||
flexDirection: 'row',
|
paddingBottom: 15,
|
||||||
alignItems: 'center',
|
|
||||||
borderBottomLeftRadius: 20,
|
|
||||||
borderBottomRightRadius: 20,
|
|
||||||
},
|
|
||||||
backButton: {
|
|
||||||
marginRight: 15,
|
|
||||||
},
|
},
|
||||||
headerTitle: {
|
headerTitle: {
|
||||||
color: '#000000ff',
|
color: '#000000',
|
||||||
fontSize: 22,
|
fontSize: 24,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
},
|
},
|
||||||
summaryContainer: {
|
summaryContainer: {
|
||||||
@@ -169,26 +199,33 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'space-around',
|
justifyContent: 'space-around',
|
||||||
padding: 20,
|
padding: 20,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
margin: 15,
|
marginHorizontal: 15,
|
||||||
borderRadius: 10,
|
borderRadius: 12,
|
||||||
|
elevation: 2,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 10,
|
||||||
},
|
},
|
||||||
summaryBox: {
|
summaryBox: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
summaryLabel: {
|
summaryLabel: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: '#666',
|
color: '#6c757d',
|
||||||
|
marginBottom: 4,
|
||||||
},
|
},
|
||||||
summaryValue: {
|
summaryValue: {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: 'bold',
|
fontWeight: '700',
|
||||||
color: colors.primary,
|
color: colors.primary,
|
||||||
},
|
},
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: 18,
|
fontSize: 20,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
marginLeft: 20,
|
marginLeft: 20,
|
||||||
|
marginTop: 20,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
|
color: '#343a40',
|
||||||
},
|
},
|
||||||
participantCard: {
|
participantCard: {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
@@ -199,17 +236,20 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
elevation: 1,
|
||||||
},
|
},
|
||||||
participantInfo: {
|
participantInfo: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
participantName: {
|
participantName: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
|
color: '#212529',
|
||||||
},
|
},
|
||||||
participantMobile: {
|
participantMobile: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: '#888',
|
color: '#6c757d',
|
||||||
|
marginTop: 2,
|
||||||
},
|
},
|
||||||
paymentStatus: {
|
paymentStatus: {
|
||||||
alignItems: 'flex-end',
|
alignItems: 'flex-end',
|
||||||
@@ -217,28 +257,37 @@ const styles = StyleSheet.create({
|
|||||||
statusText: {
|
statusText: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
|
textTransform: 'uppercase',
|
||||||
},
|
},
|
||||||
amountText: {
|
amountText: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
color: '#495057',
|
||||||
|
marginTop: 2,
|
||||||
},
|
},
|
||||||
payButton: {
|
// --- NEW STYLES ---
|
||||||
marginTop: 5,
|
lateFeeContainer: {
|
||||||
backgroundColor: colors.primary,
|
marginTop: 8,
|
||||||
paddingVertical: 5,
|
paddingTop: 5,
|
||||||
paddingHorizontal: 10,
|
borderTopWidth: 1,
|
||||||
borderRadius: 5,
|
borderTopColor: '#e9ecef',
|
||||||
|
alignItems: 'flex-end',
|
||||||
},
|
},
|
||||||
payButtonText: {
|
lateFeeText: {
|
||||||
color: '#fff',
|
fontSize: 13,
|
||||||
fontSize: 12,
|
color: colors.danger,
|
||||||
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
retryButton: {
|
retryButton: {
|
||||||
marginTop: 10,
|
marginTop: 20,
|
||||||
padding: 10,
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: colors.primary,
|
||||||
},
|
},
|
||||||
retryButtonText: {
|
retryButtonText: {
|
||||||
color: colors.primary,
|
color: '#fff',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
FlatList,
|
||||||
|
RefreshControl,
|
||||||
|
TouchableOpacity,
|
||||||
|
SafeAreaView,
|
||||||
|
} from "react-native";
|
||||||
|
import { apiCall } from "../../utils/ApiHelper";
|
||||||
|
import { TRANSACTION } from "../../utils/ApiUrls";
|
||||||
|
import colors from "../../components/colors";
|
||||||
|
import AppView from "../../components/AppView";
|
||||||
|
import Header from "../../components/common/Header";
|
||||||
|
|
||||||
|
type Transaction = {
|
||||||
|
id: string;
|
||||||
|
fundGroupId: string;
|
||||||
|
fundGroupName: string;
|
||||||
|
amount: number;
|
||||||
|
status: string;
|
||||||
|
payerMobile?: string;
|
||||||
|
transactionDate: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ApiResponse = {
|
||||||
|
message: string;
|
||||||
|
status: string;
|
||||||
|
statusCode: number;
|
||||||
|
data: Transaction[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper: convert the array-style date into a JS Date
|
||||||
|
const parseTransactionDate = (arr: number[] | undefined): Date | null => {
|
||||||
|
if (!arr || arr.length < 6) return null;
|
||||||
|
const [year, month, day, hour = 0, minute = 0, second = 0, sub = 0] = arr;
|
||||||
|
|
||||||
|
let milli = 0;
|
||||||
|
if (sub > 999999999) {
|
||||||
|
milli = Math.floor(sub / 1e6);
|
||||||
|
} else if (sub > 1000) {
|
||||||
|
milli = Math.floor(sub / 1e6);
|
||||||
|
} else {
|
||||||
|
milli = sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(year, month - 1, day, hour, minute, second, milli);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatINR = (amount: number) => {
|
||||||
|
try {
|
||||||
|
return new Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" }).format(amount);
|
||||||
|
} catch (e) {
|
||||||
|
return `₹${amount.toFixed(2)}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const StatusBadge: React.FC<{ status?: string }> = ({ status }) => {
|
||||||
|
const text = status ?? "UNKNOWN";
|
||||||
|
const color =
|
||||||
|
text === "SUCCESS" ? styles.statusSuccess : text === "PENDING" ? styles.statusPending : styles.statusFailed;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.statusBadge, color]}>
|
||||||
|
<Text style={styles.statusText}>{text}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TransactionItem: React.FC<{ item: Transaction }> = ({ item }) => {
|
||||||
|
const date = useMemo(() => parseTransactionDate(item.transactionDate), [item.transactionDate]);
|
||||||
|
const dateStr = date ? date.toLocaleString("en-IN") : "-";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity activeOpacity={0.8} style={styles.card}>
|
||||||
|
<View style={styles.cardLeft}>
|
||||||
|
<Text style={styles.fundName}>{item.fundGroupName}</Text>
|
||||||
|
<Text style={styles.payer}>Payer: {item.payerMobile ?? "-"}</Text>
|
||||||
|
<Text style={styles.date}>{dateStr}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.cardRight}>
|
||||||
|
<Text style={styles.amount}>{formatINR(item.amount)}</Text>
|
||||||
|
<StatusBadge status={item.status} />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TransactionsList: React.FC = () => {
|
||||||
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
|
||||||
|
const handleTransactionList = async () => {
|
||||||
|
try {
|
||||||
|
const res = (await apiCall(TRANSACTION, "GET")) as ApiResponse;
|
||||||
|
console.log("transaction list response : ", res);
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
setTransactions(res.data);
|
||||||
|
setMessage(res.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching transactions:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleTransactionList();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRefresh = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setRefreshing(true);
|
||||||
|
await handleTransactionList();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Refresh failed", e);
|
||||||
|
} finally {
|
||||||
|
setRefreshing(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderEmpty = () => (
|
||||||
|
<View style={styles.emptyContainer}>
|
||||||
|
<Text style={styles.emptyTitle}>No transactions</Text>
|
||||||
|
<Text style={styles.emptySubtitle}>Transactions will appear here when available.</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppView>
|
||||||
|
<Header title="transactions" titleColor={colors.Home} arrowColor={colors.Home}/>
|
||||||
|
|
||||||
|
<Text style={styles.headerTitle}>{message}</Text>
|
||||||
|
|
||||||
|
|
||||||
|
<FlatList
|
||||||
|
data={transactions}
|
||||||
|
keyExtractor={(item) => item.id}
|
||||||
|
renderItem={({ item }) => <TransactionItem item={item} />}
|
||||||
|
ListEmptyComponent={renderEmpty}
|
||||||
|
contentContainerStyle={transactions.length === 0 ? { flex: 1 } : undefined}
|
||||||
|
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />}
|
||||||
|
/>
|
||||||
|
</AppView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TransactionsList;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: { flex: 1, backgroundColor: "#f7f7fb" },
|
||||||
|
headerTitle: { fontSize: 18, fontWeight: "500", color: colors.primary },
|
||||||
|
headerSubtitle: { marginTop: 4, color: "#6b7280" },
|
||||||
|
|
||||||
|
card: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
padding: 16,
|
||||||
|
// marginHorizontal: 12,
|
||||||
|
marginVertical: 8,
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
borderRadius: 12,
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOpacity: 0.03,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
cardLeft: { flex: 1 },
|
||||||
|
fundName: { fontSize: 16, fontWeight: "600" },
|
||||||
|
payer: { marginTop: 6, color: "#6b7280" },
|
||||||
|
date: { marginTop: 6, color: "#9ca3af" },
|
||||||
|
|
||||||
|
cardRight: { alignItems: "flex-end", justifyContent: "space-between" },
|
||||||
|
amount: { fontSize: 16, fontWeight: "700" },
|
||||||
|
|
||||||
|
statusBadge: {
|
||||||
|
marginTop: 8,
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 999,
|
||||||
|
},
|
||||||
|
statusText: { fontSize: 12, fontWeight: "600", color: "#fff" },
|
||||||
|
statusSuccess: { backgroundColor: "#10b981" },
|
||||||
|
statusPending: { backgroundColor: "#f59e0b" },
|
||||||
|
statusFailed: { backgroundColor: "#ef4444" },
|
||||||
|
|
||||||
|
emptyContainer: { alignItems: "center", justifyContent: "center", },
|
||||||
|
emptyTitle: { fontSize: 18, fontWeight: "600", color: "#374151" },
|
||||||
|
emptySubtitle: { marginTop: 8, color: "#6b7280" },
|
||||||
|
});
|
||||||
+19
-28
@@ -1,4 +1,5 @@
|
|||||||
import axios, { AxiosRequestConfig, isAxiosError } from "axios";
|
import axios, { AxiosRequestConfig } from "axios";
|
||||||
|
import { Alert, ToastAndroid } from "react-native";
|
||||||
import * as Keychain from "react-native-keychain";
|
import * as Keychain from "react-native-keychain";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,16 +13,9 @@ export async function apiCall<T>(
|
|||||||
url: string,
|
url: string,
|
||||||
method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
|
method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
|
||||||
data?: any,
|
data?: any,
|
||||||
config?: AxiosRequestConfig
|
config?: AxiosRequestConfig,
|
||||||
): Promise<T | null> {
|
responseErrorMessage?:string
|
||||||
// --- Request Logging ---
|
): Promise<T | null > {
|
||||||
console.log("--- API Request ---");
|
|
||||||
console.log(`[${method}] ${url}`);
|
|
||||||
if (data) {
|
|
||||||
console.log("Payload:", JSON.stringify(data, null, 2));
|
|
||||||
}
|
|
||||||
console.log("-------------------");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get saved credentials from Keychain
|
// Get saved credentials from Keychain
|
||||||
const creds = await Keychain.getGenericPassword();
|
const creds = await Keychain.getGenericPassword();
|
||||||
@@ -49,25 +43,22 @@ export async function apiCall<T>(
|
|||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("--- API Error ---");
|
if (error.response) {
|
||||||
console.error(`Request Failed: [${method}] ${url}`);
|
// Server responded with a status other than 2xx
|
||||||
|
console.error(
|
||||||
if (isAxiosError(error)) {
|
"API Error:",
|
||||||
if (error.response) {
|
`Status: ${error.response.status}`,
|
||||||
// Server responded with a status other than 2xx
|
`Data: ${JSON.stringify(error.response.data)}`
|
||||||
console.error(`Status: ${error.response.status}`);
|
);
|
||||||
console.error("Data:", JSON.stringify(error.response.data, null, 2));
|
if(error.response.status === 400){
|
||||||
console.error("Headers:", JSON.stringify(error.response.headers, null, 2));
|
ToastAndroid.show(error.response.data.message || responseErrorMessage, ToastAndroid.SHORT)
|
||||||
} else if (error.request) {
|
|
||||||
// Request was made but no response received
|
|
||||||
console.error("No response received. This could be a network issue or CORS problem.");
|
|
||||||
} else {
|
|
||||||
// Something else went wrong while setting up the request
|
|
||||||
console.error("Error setting up request:", error.message);
|
|
||||||
}
|
}
|
||||||
|
} else if (error.request) {
|
||||||
|
// Request was made but no response received
|
||||||
|
console.error("API Error: No response received", error.request);
|
||||||
} else {
|
} else {
|
||||||
// Non-Axios error
|
// Something else went wrong while setting up the request
|
||||||
console.error("An unexpected error occurred:", error.message);
|
console.error("API Error:", error);
|
||||||
}
|
}
|
||||||
console.error("-----------------");
|
console.error("-----------------");
|
||||||
|
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ export const USER_REQUEST = `${BASE_URL}fund/my-requests`
|
|||||||
export const GROUP_ALL_MEMBER = `${BASE_URL}fund/group/all/member`
|
export const GROUP_ALL_MEMBER = `${BASE_URL}fund/group/all/member`
|
||||||
export const UPDATE_BANK_URL = `${BASE_URL}user/bank/update`;
|
export const UPDATE_BANK_URL = `${BASE_URL}user/bank/update`;
|
||||||
export const CONTRIBUTE = `${BASE_URL}payments/groups/`;
|
export const CONTRIBUTE = `${BASE_URL}payments/groups/`;
|
||||||
|
export const TRANSACTION = `${BASE_URL}payments/my-transactions`;
|
||||||
|
|||||||
Reference in New Issue
Block a user