Add cancel payment confirmation modal
parent
d44677a034
commit
4eb06631b4
|
|
@ -38,6 +38,7 @@ import { displayValue } from "@/utils/Common";
|
||||||
import RefreshIcon from "@/assets/icons/refresh.svg";
|
import RefreshIcon from "@/assets/icons/refresh.svg";
|
||||||
import CustomerSupport from "@/components/home/CustomerSupportModal";
|
import CustomerSupport from "@/components/home/CustomerSupportModal";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Image } from "expo-image";
|
||||||
|
|
||||||
export interface MyPlan {
|
export interface MyPlan {
|
||||||
no_of_emi: number;
|
no_of_emi: number;
|
||||||
|
|
@ -231,9 +232,17 @@ export default function PaymentsTabScreen() {
|
||||||
},
|
},
|
||||||
headerTitle: () => (
|
headerTitle: () => (
|
||||||
<View style={styles.headerTitleContainer}>
|
<View style={styles.headerTitleContainer}>
|
||||||
|
<View style={styles.logoContainer}>
|
||||||
|
<Image
|
||||||
|
source={require("../../assets/images/lio_logo.png")}
|
||||||
|
style={styles.logo}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View>
|
||||||
<Text style={styles.title}>{model}</Text>
|
<Text style={styles.title}>{model}</Text>
|
||||||
<Text style={styles.subtitle}>{chasisNumber}</Text>
|
<Text style={styles.subtitle}>{chasisNumber}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
),
|
),
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<View style={styles.rightContainer}>
|
<View style={styles.rightContainer}>
|
||||||
|
|
@ -270,7 +279,6 @@ export default function PaymentsTabScreen() {
|
||||||
|
|
||||||
// Format currency
|
// Format currency
|
||||||
const formatCurrency = (amount: number) => {
|
const formatCurrency = (amount: number) => {
|
||||||
console.log(amount, "amount format current");
|
|
||||||
return `₹${amount.toLocaleString()}`;
|
return `₹${amount.toLocaleString()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -590,6 +598,22 @@ const StatusBadge = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
logo: {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
resizeMode: "contain",
|
||||||
|
},
|
||||||
|
logoContainer: {
|
||||||
|
padding: 8,
|
||||||
|
borderRadius: 44 / 2, // make it perfectly round
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: "#E5E9F0",
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflow: "hidden", // ensures image doesn't overflow
|
||||||
|
},
|
||||||
loadingContainer: {
|
loadingContainer: {
|
||||||
padding: 16,
|
padding: 16,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|
@ -611,8 +635,9 @@ const styles = StyleSheet.create({
|
||||||
width: "80%",
|
width: "80%",
|
||||||
},
|
},
|
||||||
headerTitleContainer: {
|
headerTitleContainer: {
|
||||||
flexDirection: "column",
|
flexDirection: "row",
|
||||||
backgroundColor: "#F3F5F8",
|
backgroundColor: "#F3F5F8",
|
||||||
|
gap: 8,
|
||||||
},
|
},
|
||||||
subtitle: {
|
subtitle: {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import Pending from "@/assets/icons/pending.svg";
|
||||||
import Failed from "@/assets/icons/cancel.svg";
|
import Failed from "@/assets/icons/cancel.svg";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { payments } from "@/constants/config";
|
||||||
|
import { formatDate } from "@/utils/Payments";
|
||||||
|
|
||||||
const PaymentConfirmationScreen = () => {
|
const PaymentConfirmationScreen = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -22,35 +24,11 @@ const PaymentConfirmationScreen = () => {
|
||||||
const paymentStatus = paymentOrder?.status || "confirmed";
|
const paymentStatus = paymentOrder?.status || "confirmed";
|
||||||
|
|
||||||
// Format amount with currency
|
// Format amount with currency
|
||||||
const formatAmount = (amount: number | null) => {
|
const formatAmount = (amount: number | null | undefined) => {
|
||||||
if (!amount) return "₹0";
|
if (!amount) return "₹0";
|
||||||
return `₹${amount.toLocaleString("en-IN")}`;
|
return `₹${amount.toLocaleString("en-IN")}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format date
|
|
||||||
const formatDate = (dateString?: string) => {
|
|
||||||
if (!dateString)
|
|
||||||
return new Date().toLocaleString("en-IN", {
|
|
||||||
day: "numeric",
|
|
||||||
month: "long",
|
|
||||||
year: "numeric",
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "2-digit",
|
|
||||||
hour12: true,
|
|
||||||
weekday: "long",
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Date(dateString).toLocaleString("en-IN", {
|
|
||||||
day: "numeric",
|
|
||||||
month: "long",
|
|
||||||
year: "numeric",
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "2-digit",
|
|
||||||
hour12: true,
|
|
||||||
weekday: "long",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get icon and text based on status
|
// Get icon and text based on status
|
||||||
const getStatusDisplay = () => {
|
const getStatusDisplay = () => {
|
||||||
switch (paymentStatus) {
|
switch (paymentStatus) {
|
||||||
|
|
@ -95,15 +73,15 @@ const PaymentConfirmationScreen = () => {
|
||||||
<View style={styles.contentFrame}>
|
<View style={styles.contentFrame}>
|
||||||
<View style={styles.qrFrame}>
|
<View style={styles.qrFrame}>
|
||||||
<View style={styles.paymentStatusContainer}>
|
<View style={styles.paymentStatusContainer}>
|
||||||
|
<Text style={styles.statusText}>{statusDisplay.text}</Text>
|
||||||
<View style={statusDisplay.iconContainerStyle}>
|
<View style={statusDisplay.iconContainerStyle}>
|
||||||
{statusDisplay.icon}
|
{statusDisplay.icon}
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.amountText}>
|
<Text style={styles.amountText}>
|
||||||
{formatAmount(paymentOrder?.amount || due_amount)}
|
{formatAmount(paymentOrder?.amount)}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text>Payment to {payments.AMOUNT_PAID_TO}</Text>
|
||||||
<View style={styles.statusContainer}>
|
<View style={styles.statusContainer}>
|
||||||
<Text style={styles.statusText}>{statusDisplay.text}</Text>
|
|
||||||
<Text style={styles.dateText}>
|
<Text style={styles.dateText}>
|
||||||
{formatDate(paymentOrder?.transaction_date)}
|
{formatDate(paymentOrder?.transaction_date)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
@ -200,7 +178,6 @@ const styles = StyleSheet.create({
|
||||||
backgroundColor: "#fcfcfc",
|
backgroundColor: "#fcfcfc",
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
paddingVertical: 32,
|
|
||||||
},
|
},
|
||||||
paymentStatusContainer: {
|
paymentStatusContainer: {
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|
@ -220,6 +197,13 @@ const styles = StyleSheet.create({
|
||||||
successIcon: {
|
successIcon: {
|
||||||
// Icon styling handled by Ionicons
|
// Icon styling handled by Ionicons
|
||||||
},
|
},
|
||||||
|
paidTo: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: "700",
|
||||||
|
color: "#253342",
|
||||||
|
textAlign: "center",
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
amountText: {
|
amountText: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,15 @@ import {
|
||||||
import { useNavigation, useRoute } from "@react-navigation/native";
|
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import api from "@/services/axiosClient";
|
import api from "@/services/axiosClient";
|
||||||
import { BASE_URL } from "@/constants/config";
|
import { BASE_URL, payments } from "@/constants/config";
|
||||||
import { useSnackbar } from "@/contexts/Snackbar";
|
import { useSnackbar } from "@/contexts/Snackbar";
|
||||||
import { SafeAreaView } from "react-native-safe-area-context";
|
|
||||||
|
|
||||||
// Import your success/failure icons
|
|
||||||
import SuccessIcon from "@/assets/icons/check_circle.svg";
|
import SuccessIcon from "@/assets/icons/check_circle.svg";
|
||||||
import FailureIcon from "@/assets/icons/cancel.svg";
|
import FailureIcon from "@/assets/icons/cancel.svg";
|
||||||
import PendingIcon from "@/assets/icons/pending.svg";
|
import PendingIcon from "@/assets/icons/pending.svg";
|
||||||
import Header from "@/components/common/Header";
|
import Header from "@/components/common/Header";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { formatDate } from "@/utils/Payments";
|
||||||
|
|
||||||
interface TransactionDetailData {
|
interface TransactionDetailData {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -152,18 +151,18 @@ export default function TransactionDetailScreen() {
|
||||||
) : (
|
) : (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
|
<Text style={styles.statusText}>
|
||||||
|
{getStatusText(transactionData.status)}
|
||||||
|
</Text>
|
||||||
<View style={styles.iconContainer}>
|
<View style={styles.iconContainer}>
|
||||||
{getStatusIcon(transactionData.status)}
|
{getStatusIcon(transactionData.status)}
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.amount}>
|
<Text style={styles.amount}>
|
||||||
{formatCurrency(transactionData.amount)}
|
{formatCurrency(transactionData.amount)}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text>Payment to {payments.AMOUNT_PAID_TO}</Text>
|
||||||
<Text style={styles.statusText}>
|
|
||||||
{getStatusText(transactionData.status)}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.dateTime}>
|
<Text style={styles.dateTime}>
|
||||||
{formatDateTime(transactionData.transaction_date)}
|
{formatDate(transactionData?.transaction_date)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<View style={styles.divider} />
|
<View style={styles.divider} />
|
||||||
|
|
@ -305,7 +304,6 @@ const styles = StyleSheet.create({
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: "400",
|
fontWeight: "400",
|
||||||
color: "#252A34",
|
color: "#252A34",
|
||||||
marginBottom: 4,
|
|
||||||
},
|
},
|
||||||
dateTime: {
|
dateTime: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { payments } from "@/constants/config";
|
||||||
import { useFocusEffect, useRouter } from "expo-router";
|
import { useFocusEffect, useRouter } from "expo-router";
|
||||||
import { useSocket } from "@/contexts/SocketContext";
|
import { useSocket } from "@/contexts/SocketContext";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import CancelPayment from "@/components/Payments/CancelPaymentModal";
|
||||||
|
|
||||||
const { height: screenHeight } = Dimensions.get("window");
|
const { height: screenHeight } = Dimensions.get("window");
|
||||||
|
|
||||||
|
|
@ -34,9 +35,6 @@ const UpiPaymentScreen = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
//paymentorder.amount is undefined
|
//paymentorder.amount is undefined
|
||||||
console.log("inside payemi ✅✅");
|
console.log("inside payemi ✅✅");
|
||||||
useEffect(() => {
|
|
||||||
console.log("inside pay emi useeffect 🔥🔥🔥🔥🔥");
|
|
||||||
}, []);
|
|
||||||
const paymentOrder = useSelector(
|
const paymentOrder = useSelector(
|
||||||
(state: RootState) => state.payments.paymentOrder
|
(state: RootState) => state.payments.paymentOrder
|
||||||
);
|
);
|
||||||
|
|
@ -46,36 +44,36 @@ const UpiPaymentScreen = () => {
|
||||||
const { showSnackbar } = useSnackbar();
|
const { showSnackbar } = useSnackbar();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
useFocusEffect(
|
const [isCancelModalVisible, setIsCancelModalVisible] =
|
||||||
React.useCallback(() => {
|
useState<boolean>(false);
|
||||||
let backPressCount = 0;
|
|
||||||
let backPressTimer: NodeJS.Timeout | null = null;
|
function toggleCancelModal() {
|
||||||
const backAction = () => {
|
setIsCancelModalVisible(!isCancelModalVisible);
|
||||||
if (backPressCount === 0) {
|
}
|
||||||
backPressCount++;
|
|
||||||
showSnackbar("Press back again to cancel payment", "info");
|
function handleCancelPayment() {
|
||||||
backPressTimer = setTimeout(() => {
|
|
||||||
backPressCount = 0;
|
|
||||||
}, 2000);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
if (backPressTimer) clearTimeout(backPressTimer);
|
|
||||||
offPaymentConfirmation();
|
offPaymentConfirmation();
|
||||||
disconnect();
|
disconnect();
|
||||||
router.back();
|
router.back();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
React.useCallback(() => {
|
||||||
|
let backPressTimer: NodeJS.Timeout | null = null;
|
||||||
|
const backAction = () => {
|
||||||
|
setIsCancelModalVisible(true);
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
const backHandler = BackHandler.addEventListener(
|
const backHandler = BackHandler.addEventListener(
|
||||||
"hardwareBackPress",
|
"hardwareBackPress",
|
||||||
backAction
|
backAction
|
||||||
);
|
);
|
||||||
// ✅ Cleanup when screen loses focus
|
|
||||||
return () => {
|
return () => {
|
||||||
if (backPressTimer) clearTimeout(backPressTimer);
|
if (backPressTimer) clearTimeout(backPressTimer);
|
||||||
backHandler.remove();
|
backHandler.remove();
|
||||||
};
|
};
|
||||||
}, [offPaymentConfirmation, disconnect, router])
|
}, [])
|
||||||
);
|
);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
|
|
@ -99,7 +97,7 @@ const UpiPaymentScreen = () => {
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
const formatAmount = (amount: number): string => {
|
const formatAmount = (amount: number | null | undefined): string => {
|
||||||
if (amount == null || amount == undefined) return `₹${0}`;
|
if (amount == null || amount == undefined) return `₹${0}`;
|
||||||
return `₹${amount.toLocaleString("en-IN")}`;
|
return `₹${amount.toLocaleString("en-IN")}`;
|
||||||
};
|
};
|
||||||
|
|
@ -143,18 +141,28 @@ const UpiPaymentScreen = () => {
|
||||||
|
|
||||||
const shareQR = async (): Promise<void> => {
|
const shareQR = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
const fileUri =
|
||||||
|
FileSystem.documentDirectory + `qr-${paymentOrder?.order_id}.png`;
|
||||||
|
|
||||||
|
// Download QR code image to local file
|
||||||
|
const downloadResult = await FileSystem.downloadAsync(
|
||||||
|
paymentOrder?.qr_code_url,
|
||||||
|
fileUri
|
||||||
|
);
|
||||||
|
|
||||||
|
// Share using expo-sharing
|
||||||
if (await Sharing.isAvailableAsync()) {
|
if (await Sharing.isAvailableAsync()) {
|
||||||
await Share.share({
|
await Sharing.shareAsync(downloadResult.uri, {
|
||||||
message: `Pay ${formatAmount(
|
mimeType: "image/png",
|
||||||
paymentOrder.amount
|
dialogTitle: "Share QR Code",
|
||||||
)} using this QR code`,
|
UTI: "public.png",
|
||||||
url: paymentOrder.qr_code_url,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Alert.alert("Error", "Sharing is not available on this device");
|
showSnackbar("Sharing is not available on this device.", "error");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Alert.alert("Error", "Failed to share QR code");
|
console.error("Error sharing QR code:", error);
|
||||||
|
showSnackbar("Failed to share QR code", "error");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -190,6 +198,7 @@ const UpiPaymentScreen = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Header title={t("payment.pay-emi")} showBackButton={false} />
|
<Header title={t("payment.pay-emi")} showBackButton={false} />
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
|
@ -203,6 +212,7 @@ const UpiPaymentScreen = () => {
|
||||||
<Text style={styles.amountLabel}>
|
<Text style={styles.amountLabel}>
|
||||||
{t("payment.amount-to-be-paid")}
|
{t("payment.amount-to-be-paid")}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text style={styles.paidTo}>{payments.AMOUNT_PAID_TO}</Text>
|
||||||
<Text style={styles.amount}>
|
<Text style={styles.amount}>
|
||||||
{formatAmount(paymentOrder?.amount)}
|
{formatAmount(paymentOrder?.amount)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
@ -258,6 +268,12 @@ const UpiPaymentScreen = () => {
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
|
<CancelPayment
|
||||||
|
visible={isCancelModalVisible}
|
||||||
|
onClose={toggleCancelModal}
|
||||||
|
onCancel={handleCancelPayment}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -305,6 +321,7 @@ const styles = StyleSheet.create({
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
},
|
},
|
||||||
confirmTitle: {
|
confirmTitle: {
|
||||||
|
padding: 2,
|
||||||
fontWeight: "400",
|
fontWeight: "400",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: 14,
|
lineHeight: 14,
|
||||||
|
|
@ -346,6 +363,13 @@ const styles = StyleSheet.create({
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
},
|
},
|
||||||
|
paidTo: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: "700",
|
||||||
|
color: "#253342",
|
||||||
|
textAlign: "center",
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
amount: {
|
amount: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { useSocket } from "@/contexts/SocketContext";
|
||||||
import { useSnackbar } from "@/contexts/Snackbar";
|
import { useSnackbar } from "@/contexts/Snackbar";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { displayValue, formatCurrency } from "@/utils/Common";
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
paymentType: Yup.string().required("Please select a payment option"),
|
paymentType: Yup.string().required("Please select a payment option"),
|
||||||
|
|
@ -169,34 +170,41 @@ const SelectAmountScreen = () => {
|
||||||
values,
|
values,
|
||||||
errors,
|
errors,
|
||||||
touched,
|
touched,
|
||||||
handleChange,
|
|
||||||
handleBlur,
|
handleBlur,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setFieldValue,
|
setFieldValue,
|
||||||
isValid,
|
|
||||||
validateField,
|
|
||||||
dirty,
|
|
||||||
}) => {
|
}) => {
|
||||||
const handleQuickAmountPress = (amount: number) => {
|
const handleQuickAmountPress = (amount: number) => {
|
||||||
const currentAmount = values.customAmount
|
let newAmount = values.customAmount
|
||||||
? parseFloat(values.customAmount)
|
? parseFloat(values.customAmount) + amount
|
||||||
: 0;
|
: amount;
|
||||||
const newAmount = currentAmount + amount;
|
|
||||||
|
if (newAmount > payments.MAX_AMOUNT) {
|
||||||
|
newAmount = payments.MAX_AMOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFieldValue("paymentType", "custom");
|
||||||
setFieldValue("customAmount", newAmount.toString());
|
setFieldValue("customAmount", newAmount.toString());
|
||||||
validateField("customAmount");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCustomAmountChange = (text: string) => {
|
const handleCustomAmountChange = (text: string) => {
|
||||||
const numericText = text.replace(/[^0-9.]/g, "");
|
let numericText = text.replace(/[^0-9.]/g, "");
|
||||||
const parts = numericText.split(".");
|
const parts = numericText.split(".");
|
||||||
const formattedText =
|
|
||||||
parts.length > 2
|
|
||||||
? parts[0] + "." + parts.slice(1).join("")
|
|
||||||
: numericText;
|
|
||||||
|
|
||||||
setFieldValue("customAmount", formattedText, true);
|
if (parts.length > 2) {
|
||||||
|
numericText = parts[0] + "." + parts[1];
|
||||||
|
}
|
||||||
|
if (parts[1]?.length > 2) {
|
||||||
|
numericText = parts[0] + "." + parts[1].slice(0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const numValue = parseFloat(numericText);
|
||||||
|
if (!isNaN(numValue) && numValue > payments.MAX_AMOUNT) {
|
||||||
|
numericText = payments.MAX_AMOUNT.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
setFieldValue("customAmount", numericText, true);
|
||||||
setFieldValue("paymentType", "custom");
|
setFieldValue("paymentType", "custom");
|
||||||
validateField("customAmount"); // Trigger validation after change
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPaymentAmount = () => {
|
const getPaymentAmount = () => {
|
||||||
|
|
@ -214,34 +222,15 @@ const SelectAmountScreen = () => {
|
||||||
!isNaN(paymentAmount) &&
|
!isNaN(paymentAmount) &&
|
||||||
paymentAmount >= payments.MIN_AMOUNT);
|
paymentAmount >= payments.MIN_AMOUNT);
|
||||||
|
|
||||||
// Button text logic
|
|
||||||
let buttonText = "";
|
|
||||||
if (values.paymentType === "due") {
|
|
||||||
buttonText = `Pay ₹${paymentAmount.toFixed(2)}`;
|
|
||||||
} else {
|
|
||||||
buttonText =
|
|
||||||
paymentAmount >= payments.MIN_AMOUNT
|
|
||||||
? `Pay ₹${paymentAmount.toFixed(2)}`
|
|
||||||
: "Select Amount";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView
|
<View style={styles.container}>
|
||||||
style={styles.container}
|
|
||||||
behavior={"height"}
|
|
||||||
// Improved keyboard offset
|
|
||||||
// keyboardVerticalOffset={Platform.OS === "ios" ? 88 : 0}
|
|
||||||
>
|
|
||||||
<Header
|
<Header
|
||||||
title={t("payment.select-amount")}
|
title={t("payment.select-amount")}
|
||||||
showBackButton={true}
|
showBackButton={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.content}
|
style={styles.content}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
// Add keyboard dismiss on scroll
|
|
||||||
keyboardShouldPersistTaps="handled"
|
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
>
|
>
|
||||||
<View style={styles.selectAmountContainer}>
|
<View style={styles.selectAmountContainer}>
|
||||||
|
|
@ -269,7 +258,7 @@ const SelectAmountScreen = () => {
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.amountText}>
|
<Text style={styles.amountText}>
|
||||||
₹{dueAmount?.toFixed(2)}
|
{displayValue(dueAmount, formatCurrency)}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
|
@ -317,7 +306,6 @@ const SelectAmountScreen = () => {
|
||||||
placeholderTextColor="#94A3B8"
|
placeholderTextColor="#94A3B8"
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
onFocus={() => setFieldValue("paymentType", "custom")}
|
onFocus={() => setFieldValue("paymentType", "custom")}
|
||||||
// Add return key handling
|
|
||||||
returnKeyType="done"
|
returnKeyType="done"
|
||||||
/>
|
/>
|
||||||
<View style={styles.helperContainer}>
|
<View style={styles.helperContainer}>
|
||||||
|
|
@ -358,14 +346,7 @@ const SelectAmountScreen = () => {
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
<View style={[styles.buttonContainer]}>
|
||||||
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.buttonContainer,
|
|
||||||
!keyboardVisible && { marginBottom: insets.bottom },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.payButton,
|
styles.payButton,
|
||||||
|
|
@ -380,12 +361,13 @@ const SelectAmountScreen = () => {
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.payButtonText}>
|
<Text style={styles.payButtonText}>
|
||||||
Pay ₹{getPaymentAmount().toFixed(2)}
|
Pay {displayValue(getPaymentAmount(), formatCurrency)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</KeyboardAvoidingView>
|
</ScrollView>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,6 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
formContainer: {
|
formContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: "space-between",
|
|
||||||
},
|
},
|
||||||
inputContainer: {
|
inputContainer: {
|
||||||
marginBottom: 24,
|
marginBottom: 24,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import BottomSheetModal from "@/components/common/BottomSheetModal";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
||||||
|
|
||||||
|
interface CancelPaymentProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CancelPayment({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
onCancel,
|
||||||
|
}: CancelPaymentProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<BottomSheetModal
|
||||||
|
visible={visible}
|
||||||
|
onClose={onClose}
|
||||||
|
heading={t("payment.cancel-payment")}
|
||||||
|
>
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Text>{t("payment.do-you-want-to-cancel-payment")}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.buttonsContainer}>
|
||||||
|
<TouchableOpacity onPress={onCancel} style={styles.button}>
|
||||||
|
<Text style={styles.bottomButtonText}>
|
||||||
|
{t("payment.cancel-payment")}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity onPress={onClose} style={styles.button}>
|
||||||
|
<Text style={styles.bottomButtonText}>
|
||||||
|
{t("payment.continue-payment")}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</BottomSheetModal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
buttonsContainer: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderColor: "#D8DDE7",
|
||||||
|
},
|
||||||
|
bottomButtonText: {
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
width: "45%",
|
||||||
|
height: 44,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: "#E5E9F0",
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: "#F3F5F8",
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: 16,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
},
|
||||||
|
secondaryButton: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingVertical: 8,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: "#F3F5F8",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: "#D8DDE7",
|
||||||
|
width: 156,
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
fullButton: {
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: "500",
|
||||||
|
color: "#252A34",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -124,7 +124,6 @@ const styles = StyleSheet.create({
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
},
|
},
|
||||||
paymentAmount: {
|
paymentAmount: {
|
||||||
width: 69,
|
|
||||||
height: 20,
|
height: 20,
|
||||||
fontFamily: "Inter-SemiBold",
|
fontFamily: "Inter-SemiBold",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
@ -140,7 +139,6 @@ const styles = StyleSheet.create({
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
paymentDetails: {
|
paymentDetails: {
|
||||||
width: 237,
|
|
||||||
height: 16,
|
height: 16,
|
||||||
fontFamily: "Inter-Regular",
|
fontFamily: "Inter-Regular",
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
|
@ -156,7 +154,6 @@ const styles = StyleSheet.create({
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
paddingVertical: 2,
|
paddingVertical: 2,
|
||||||
marginLeft: 4, // simulate gap
|
|
||||||
},
|
},
|
||||||
statusText: {
|
statusText: {
|
||||||
fontFamily: "Inter-Medium",
|
fontFamily: "Inter-Medium",
|
||||||
|
|
|
||||||
|
|
@ -185,11 +185,13 @@ export const issueConfig = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const payments = {
|
export const payments = {
|
||||||
MIN_AMOUNT: 200,
|
MIN_AMOUNT: 1,
|
||||||
|
MAX_AMOUNT: 500000,
|
||||||
LINK_EXPIRED: "Payment link expired",
|
LINK_EXPIRED: "Payment link expired",
|
||||||
SOCKET_CONNECTION_TIMEOUT_IN_SECS: 5,
|
SOCKET_CONNECTION_TIMEOUT_IN_SECS: 5,
|
||||||
REGISTER_TRANSACTION_EMIT_EVENT_NAME: "register-transaction",
|
REGISTER_TRANSACTION_EMIT_EVENT_NAME: "register-transaction",
|
||||||
REGISTER_TRANSACTION_LISTEN_EVENT_NAME: "registration-ack",
|
REGISTER_TRANSACTION_LISTEN_EVENT_NAME: "registration-ack",
|
||||||
PAYMENT_CONFIRMATION_EVENT_NAME: "register-transaction",
|
PAYMENT_CONFIRMATION_EVENT_NAME: "register-transaction",
|
||||||
EMI_WARNING_DAYS_THRESHOLD: 14,
|
EMI_WARNING_DAYS_THRESHOLD: 14,
|
||||||
|
AMOUNT_PAID_TO: "Nav Shakti Lithium Pvt. Ltd.",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,10 @@
|
||||||
"pay-using-upi": "Pay using UPI App",
|
"pay-using-upi": "Pay using UPI App",
|
||||||
"confirm-payment": "Confirm once your payment is completed",
|
"confirm-payment": "Confirm once your payment is completed",
|
||||||
"payment-done": "Payment Done",
|
"payment-done": "Payment Done",
|
||||||
"view-plan": "View Plan"
|
"view-plan": "View Plan",
|
||||||
|
"cancel-payment": "Cancel Payment",
|
||||||
|
"continue-payment": "Continue Payment",
|
||||||
|
"do-you-want-to-cancel-payment": "Are you sure you want to cancel the payment?"
|
||||||
},
|
},
|
||||||
"service": {
|
"service": {
|
||||||
"schedule-maintenance": "Schedule Maintenance",
|
"schedule-maintenance": "Schedule Maintenance",
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,10 @@
|
||||||
"amount-to-be-paid": "भुगतान की जाने वाली राशि",
|
"amount-to-be-paid": "भुगतान की जाने वाली राशि",
|
||||||
"pay-using-upi": "UPI ऐप का उपयोग करके भुगतान करें",
|
"pay-using-upi": "UPI ऐप का उपयोग करके भुगतान करें",
|
||||||
"confirm-payment": "भुगतान पूरा होने पर पुष्टि करें।",
|
"confirm-payment": "भुगतान पूरा होने पर पुष्टि करें।",
|
||||||
"payment-done": "भुगतान हो गया"
|
"payment-done": "भुगतान हो गया",
|
||||||
|
"cancel-payment": "भुगतान रद्द करें",
|
||||||
|
"continue-payment": "भुगतान जारी रखें",
|
||||||
|
"do-you-want-to-cancel-payment": "क्या आप वाकई भुगतान रद्द करना चाहते हैं?"
|
||||||
},
|
},
|
||||||
"service": {
|
"service": {
|
||||||
"schedule-maintenance": "शेड्यूल मेंटेनेंस",
|
"schedule-maintenance": "शेड्यूल मेंटेनेंस",
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,7 @@ export const displayValue = (value: any, formatter?: (val: any) => string) => {
|
||||||
}
|
}
|
||||||
return formatter ? formatter(value) : value;
|
return formatter ? formatter(value) : value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const formatCurrency = (amount: number) => {
|
||||||
|
return `₹${amount.toLocaleString()}`;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,28 @@ export const toCamel = (obj: any): any => {
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const formatDate = (dateString?: string | null) => {
|
||||||
|
if (!dateString) {
|
||||||
|
return "--";
|
||||||
|
}
|
||||||
|
const date = dateString ? new Date(dateString) : new Date();
|
||||||
|
|
||||||
|
const optionsDate = {
|
||||||
|
day: "numeric",
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
} as const;
|
||||||
|
const optionsTime = {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: true,
|
||||||
|
} as const;
|
||||||
|
const optionsWeekday = { weekday: "long" } as const;
|
||||||
|
|
||||||
|
const formattedDate = date.toLocaleDateString("en-IN", optionsDate);
|
||||||
|
const formattedTime = date.toLocaleTimeString("en-IN", optionsTime);
|
||||||
|
const formattedWeekday = date.toLocaleDateString("en-IN", optionsWeekday);
|
||||||
|
|
||||||
|
return `${formattedDate}, ${formattedTime}, ${formattedWeekday}`;
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue