446 lines
13 KiB
TypeScript
446 lines
13 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
TouchableOpacity,
|
|
TextInput,
|
|
ScrollView,
|
|
Platform,
|
|
KeyboardAvoidingView,
|
|
Alert,
|
|
} from "react-native";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import { Formik } from "formik";
|
|
import * as Yup from "yup";
|
|
import Header from "../../components/common/Header";
|
|
import { RootState } from "@/store/rootReducer";
|
|
import { BASE_URL, payments } from "@/constants/config";
|
|
import api from "@/services/axiosClient";
|
|
import { setPaymentOrder } from "@/store/paymentSlice";
|
|
import { Overlay } from "@/components/common/Overlay";
|
|
import { useRouter } from "expo-router";
|
|
import { useSocket } from "@/contexts/SocketContext";
|
|
|
|
// Validation schema
|
|
const validationSchema = Yup.object().shape({
|
|
paymentType: Yup.string().required("Please select a payment option"),
|
|
customAmount: Yup.string().when("paymentType", {
|
|
is: "custom",
|
|
then: (schema) =>
|
|
schema
|
|
.required("Amount is required")
|
|
.test("valid-number", "Please enter a valid amount", (value) => {
|
|
const numValue = parseFloat(value);
|
|
return !isNaN(numValue) && numValue > 0;
|
|
})
|
|
.test(
|
|
"min-amount",
|
|
`Minimum amount is ₹${payments.MIN_AMOUNT}`,
|
|
(value) => {
|
|
const numValue = parseFloat(value);
|
|
return !isNaN(numValue) && numValue >= payments.MIN_AMOUNT;
|
|
}
|
|
),
|
|
otherwise: (schema) => schema.notRequired(),
|
|
}),
|
|
});
|
|
|
|
const SelectAmountScreen = () => {
|
|
// Fetch due amount from Redux
|
|
const dueAmount = useSelector(
|
|
(state: RootState) => state.payments?.due_amount || 0
|
|
);
|
|
|
|
const router = useRouter();
|
|
const [isFetching, setIsFetching] = useState<boolean>(false);
|
|
|
|
const { registerTransaction } = useSocket();
|
|
|
|
const quickAmounts = [50, 100, 500, 1000];
|
|
|
|
const initialValues = {
|
|
paymentType: "due",
|
|
customAmount: "",
|
|
};
|
|
|
|
const dispatch = useDispatch();
|
|
|
|
const handleSubmit = async (values: any) => {
|
|
setIsFetching(true);
|
|
const paymentAmount =
|
|
values.paymentType === "due"
|
|
? dueAmount
|
|
: parseFloat(values.customAmount);
|
|
console.log(values, "values");
|
|
try {
|
|
const res = await api.post(`/api/v1/create-order`, {
|
|
amount: paymentAmount,
|
|
});
|
|
|
|
console.log(res.data, "response from select amount");
|
|
if (res.data && res.data.success) {
|
|
dispatch(setPaymentOrder(res.data.data));
|
|
try {
|
|
await registerTransaction(res.data.data.transaction_id);
|
|
console.log("Transaction registered successfully");
|
|
|
|
// Navigate to payment screen
|
|
router.push("/payments/payEmi");
|
|
} catch (socketError) {
|
|
throw socketError;
|
|
}
|
|
} else {
|
|
throw new Error("Failed to create order");
|
|
}
|
|
} catch (err) {
|
|
console.error(err, "Error in creating order.");
|
|
} finally {
|
|
setIsFetching(false);
|
|
}
|
|
|
|
console.log("Payment Amount:", paymentAmount);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Formik
|
|
initialValues={initialValues}
|
|
validationSchema={validationSchema}
|
|
onSubmit={handleSubmit}
|
|
>
|
|
{({
|
|
values,
|
|
errors,
|
|
touched,
|
|
handleChange,
|
|
handleBlur,
|
|
handleSubmit,
|
|
setFieldValue,
|
|
isValid,
|
|
dirty,
|
|
}) => {
|
|
const handleQuickAmountPress = (amount: number) => {
|
|
const currentAmount = values.customAmount
|
|
? parseFloat(values.customAmount)
|
|
: 0;
|
|
const newAmount = currentAmount + amount;
|
|
setFieldValue("customAmount", newAmount.toString());
|
|
};
|
|
|
|
const getPaymentAmount = () => {
|
|
if (values.paymentType === "due") {
|
|
return dueAmount;
|
|
}
|
|
return values.customAmount ? parseFloat(values.customAmount) : 0;
|
|
};
|
|
|
|
const isPayButtonEnabled = () => {
|
|
if (values.paymentType === "due") return true;
|
|
return isValid && dirty && values.customAmount;
|
|
};
|
|
|
|
return (
|
|
<KeyboardAvoidingView
|
|
style={styles.container}
|
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
keyboardVerticalOffset={Platform.OS === "ios" ? 0 : 20}
|
|
>
|
|
<Header title="Select Amount" showBackButton={true} />
|
|
|
|
<ScrollView
|
|
style={styles.content}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
<View style={styles.selectAmountContainer}>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.option,
|
|
values.paymentType === "due" && styles.selectedOption,
|
|
]}
|
|
onPress={() => setFieldValue("paymentType", "due")}
|
|
>
|
|
<View style={styles.radioContainer}>
|
|
<View
|
|
style={[
|
|
styles.radioDot,
|
|
values.paymentType === "due" &&
|
|
styles.selectedRadioDot,
|
|
]}
|
|
>
|
|
{values.paymentType === "due" && (
|
|
<View style={styles.radioInner} />
|
|
)}
|
|
</View>
|
|
<Text style={styles.radioLabel}>Pay amount due</Text>
|
|
</View>
|
|
<Text style={styles.amountText}>
|
|
₹{dueAmount?.toFixed(2)}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
|
|
<View
|
|
style={[
|
|
styles.customOption,
|
|
values.paymentType === "custom" && styles.selectedOption,
|
|
touched.customAmount &&
|
|
errors.customAmount &&
|
|
styles.errorOption,
|
|
]}
|
|
>
|
|
<TouchableOpacity
|
|
style={styles.radioContainer}
|
|
onPress={() => setFieldValue("paymentType", "custom")}
|
|
>
|
|
<View
|
|
style={[
|
|
styles.radioDot,
|
|
values.paymentType === "custom" &&
|
|
styles.selectedRadioDot,
|
|
]}
|
|
>
|
|
{values.paymentType === "custom" && (
|
|
<View style={styles.radioInner} />
|
|
)}
|
|
</View>
|
|
<Text style={styles.radioLabel}>Enter custom amount</Text>
|
|
</TouchableOpacity>
|
|
|
|
<View style={styles.inputContainer}>
|
|
<TextInput
|
|
style={[
|
|
styles.textInput,
|
|
touched.customAmount &&
|
|
errors.customAmount &&
|
|
styles.errorInput,
|
|
]}
|
|
value={values.customAmount}
|
|
onChangeText={(text) => {
|
|
handleChange("customAmount")(text);
|
|
setFieldValue("paymentType", "custom");
|
|
}}
|
|
onBlur={handleBlur("customAmount")}
|
|
placeholder="₹"
|
|
placeholderTextColor="#94A3B8"
|
|
keyboardType="numeric"
|
|
onFocus={() => setFieldValue("paymentType", "custom")}
|
|
/>
|
|
<View style={styles.helperContainer}>
|
|
<Text
|
|
style={[
|
|
styles.helperText,
|
|
touched.customAmount &&
|
|
errors.customAmount &&
|
|
styles.errorText,
|
|
]}
|
|
>
|
|
{touched.customAmount && errors.customAmount
|
|
? errors.customAmount
|
|
: `Minimum: ₹${payments.MIN_AMOUNT}`}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.chipsContainer}>
|
|
{quickAmounts.map((amount) => (
|
|
<TouchableOpacity
|
|
key={amount}
|
|
style={styles.chip}
|
|
onPress={() => handleQuickAmountPress(amount)}
|
|
>
|
|
<Text style={styles.chipText}>+₹{amount}</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
{/* General form error */}
|
|
{touched.paymentType && errors.paymentType && (
|
|
<Text style={styles.generalErrorText}>
|
|
{errors.paymentType}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
</ScrollView>
|
|
|
|
{/* Pay Button */}
|
|
<View style={styles.buttonContainer}>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.payButton,
|
|
!isPayButtonEnabled() && styles.disabledButton,
|
|
]}
|
|
onPress={() => handleSubmit()}
|
|
disabled={!isPayButtonEnabled()}
|
|
>
|
|
{getPaymentAmount() < payments.MIN_AMOUNT ? (
|
|
<Text style={styles.payButtonText}>Select Amount</Text>
|
|
) : (
|
|
<Text style={styles.payButtonText}>
|
|
Pay ₹{getPaymentAmount().toFixed(2)}
|
|
</Text>
|
|
)}
|
|
</TouchableOpacity>
|
|
</View>
|
|
</KeyboardAvoidingView>
|
|
);
|
|
}}
|
|
</Formik>
|
|
<Overlay isUploading={isFetching} />
|
|
</>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: "#F3F4F6",
|
|
},
|
|
content: {
|
|
flex: 1,
|
|
paddingHorizontal: 16,
|
|
},
|
|
selectAmountContainer: {
|
|
paddingTop: 16,
|
|
gap: 16,
|
|
},
|
|
option: {
|
|
backgroundColor: "#FCFCFC",
|
|
borderRadius: 8,
|
|
padding: 16,
|
|
borderWidth: 1,
|
|
borderColor: "transparent",
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
minHeight: 56,
|
|
},
|
|
selectedOption: {
|
|
borderColor: "#009E71",
|
|
},
|
|
errorOption: {
|
|
borderColor: "#EF4444",
|
|
},
|
|
customOption: {
|
|
backgroundColor: "#FCFCFC",
|
|
borderRadius: 8,
|
|
padding: 16,
|
|
borderWidth: 1,
|
|
borderColor: "transparent",
|
|
minHeight: 180,
|
|
},
|
|
radioContainer: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
flex: 1,
|
|
marginBottom: 16,
|
|
},
|
|
radioDot: {
|
|
width: 18,
|
|
height: 18,
|
|
borderRadius: 9,
|
|
borderWidth: 2,
|
|
borderColor: "#D1D5DB",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
marginRight: 12,
|
|
},
|
|
selectedRadioDot: {
|
|
borderColor: "#009E71",
|
|
},
|
|
radioInner: {
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
backgroundColor: "#009E71",
|
|
},
|
|
radioLabel: {
|
|
fontSize: 14,
|
|
color: "#252936",
|
|
fontWeight: "400",
|
|
},
|
|
amountText: {
|
|
fontSize: 14,
|
|
color: "#252936",
|
|
fontWeight: "400",
|
|
},
|
|
inputContainer: {
|
|
marginBottom: 16,
|
|
},
|
|
textInput: {
|
|
backgroundColor: "#FFFFFF",
|
|
borderWidth: 1,
|
|
borderColor: "#D8DDE7",
|
|
borderRadius: 4,
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 10,
|
|
fontSize: 14,
|
|
color: "#252936",
|
|
height: 40,
|
|
},
|
|
errorInput: {
|
|
borderColor: "#EF4444",
|
|
},
|
|
helperContainer: {
|
|
marginTop: 4,
|
|
},
|
|
helperText: {
|
|
fontSize: 14,
|
|
color: "#565F70",
|
|
},
|
|
errorText: {
|
|
color: "#EF4444",
|
|
},
|
|
generalErrorText: {
|
|
fontSize: 14,
|
|
color: "#EF4444",
|
|
textAlign: "center",
|
|
marginTop: 8,
|
|
},
|
|
chipsContainer: {
|
|
flexDirection: "row",
|
|
gap: 8,
|
|
flexWrap: "wrap",
|
|
},
|
|
chip: {
|
|
backgroundColor: "#F3F4F6",
|
|
borderWidth: 1,
|
|
borderColor: "#D8DDE7",
|
|
borderRadius: 4,
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
minWidth: 68,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
height: 28,
|
|
},
|
|
chipText: {
|
|
fontSize: 14,
|
|
color: "#252936",
|
|
fontWeight: "500",
|
|
},
|
|
buttonContainer: {
|
|
padding: 16,
|
|
backgroundColor: "#F3F4F6",
|
|
},
|
|
payButton: {
|
|
backgroundColor: "#009E71",
|
|
borderRadius: 4,
|
|
paddingVertical: 8,
|
|
paddingHorizontal: 16,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
height: 40,
|
|
},
|
|
disabledButton: {
|
|
backgroundColor: "#94A3B8",
|
|
},
|
|
payButtonText: {
|
|
fontSize: 14,
|
|
color: "#FCFCFC",
|
|
fontWeight: "500",
|
|
},
|
|
});
|
|
|
|
export default SelectAmountScreen;
|