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}>
<TouchableOpacity
style={styles.primaryButton}
style={[
styles.primaryButton,
(isLoading || !emiDetails) && { opacity: 0.5 }, // dim if disabled
]}
onPress={() => router.push("/payments/selectAmount")}
disabled={isLoading || !emiDetails}
>
<Text style={styles.primaryButtonText}>Pay EMI</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.tertiaryButton}
style={[
styles.tertiaryButton,
(isLoading || !emiDetails) && { opacity: 0.5 },
]}
onPress={() => router.push("/payments/myPlan")}
disabled={isLoading || !emiDetails}
>
<Text style={styles.tertiaryButtonText}>View Plan</Text>
</TouchableOpacity>

View File

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

View File

@ -5,6 +5,8 @@ import { useSelector } from "react-redux";
import { RootState } from "@/store/rootReducer";
import Header from "@/components/common/Header";
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";
const PaymentConfirmationScreen = () => {
@ -15,6 +17,9 @@ const PaymentConfirmationScreen = () => {
const insets = useSafeAreaInsets();
// Get payment status - assuming it comes from paymentOrder.status
const paymentStatus = paymentOrder?.status || "confirmed";
// Format amount with currency
const formatAmount = (amount: number | null) => {
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 (
<View style={styles.container}>
<Header title="Payment Status" />
@ -52,15 +94,15 @@ const PaymentConfirmationScreen = () => {
<View style={styles.contentFrame}>
<View style={styles.qrFrame}>
<View style={styles.paymentStatusContainer}>
<View style={styles.successIconContainer}>
<CheckCircle />
<View style={statusDisplay.iconContainerStyle}>
{statusDisplay.icon}
</View>
<Text style={styles.amountText}>
{formatAmount(paymentOrder?.amount || due_amount)}
</Text>
<View style={styles.statusContainer}>
<Text style={styles.statusText}>Payment successful</Text>
<Text style={styles.statusText}>{statusDisplay.text}</Text>
<Text style={styles.dateText}>
{formatDate(paymentOrder?.transaction_date)}
</Text>
@ -75,42 +117,43 @@ const PaymentConfirmationScreen = () => {
<View style={styles.detailRow}>
<Text style={styles.detailLabel}>Payment mode</Text>
<Text style={styles.detailValue}>
{paymentOrder?.payment_mode?.[0] || "UPI"}
{displayValue(paymentOrder?.payment_mode?.[0])}
</Text>
</View>
<View style={styles.detailRow}>
<Text style={styles.detailLabel}>Paid to</Text>
<Text style={styles.detailValue}>
{paymentOrder?.upi_handle || "randomupiid@vecpay"}
{displayValue(paymentOrder?.upi_handle)}
</Text>
</View>
<View style={styles.detailRow}>
<Text style={styles.detailLabel}>Paid by</Text>
<Text style={styles.detailValue}>
{paymentOrder?.paid_by_upi_handle || "amar.kesari@vecpay"}
{displayValue(paymentOrder?.paid_by_upi_handle)}
</Text>
</View>
<View style={styles.detailRow}>
<Text style={styles.detailLabel}>Order ID</Text>
<Text style={styles.detailValue}>
{paymentOrder?.order_id || "1000516861984940"}
{displayValue(paymentOrder?.order_id)}
</Text>
</View>
<View style={styles.detailRow}>
<Text style={styles.detailLabel}>Transaction ID</Text>
<Text style={styles.detailValue}>
{paymentOrder?.transaction_id ||
paymentOrder?.transaction_order_id ||
"1000516861984940"}
{displayValue(
paymentOrder?.transaction_id ||
paymentOrder?.transaction_order_id
)}
</Text>
</View>
<View style={styles.detailRow}>
<Text style={styles.detailLabel}>RRN</Text>
<Text style={styles.detailValue}>
{paymentOrder?.payment_reference_id || "1000516861984940"}
{displayValue(paymentOrder?.payment_reference_id)}
</Text>
</View>
</View>
@ -153,6 +196,14 @@ const styles = StyleSheet.create({
marginBottom: 16,
alignItems: "center",
},
failureIconContainer: {
marginBottom: 16,
alignItems: "center",
},
pendingIconContainer: {
marginBottom: 16,
alignItems: "center",
},
successIcon: {
// 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> => {
try {
if (await Sharing.isAvailableAsync()) {
@ -207,6 +187,10 @@ const UpiPaymentScreen = () => {
}
};
function handlePaymentDone() {
router.push("/(tabs)/payments");
}
return (
<SafeAreaView style={styles.container}>
<Header title="Pay EMI" showBackButton={false} />
@ -216,13 +200,13 @@ const UpiPaymentScreen = () => {
<View style={styles.amountSection}>
<Text style={styles.amountLabel}>Amount to be paid</Text>
<Text style={styles.amount}>
{formatAmount(paymentOrder.amount)}
{formatAmount(paymentOrder?.amount)}
</Text>
</View>
<View style={styles.qrCodeContainer}>
<Image
source={{ uri: paymentOrder.qr_code_url }}
source={{ uri: paymentOrder?.qr_code_url }}
style={styles.qrCode}
contentFit="contain"
/>
@ -242,20 +226,53 @@ const UpiPaymentScreen = () => {
<Text style={styles.secondaryButtonText}>Download QR</Text>
</TouchableOpacity>
</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>
<TouchableOpacity
onPress={payUsingUpiApp}
style={[styles.primaryButton, { marginBottom: insets.bottom || 20 }]}
>
<Text style={[styles.primaryButtonText]}>Pay using UPI app</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
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: {
flex: 1,
backgroundColor: "#F3F5F8",
@ -288,9 +305,9 @@ const styles = StyleSheet.create({
},
content: {
flex: 1,
justifyContent: "space-between",
paddingHorizontal: 16,
paddingBottom: 16,
justifyContent: "space-between",
},
qrFrame: {
backgroundColor: "#FCFCFC",
@ -327,19 +344,18 @@ const styles = StyleSheet.create({
color: "#FFFFFF",
},
qrCodeContainer: {
width: 200,
height: 200,
justifyContent: "center",
alignItems: "center",
marginBottom: 16,
borderWidth: 2,
borderColor: "#E5E9F0",
padding: 12,
padding: 16,
borderRadius: 8,
width: "100%",
height: "auto",
},
qrCode: {
width: "100%",
height: "100%",
aspectRatio: 1,
alignSelf: "center",
},
upiIdSection: {
flexDirection: "row",
@ -398,6 +414,8 @@ const styles = StyleSheet.create({
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 4,
marginTop: 16,
width: "100%",
alignItems: "center",
},
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" />
<Stack
screenOptions={{
headerShown: true,
headerShown: false,
headerTitleStyle: {
fontSize: 16,
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 { setUserData } from "@/store/userSlice";
import { Overlay } from "@/components/common/Overlay";
import Header from "@/components/common/Header";
export default function ProfileScreen() {
const [isLangaugeModalVisible, setLanguageModalVisible] =
@ -91,6 +92,7 @@ export default function ProfileScreen() {
};
return (
<>
<Header title="My Account" showBackButton={true} />
<ScrollView contentContainerStyle={styles.scrollContent}>
<View style={styles.avatarContainer}>
<Image
@ -134,7 +136,7 @@ export default function ProfileScreen() {
</View>
<View style={styles.card}>
{menuItem("My Vehicle")}
{menuItem("My Vehicle", () => router.push("/user/MyVechicle"))}
<View style={styles.divider} />
{menuItem("Language", () => toggleLanguageModal())}
</View>