feature/app-setup
vinay kumar 2025-08-18 10:47:16 +05:30
parent 9dd62f9377
commit c5a3e5b8a0
7 changed files with 338 additions and 59 deletions

View File

@ -429,15 +429,23 @@ export default function PaymentsTabScreen() {
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
<TouchableOpacity <TouchableOpacity
style={styles.primaryButton} style={[
styles.primaryButton,
(isLoading || !emiDetails) && { opacity: 0.5 }, // dim if disabled
]}
onPress={() => router.push("/payments/selectAmount")} onPress={() => router.push("/payments/selectAmount")}
disabled={isLoading || !emiDetails}
> >
<Text style={styles.primaryButtonText}>Pay EMI</Text> <Text style={styles.primaryButtonText}>Pay EMI</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={styles.tertiaryButton} style={[
styles.tertiaryButton,
(isLoading || !emiDetails) && { opacity: 0.5 },
]}
onPress={() => router.push("/payments/myPlan")} onPress={() => router.push("/payments/myPlan")}
disabled={isLoading || !emiDetails}
> >
<Text style={styles.tertiaryButtonText}>View Plan</Text> <Text style={styles.tertiaryButtonText}>View Plan</Text>
</TouchableOpacity> </TouchableOpacity>

View File

@ -8,6 +8,8 @@ import {
StyleSheet, StyleSheet,
GestureResponderEvent, GestureResponderEvent,
ScrollView, ScrollView,
KeyboardAvoidingView,
Platform,
} from "react-native"; } from "react-native";
import { Dropdown } from "react-native-element-dropdown"; import { Dropdown } from "react-native-element-dropdown";
import { DateTimePickerAndroid } from "@react-native-community/datetimepicker"; import { DateTimePickerAndroid } from "@react-native-community/datetimepicker";
@ -85,7 +87,7 @@ export default function ServiceFormScreen(): JSX.Element {
DateTimePickerAndroid.open({ DateTimePickerAndroid.open({
value: now, value: now,
mode: "date", mode: "date",
is24Hour: true, is24Hour: false,
display: "default", display: "default",
onChange: (event, selectedDate) => { onChange: (event, selectedDate) => {
if (event.type === "set" && selectedDate) { if (event.type === "set" && selectedDate) {
@ -93,7 +95,7 @@ export default function ServiceFormScreen(): JSX.Element {
DateTimePickerAndroid.open({ DateTimePickerAndroid.open({
value: selectedDate, value: selectedDate,
mode: "time", mode: "time",
is24Hour: true, is24Hour: false,
display: "default", display: "default",
onChange: (timeEvent, selectedTime) => { onChange: (timeEvent, selectedTime) => {
if (timeEvent.type === "set" && selectedTime) { if (timeEvent.type === "set" && selectedTime) {
@ -115,8 +117,17 @@ export default function ServiceFormScreen(): JSX.Element {
}; };
return ( return (
<> <KeyboardAvoidingView
<ScrollView style={styles.screen}> style={styles.container}
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === "ios" ? 100 : 0}
>
<ScrollView
style={styles.screen}
contentContainerStyle={styles.scrollContent}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
validationSchema={validationSchema} validationSchema={validationSchema}
@ -289,7 +300,7 @@ export default function ServiceFormScreen(): JSX.Element {
value={values.comments} value={values.comments}
/> />
<Text style={styles.wordCount}> <Text style={styles.wordCount}>
{values.comments.length}/100 words {values.comments?.length || 0}/100 words
</Text> </Text>
</View> </View>
@ -312,17 +323,20 @@ export default function ServiceFormScreen(): JSX.Element {
)} )}
</Formik> </Formik>
</ScrollView> </ScrollView>
</> </KeyboardAvoidingView>
); );
} }
// styles stay unchanged
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: {
flex: 1,
},
inputContainer: {}, inputContainer: {},
screen: { screen: {
flex: 1, flex: 1,
backgroundColor: "#F3F5F8", backgroundColor: "#F3F5F8",
},
scrollContent: {
paddingBottom: 116, paddingBottom: 116,
}, },
topBar: { topBar: {

View File

@ -5,6 +5,8 @@ import { useSelector } from "react-redux";
import { RootState } from "@/store/rootReducer"; import { RootState } from "@/store/rootReducer";
import Header from "@/components/common/Header"; import Header from "@/components/common/Header";
import CheckCircle from "@/assets/icons/check_circle.svg"; import CheckCircle from "@/assets/icons/check_circle.svg";
import Pending from "@/assets/icons/pending.svg";
import Failed from "@/assets/icons/cancel.svg";
import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useSafeAreaInsets } from "react-native-safe-area-context";
const PaymentConfirmationScreen = () => { const PaymentConfirmationScreen = () => {
@ -15,6 +17,9 @@ const PaymentConfirmationScreen = () => {
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
// Get payment status - assuming it comes from paymentOrder.status
const paymentStatus = paymentOrder?.status || "confirmed";
// Format amount with currency // Format amount with currency
const formatAmount = (amount: number | null) => { const formatAmount = (amount: number | null) => {
if (!amount) return "₹0"; if (!amount) return "₹0";
@ -45,6 +50,43 @@ const PaymentConfirmationScreen = () => {
}); });
}; };
// Get icon and text based on status
const getStatusDisplay = () => {
switch (paymentStatus) {
case "confirmed":
return {
icon: <CheckCircle />,
text: "Payment successful",
iconContainerStyle: styles.successIconContainer,
};
case "failure":
return {
icon: <Failed />,
text: "Payment failed",
iconContainerStyle: styles.failureIconContainer,
};
case "pending":
return {
icon: <Pending />,
text: "Payment pending",
iconContainerStyle: styles.pendingIconContainer,
};
default:
return {
icon: <CheckCircle />,
text: "Payment successful",
iconContainerStyle: styles.successIconContainer,
};
}
};
const statusDisplay = getStatusDisplay();
// Helper function to display value or "--" if missing
const displayValue = (value: any) => {
return value ? String(value) : "--";
};
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Header title="Payment Status" /> <Header title="Payment Status" />
@ -52,15 +94,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}>
<View style={styles.successIconContainer}> <View style={statusDisplay.iconContainerStyle}>
<CheckCircle /> {statusDisplay.icon}
</View> </View>
<Text style={styles.amountText}> <Text style={styles.amountText}>
{formatAmount(paymentOrder?.amount || due_amount)} {formatAmount(paymentOrder?.amount || due_amount)}
</Text> </Text>
<View style={styles.statusContainer}> <View style={styles.statusContainer}>
<Text style={styles.statusText}>Payment successful</Text> <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>
@ -75,42 +117,43 @@ const PaymentConfirmationScreen = () => {
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Payment mode</Text> <Text style={styles.detailLabel}>Payment mode</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{paymentOrder?.payment_mode?.[0] || "UPI"} {displayValue(paymentOrder?.payment_mode?.[0])}
</Text> </Text>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Paid to</Text> <Text style={styles.detailLabel}>Paid to</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{paymentOrder?.upi_handle || "randomupiid@vecpay"} {displayValue(paymentOrder?.upi_handle)}
</Text> </Text>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Paid by</Text> <Text style={styles.detailLabel}>Paid by</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{paymentOrder?.paid_by_upi_handle || "amar.kesari@vecpay"} {displayValue(paymentOrder?.paid_by_upi_handle)}
</Text> </Text>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Order ID</Text> <Text style={styles.detailLabel}>Order ID</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{paymentOrder?.order_id || "1000516861984940"} {displayValue(paymentOrder?.order_id)}
</Text> </Text>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Transaction ID</Text> <Text style={styles.detailLabel}>Transaction ID</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{paymentOrder?.transaction_id || {displayValue(
paymentOrder?.transaction_order_id || paymentOrder?.transaction_id ||
"1000516861984940"} paymentOrder?.transaction_order_id
)}
</Text> </Text>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>RRN</Text> <Text style={styles.detailLabel}>RRN</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{paymentOrder?.payment_reference_id || "1000516861984940"} {displayValue(paymentOrder?.payment_reference_id)}
</Text> </Text>
</View> </View>
</View> </View>
@ -153,6 +196,14 @@ const styles = StyleSheet.create({
marginBottom: 16, marginBottom: 16,
alignItems: "center", alignItems: "center",
}, },
failureIconContainer: {
marginBottom: 16,
alignItems: "center",
},
pendingIconContainer: {
marginBottom: 16,
alignItems: "center",
},
successIcon: { successIcon: {
// Icon styling handled by Ionicons // Icon styling handled by Ionicons
}, },

View File

@ -143,26 +143,6 @@ const UpiPaymentScreen = () => {
} }
}; };
const sharePaymentDetails = async (): Promise<void> => {
try {
const shareMessage =
`Payment Request\n\n` +
`Amount: ${formatAmount(paymentOrder.amount)}\n` +
`UPI ID: ${paymentOrder.upi_handle}\n` +
`Order ID: ${paymentOrder.order_id}\n` +
`Transaction ID: ${paymentOrder.transaction_id}\n` +
`Expires: ${getExpiryTime()}\n\n` +
`UPI Link: ${getUpiUrl()}`;
await Share.share({
message: shareMessage,
title: "UPI Payment Details",
});
} catch (error) {
showSnackbar("Failed to share payment details", "error");
}
};
const shareQR = async (): Promise<void> => { const shareQR = async (): Promise<void> => {
try { try {
if (await Sharing.isAvailableAsync()) { if (await Sharing.isAvailableAsync()) {
@ -207,6 +187,10 @@ const UpiPaymentScreen = () => {
} }
}; };
function handlePaymentDone() {
router.push("/(tabs)/payments");
}
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
<Header title="Pay EMI" showBackButton={false} /> <Header title="Pay EMI" showBackButton={false} />
@ -216,13 +200,13 @@ const UpiPaymentScreen = () => {
<View style={styles.amountSection}> <View style={styles.amountSection}>
<Text style={styles.amountLabel}>Amount to be paid</Text> <Text style={styles.amountLabel}>Amount to be paid</Text>
<Text style={styles.amount}> <Text style={styles.amount}>
{formatAmount(paymentOrder.amount)} {formatAmount(paymentOrder?.amount)}
</Text> </Text>
</View> </View>
<View style={styles.qrCodeContainer}> <View style={styles.qrCodeContainer}>
<Image <Image
source={{ uri: paymentOrder.qr_code_url }} source={{ uri: paymentOrder?.qr_code_url }}
style={styles.qrCode} style={styles.qrCode}
contentFit="contain" contentFit="contain"
/> />
@ -242,20 +226,53 @@ const UpiPaymentScreen = () => {
<Text style={styles.secondaryButtonText}>Download QR</Text> <Text style={styles.secondaryButtonText}>Download QR</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<TouchableOpacity
onPress={payUsingUpiApp}
style={[styles.primaryButton]}
>
<Text style={[styles.primaryButtonText]}>Pay using UPI app</Text>
</TouchableOpacity>
</View>
<View style={[styles.confirm, { marginBottom: insets.bottom }]}>
<Text style={styles.confirmTitle}>
Confirm once your payment is completed.
</Text>
<TouchableOpacity
onPress={() => handlePaymentDone()}
style={styles.paymentDone}
>
<Text style={styles.doneText}>Payment Done</Text>
</TouchableOpacity>
</View> </View>
<TouchableOpacity
onPress={payUsingUpiApp}
style={[styles.primaryButton, { marginBottom: insets.bottom || 20 }]}
>
<Text style={[styles.primaryButtonText]}>Pay using UPI app</Text>
</TouchableOpacity>
</View> </View>
</SafeAreaView> </SafeAreaView>
); );
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
doneText: {
textAlign: "center",
fontWeight: "500",
fontSize: 14,
lineHeight: 20,
},
paymentDone: {
padding: 16,
borderWidth: 1,
borderColor: "#DCE1E9",
},
confirmTitle: {
fontWeight: "400",
fontSize: 14,
lineHeight: 14,
color: "#252A34",
},
confirm: {
padding: 16,
flexDirection: "column",
backgroundColor: "#FCFCFC",
gap: 16,
},
container: { container: {
flex: 1, flex: 1,
backgroundColor: "#F3F5F8", backgroundColor: "#F3F5F8",
@ -288,9 +305,9 @@ const styles = StyleSheet.create({
}, },
content: { content: {
flex: 1, flex: 1,
justifyContent: "space-between",
paddingHorizontal: 16, paddingHorizontal: 16,
paddingBottom: 16, paddingBottom: 16,
justifyContent: "space-between",
}, },
qrFrame: { qrFrame: {
backgroundColor: "#FCFCFC", backgroundColor: "#FCFCFC",
@ -327,19 +344,18 @@ const styles = StyleSheet.create({
color: "#FFFFFF", color: "#FFFFFF",
}, },
qrCodeContainer: { qrCodeContainer: {
width: 200,
height: 200,
justifyContent: "center",
alignItems: "center",
marginBottom: 16, marginBottom: 16,
borderWidth: 2, borderWidth: 2,
borderColor: "#E5E9F0", borderColor: "#E5E9F0",
padding: 12, padding: 16,
borderRadius: 8, borderRadius: 8,
width: "100%",
height: "auto",
}, },
qrCode: { qrCode: {
width: "100%", width: "100%",
height: "100%", aspectRatio: 1,
alignSelf: "center",
}, },
upiIdSection: { upiIdSection: {
flexDirection: "row", flexDirection: "row",
@ -398,6 +414,8 @@ const styles = StyleSheet.create({
paddingVertical: 12, paddingVertical: 12,
paddingHorizontal: 16, paddingHorizontal: 16,
borderRadius: 4, borderRadius: 4,
marginTop: 16,
width: "100%",
alignItems: "center", alignItems: "center",
}, },
primaryButtonText: { primaryButtonText: {

186
app/user/MyVechicle.tsx Normal file
View File

@ -0,0 +1,186 @@
import React, { useEffect } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity,
SafeAreaView,
StatusBar,
ActivityIndicator,
} from "react-native";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../../store/rootReducer"; // Adjust path as needed
import { getUserDetails } from "../../store/userSlice";
import { AppDispatch } from "@/store";
import Header from "@/components/common/Header";
interface MyVehicleScreenProps {
navigation?: any; // Replace with proper navigation type
}
const MyVehicleScreen: React.FC<MyVehicleScreenProps> = ({ navigation }) => {
const dispatch = useDispatch<AppDispatch>();
const {
data: userData,
loading,
error,
} = useSelector((state: RootState) => state.user);
useEffect(() => {
// Fetch user details when component mounts
if (!userData) {
dispatch(getUserDetails());
}
}, [dispatch, userData]);
// Get the first vehicle from the user data
const vehicle = userData?.vehicles?.[0];
if (loading) {
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor="#ffffff" />
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#25324B" />
<Text style={styles.loadingText}>Loading vehicle details...</Text>
</View>
</SafeAreaView>
);
}
if (error) {
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor="#ffffff" />
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Error: {error}</Text>
</View>
</SafeAreaView>
);
}
return (
<>
<Header title="My Vehicle" showBackButton={true} />
<View style={styles.content}>
<View style={styles.itemContainer}>
{/* OEM - Model */}
<View style={styles.item}>
<Text style={styles.label}>OEM - Model</Text>
<Text style={styles.value}>
{vehicle?.model ? `Yatri - ${vehicle.model}` : "--"}
</Text>
</View>
<View style={styles.divider} />
{/* Chassis Number */}
<View style={styles.item}>
<Text style={styles.label}>Chassis Number</Text>
<Text style={styles.value}>{vehicle?.chasis_number || "--"}</Text>
</View>
<View style={styles.divider} />
{/* Vehicle ID */}
<View style={styles.item}>
<Text style={styles.label}>Vehicle ID</Text>
<Text style={styles.value}>
{vehicle?.vehicle_id ? vehicle.vehicle_id.toString() : "--"}
</Text>
</View>
</View>
</View>
</>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#ffffff",
},
header: {
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: "#f0f0f0",
},
backButton: {
marginRight: 12,
padding: 4,
},
backIcon: {
width: 24,
height: 24,
justifyContent: "center",
alignItems: "center",
},
backIconText: {
fontSize: 20,
fontWeight: "600",
color: "#25324B",
},
headerTitle: {
fontSize: 18,
fontWeight: "600",
color: "#25324B",
},
content: {
flex: 1,
paddingHorizontal: 16,
paddingTop: 16,
},
itemContainer: {
backgroundColor: "#FCFCFC",
borderRadius: 8,
overflow: "hidden",
},
item: {
paddingHorizontal: 16,
paddingVertical: 16,
},
label: {
fontSize: 14,
color: "#25324B",
fontWeight: "400",
marginBottom: 4,
lineHeight: 20,
},
value: {
fontSize: 14,
color: "#25324B",
fontWeight: "600",
lineHeight: 20,
},
divider: {
height: 1,
backgroundColor: "#E5E9F0",
marginHorizontal: 16,
},
loadingContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
loadingText: {
marginTop: 16,
fontSize: 16,
color: "#25324B",
},
errorContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
paddingHorizontal: 16,
},
errorText: {
fontSize: 16,
color: "#FF6B6B",
textAlign: "center",
},
});
export default MyVehicleScreen;

View File

@ -12,7 +12,7 @@ export default function UserLayout() {
<StatusBar style="dark" /> <StatusBar style="dark" />
<Stack <Stack
screenOptions={{ screenOptions={{
headerShown: true, headerShown: false,
headerTitleStyle: { headerTitleStyle: {
fontSize: 16, fontSize: 16,
color: "#252A34", color: "#252A34",

View File

@ -21,6 +21,7 @@ import { AWS, BASE_URL, USER_PROFILE } from "@/constants/config";
import { bytesToMB, updateUserProfile, uploadImage } from "@/utils/User"; import { bytesToMB, updateUserProfile, uploadImage } from "@/utils/User";
import { setUserData } from "@/store/userSlice"; import { setUserData } from "@/store/userSlice";
import { Overlay } from "@/components/common/Overlay"; import { Overlay } from "@/components/common/Overlay";
import Header from "@/components/common/Header";
export default function ProfileScreen() { export default function ProfileScreen() {
const [isLangaugeModalVisible, setLanguageModalVisible] = const [isLangaugeModalVisible, setLanguageModalVisible] =
@ -91,6 +92,7 @@ export default function ProfileScreen() {
}; };
return ( return (
<> <>
<Header title="My Account" showBackButton={true} />
<ScrollView contentContainerStyle={styles.scrollContent}> <ScrollView contentContainerStyle={styles.scrollContent}>
<View style={styles.avatarContainer}> <View style={styles.avatarContainer}>
<Image <Image
@ -134,7 +136,7 @@ export default function ProfileScreen() {
</View> </View>
<View style={styles.card}> <View style={styles.card}>
{menuItem("My Vehicle")} {menuItem("My Vehicle", () => router.push("/user/MyVechicle"))}
<View style={styles.divider} /> <View style={styles.divider} />
{menuItem("Language", () => toggleLanguageModal())} {menuItem("Language", () => toggleLanguageModal())}
</View> </View>