Add cancel payment confirmation modal

rename-package
vinay kumar 2025-09-16 19:14:23 +05:30
parent d44677a034
commit 4eb06631b4
13 changed files with 339 additions and 202 deletions

View File

@ -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,

View File

@ -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",

View File

@ -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,

View File

@ -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",

View File

@ -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>

View File

@ -148,7 +148,6 @@ const styles = StyleSheet.create({
}, },
formContainer: { formContainer: {
flex: 1, flex: 1,
justifyContent: "space-between",
}, },
inputContainer: { inputContainer: {
marginBottom: 24, marginBottom: 24,

View File

@ -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",
},
});

View File

@ -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",

View File

@ -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.",
}; };

View File

@ -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",

View File

@ -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": "शेड्यूल मेंटेनेंस",

View File

@ -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()}`;
};

View File

@ -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}`;
};