diff --git a/App.js b/App.js deleted file mode 100644 index 80d3d99..0000000 --- a/App.js +++ /dev/null @@ -1 +0,0 @@ -import "expo-router/entry"; diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 0be4815..3d0fb13 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -38,6 +38,7 @@ import { import api from "@/services/axiosClient"; import { setDueAmount } from "@/store/paymentSlice"; import { EmiResponse } from "./payments"; +import { Image } from "expo-image"; export default function HomeScreen() { const { t } = useTranslation(); @@ -137,21 +138,23 @@ export default function HomeScreen() { setIsSupportModalVisible(true)} + onPress={() => { + setIsSupportModalVisible(true); + console.log("hkdlfjaldf"); + }} > - { - router.push("/user/profile"); - }} - > + { + router.push("/user/profile"); + }} /> - + ), }); @@ -229,7 +232,7 @@ export default function HomeScreen() { : "danger" } message={daysLeftToPayEmiText} - subMessage="Pay now" + subMessage={t("home.pay-now")} redirectPath="/(tabs)/payments" /> )} @@ -251,12 +254,12 @@ export default function HomeScreen() { {due_amount && ( { router.push("/payments/selectAmount"); @@ -269,7 +272,7 @@ export default function HomeScreen() { Fetching Location... - ) : lat != null && lon != null ? ( + ) : lat != null && lon != null && !(lat == 0 && lon == 0) ? ( <> + > + + @@ -302,24 +309,24 @@ export default function HomeScreen() { style={styles.viewLocationText} onPress={openInGoogleMaps} > - View Vehicle Location + {t("home.view-vehicle-location")} ) : ( - error && ( - - - Error fetching location - - ) + + + {t("home.error-location")} + )} {warrantyEndDate && warrantyStartDate && ( - + router.push("/(tabs)/my-battery")}> + + )} { const { data } = useSelector((state: RootState) => state.user); - + const { t } = useTranslation(); const model = data?.batteries[0]?.battery_model ?? "---"; const batteryId = data?.batteries[0]?.battery_id ?? "---"; const bmsId = data?.batteries[0]?.bms_id ?? "---"; @@ -48,7 +49,19 @@ const BatteryDetails = () => { (remaining % (30.44 * 24 * 60 * 60 * 1000)) / (24 * 60 * 60 * 1000) ); - durationText = `${yearsLeft} years, ${monthsLeft} months, ${daysLeft} days`; + const parts: string[] = []; + + if (yearsLeft > 0) { + parts.push(`${yearsLeft} year${yearsLeft !== 1 ? "s" : ""}`); + } + if (monthsLeft > 0) { + parts.push(`${monthsLeft} month${monthsLeft !== 1 ? "s" : ""}`); + } + if (daysLeft > 0 || parts.length === 0) { + parts.push(`${daysLeft} day${daysLeft !== 1 ? "s" : ""}`); + } + + durationText = parts.join(", "); } const formatDate = (date?: Date | null): string => @@ -70,29 +83,37 @@ const BatteryDetails = () => { return ( - {/* Battery Card */} + {/* Battery Section */} - Battery - {isInWarranty && ( + {t("battery.battery")} + {isInWarranty ? ( - In Warranty + {t("battery.in-warranty")} + + ) : ( + + + {t("battery.warranty-expired")} + )} - - - + + + - {/* Warranty Details */} + {/* Battery Warranty Details Section */} - Battery Warranty Details + + {t("battery.battery-warranty-details")} + - - - + + + {start && end && !isNaN(start.getTime()) && !isNaN(end.getTime()) && ( { )} - {/* VIM Details */} + {/* VIM Details Section */} - VIM Details + {t("battery.vim-details")} - - + + - {/* Charger Details */} + {/* Charger Details Section */} - Charger Details + {t("battery.charger-details")} - + ); @@ -138,9 +159,14 @@ const InfoRow: React.FC = ({ label, value }) => ( export default BatteryDetails; const styles = StyleSheet.create({ + expiredBadgeText: { + fontSize: 12, + fontWeight: "500", + color: "#D51D10", + }, container: { backgroundColor: "#F3F5F8", - padding: 16, + paddingHorizontal: 16, }, card: { backgroundColor: "#FCFCFC", @@ -158,6 +184,12 @@ const styles = StyleSheet.create({ fontWeight: "600", color: "#252A34", }, + expiredBadge: { + backgroundColor: "#FDE9E7", + paddingVertical: 2, + paddingHorizontal: 8, + borderRadius: 4, + }, badge: { backgroundColor: "#DAF5ED", paddingVertical: 2, diff --git a/app/(tabs)/payments.tsx b/app/(tabs)/payments.tsx index cfc695c..4292dfc 100644 --- a/app/(tabs)/payments.tsx +++ b/app/(tabs)/payments.tsx @@ -27,12 +27,17 @@ import api from "@/services/axiosClient"; import PaymentHistoryCard from "@/components/Payments/PaymentHistoryCard"; import { BASE_URL } from "@/constants/config"; import { useDispatch } from "react-redux"; -import { setDueAmount, setMyPlan } from "@/store/paymentSlice"; +import { + setAdvanceBalance, + setDueAmount, + setMyPlan, +} from "@/store/paymentSlice"; import { ActivityIndicator } from "react-native-paper"; import { useFocusEffect } from "@react-navigation/native"; import { displayValue } from "@/utils/Common"; import RefreshIcon from "@/assets/icons/refresh.svg"; import CustomerSupport from "@/components/home/CustomerSupportModal"; +import { useTranslation } from "react-i18next"; export interface MyPlan { no_of_emi: number; @@ -145,6 +150,7 @@ export default function PaymentsTabScreen() { try { setIsLoading(true); setEmiDetails(null); + setAdvanceBalance(null); const response = await api.get(`/api/v1/emi-details`); const result: EmiResponse = response.data; @@ -156,6 +162,7 @@ export default function PaymentsTabScreen() { dispatch(setDueAmount(details.due_amount)); dispatch(setMyPlan(details.myPlain)); + dispatch(setAdvanceBalance(details.advance_balance)); } else { showSnackbar("No EMI details found", "error"); } @@ -246,18 +253,14 @@ export default function PaymentsTabScreen() { > - { - router.push("/user/profile"); - }} - > + router.push("/user/profile")} textSize={20} boxSize={40} /> - + ), }); @@ -367,6 +370,7 @@ export default function PaymentsTabScreen() { setIsEndReached(isAtBottom); }; + const { t } = useTranslation(); return ( <> {/* Header */} - Last EMI Details + + {t("payment.last-emi-details")} + {getCurrentMonthYear()} @@ -389,14 +395,14 @@ export default function PaymentsTabScreen() { {/* EMI Details Content */} - Amount Due + {t("payment.amount-due")} {displayValue(emiDetails?.due_amount, formatCurrency)} - Amount Paid + {t("payment.amount-paid")} {displayValue( emiDetails?.total_amount_paid_in_current_cycle, @@ -406,14 +412,16 @@ export default function PaymentsTabScreen() { - Due Date + {t("payment.due-date")} {displayValue(emiDetails?.due_date)} - Payment Status + + {t("payment.payment-status")} + {emiDetails?.status ? ( router.push("/payments/selectAmount")} disabled={isLoading || !emiDetails} > - Pay EMI + + {t("payment.pay-emi")} + router.push("/payments/myPlan")} disabled={isLoading || !emiDetails} > - View Plan + + {t("payment.view-plan")} + - Payment History + + {t("payment.payment-history")} + {isHistoryLoading ? ( @@ -483,7 +497,9 @@ export default function PaymentsTabScreen() { style={styles.viewAllButton} onPress={handleViewAll} > - View all + + {t("payment.view-all")} + )} diff --git a/app/(tabs)/service.tsx b/app/(tabs)/service.tsx index dc849ea..5cd5ac6 100644 --- a/app/(tabs)/service.tsx +++ b/app/(tabs)/service.tsx @@ -25,6 +25,7 @@ import { useSnackbar } from "@/contexts/Snackbar"; import { BASE_URL } from "@/constants/config"; import { Overlay } from "@/components/common/Overlay"; import CrossIcon from "@/assets/icons/close_white.svg"; +import { useTranslation } from "react-i18next"; interface FormValues { serviceType: string | null; @@ -66,7 +67,7 @@ export default function ServiceFormScreen(): JSX.Element { let result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: true, - quality: 1, + quality: 0.5, allowsMultipleSelection: true, }); @@ -89,6 +90,7 @@ export default function ServiceFormScreen(): JSX.Element { mode: "date", is24Hour: false, display: "default", + minimumDate: now, onChange: (event, selectedDate) => { if (event.type === "set" && selectedDate) { // When date is selected, show time picker next @@ -107,6 +109,11 @@ export default function ServiceFormScreen(): JSX.Element { selectedTime.getHours(), selectedTime.getMinutes() ); + + if (combinedDate < now) { + showSnackbar(`${t("service.select-valid-time")}`, "error"); + return; + } setFieldValue("date", combinedDate); } }, @@ -116,6 +123,8 @@ export default function ServiceFormScreen(): JSX.Element { }); }; + const { t } = useTranslation(); + return ( - Service Type * + {t("service.service-type")}{" "} + * setIsFocus(true)} onBlur={() => setIsFocus(false)} @@ -222,7 +241,7 @@ export default function ServiceFormScreen(): JSX.Element { - Issues * + {t("service.issue")} * {values.issues.length > 0 ? values.issues.length + " issues selected" - : "Select Issue"} + : `${t("service.select-issue")}`} @@ -242,7 +261,8 @@ export default function ServiceFormScreen(): JSX.Element { - Select Date and Time * + {t("service.select-datetime")}{" "} + * showPicker(values.date, setFieldValue)} @@ -261,10 +281,12 @@ export default function ServiceFormScreen(): JSX.Element { onPress={() => handlePhotoPick(setFieldValue, values.photos)} > - Add photos + + {t("service.add-photos")}{" "} + - Supported formats include JPG, JPEG and PNG. + {t("service.supported-formats")} {/* Selected Images Preview */} @@ -290,7 +312,7 @@ export default function ServiceFormScreen(): JSX.Element { {errors.photos} )} - Comments + {t("service.comments")} - {values.comments?.length || 0}/100 words + {values.comments?.length || 0}/100 {t("service.words")} @@ -308,7 +330,7 @@ export default function ServiceFormScreen(): JSX.Element { style={styles.submitButton} onPress={handleSubmit as (e?: GestureResponderEvent) => void} > - Submit + {t("service.submit")} { setFieldValue("issues", selectedIssues); }} + initialSelectedValues={values.issues} /> {isSubmitting && } diff --git a/app/payments/myPlan.tsx b/app/payments/myPlan.tsx index 0f84159..e7d0d1d 100644 --- a/app/payments/myPlan.tsx +++ b/app/payments/myPlan.tsx @@ -3,23 +3,22 @@ import ProgressCard from "@/components/common/ProgressCard"; import { RootState } from "@/store/rootReducer"; import { useRouter } from "expo-router"; import React from "react"; -import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import { useTranslation } from "react-i18next"; +import { + View, + Text, + StyleSheet, + TouchableOpacity, + RootViewStyleProvider, + ScrollView, +} from "react-native"; import { useSelector } from "react-redux"; -interface MyPlan { - planType?: string; - totalCost?: number; - downPayment?: number; - totalEmi?: number; - monthlyEmi?: number; - installmentsPaid?: number; - totalInstallments?: number; - emiPaidTillNow?: number; - totalAmountDue?: number; -} - const MyPlanScreen: React.FC = () => { const myPlan = useSelector((state: RootState) => state.payments.myPlan); + const advance_balance = useSelector( + (state: RootState) => state.payments.advance_balance + ); const dueAmount = useSelector( (state: RootState) => state.payments.due_amount ); @@ -46,21 +45,21 @@ const MyPlanScreen: React.FC = () => { if (!firstValue || !secondValue) return 0; return (firstValue / secondValue) * 100; }; - + const { t } = useTranslation(); return ( - -
+ +
{/* Plan Details Card */} - Plan Details + {t("payment.plan-details")} {/* Plan Type Row */} - Plan Type + {t("payment.plan-type")} {displayValue(myPlan?.no_of_emi)} @@ -68,7 +67,7 @@ const MyPlanScreen: React.FC = () => { {/* Total Cost Row */} - Total Cost + {t("payment.total-cost")} {formatCurrency(myPlan?.total_amount)} @@ -76,7 +75,7 @@ const MyPlanScreen: React.FC = () => { {/* Down Payment Row */} - Down Payment + {t("payment.down-payment")} {formatCurrency(myPlan?.down_payment)} @@ -84,7 +83,7 @@ const MyPlanScreen: React.FC = () => { {/* Total EMI Row */} - Total EMI + {t("payment.total-emi")} {formatCurrency(myPlan?.total_emi)} @@ -94,7 +93,9 @@ const MyPlanScreen: React.FC = () => { {/* Total Amount Due Card */} - Total Amount Due + + {t("payment.total-amount-due")} + {formatCurrency(dueAmount)} @@ -103,14 +104,14 @@ const MyPlanScreen: React.FC = () => { {/* Monthly EMI Card */} - Monthly EMI + {t("payment.monthly-emi")} {formatCurrency(myPlan?.emi_amount)} - Installments Paid + {t("payment.installments-paid")} {displayValue(myPlan?.installment_paid)} @@ -123,7 +124,7 @@ const MyPlanScreen: React.FC = () => { { )} /> + + {t("payment.advance-amount")} + {formatCurrency(advance_balance)} + + router.push("/payments/selectAmount")} > - Pay EMI + {t("payment.pay-emi")} - + ); }; const styles = StyleSheet.create({ + amount: { + fontSize: 14, + fontWeight: "600", + color: "#252A34", + height: 20, + lineHeight: 20, + }, container: { flex: 1, backgroundColor: "#F3F5F8", diff --git a/app/payments/payEmi.tsx b/app/payments/payEmi.tsx index 6882453..ceb929f 100644 --- a/app/payments/payEmi.tsx +++ b/app/payments/payEmi.tsx @@ -9,6 +9,7 @@ import { Share, Linking, BackHandler, + ScrollView, } from "react-native"; import * as FileSystem from "expo-file-system"; import * as MediaLibrary from "expo-media-library"; @@ -26,6 +27,7 @@ import DownloadIcon from "@/assets/icons/download.svg"; import { payments } from "@/constants/config"; import { useRouter } from "expo-router"; import { useSocket } from "@/contexts/SocketContext"; +import { useTranslation } from "react-i18next"; const UpiPaymentScreen = () => { const dispatch = useDispatch(); @@ -191,14 +193,18 @@ const UpiPaymentScreen = () => { router.push("/(tabs)/payments"); } + const { t } = useTranslation(); + return ( - +
- Amount to be paid + + {t("payment.amount-to-be-paid")} + {formatAmount(paymentOrder?.amount)} @@ -215,7 +221,9 @@ const UpiPaymentScreen = () => { - Share QR + + {t("payment.share-qr")} + { style={styles.secondaryButton} > - Download QR + + {t("payment.download-qr")} + - Pay using UPI app + + {t("payment.pay-using-upi")} + - Confirm once your payment is completed. + {t("payment.confirm-payment")} handlePaymentDone()} style={styles.paymentDone} > - Payment Done + {t("payment.payment-done")} - + ); }; diff --git a/app/payments/selectAmount.tsx b/app/payments/selectAmount.tsx index 24ddd95..9371289 100644 --- a/app/payments/selectAmount.tsx +++ b/app/payments/selectAmount.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { View, Text, @@ -6,9 +6,8 @@ import { TouchableOpacity, TextInput, ScrollView, - Platform, KeyboardAvoidingView, - Alert, + Keyboard, } from "react-native"; import { useDispatch, useSelector } from "react-redux"; import { Formik } from "formik"; @@ -22,6 +21,8 @@ import { Overlay } from "@/components/common/Overlay"; import { useFocusEffect, useRouter } from "expo-router"; import { useSocket } from "@/contexts/SocketContext"; import { useSnackbar } from "@/contexts/Snackbar"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { useTranslation } from "react-i18next"; const validationSchema = Yup.object().shape({ paymentType: Yup.string().required("Please select a payment option"), @@ -71,6 +72,24 @@ const SelectAmountScreen = () => { const dispatch = useDispatch(); + const insets = useSafeAreaInsets(); + + const [keyboardVisible, setKeyboardVisible] = useState(false); + + useEffect(() => { + const showSub = Keyboard.addListener("keyboardDidShow", () => + setKeyboardVisible(true) + ); + const hideSub = Keyboard.addListener("keyboardDidHide", () => + setKeyboardVisible(false) + ); + + return () => { + showSub.remove(); + hideSub.remove(); + }; + }, []); + useFocusEffect( React.useCallback(() => { console.log( @@ -153,6 +172,7 @@ const SelectAmountScreen = () => { handleSubmit, setFieldValue, isValid, + validateField, dirty, }) => { const handleQuickAmountPress = (amount: number) => { @@ -161,6 +181,20 @@ const SelectAmountScreen = () => { : 0; const newAmount = currentAmount + amount; setFieldValue("customAmount", newAmount.toString()); + validateField("customAmount"); + }; + + const handleCustomAmountChange = (text: string) => { + const numericText = text.replace(/[^0-9.]/g, ""); + const parts = numericText.split("."); + const formattedText = + parts.length > 2 + ? parts[0] + "." + parts.slice(1).join("") + : numericText; + + setFieldValue("customAmount", formattedText, true); + setFieldValue("paymentType", "custom"); + validateField("customAmount"); // Trigger validation after change }; const getPaymentAmount = () => { @@ -170,30 +204,33 @@ const SelectAmountScreen = () => { return values.customAmount ? parseFloat(values.customAmount) : 0; }; - // Improved button state logic - const isPayButtonEnabled = () => { - if (values.paymentType === "due") return true; + const paymentAmount = getPaymentAmount(); - // For custom amount, check if it's valid and meets minimum requirement - if (values.paymentType === "custom") { - const amount = parseFloat(values.customAmount); - return ( - values.customAmount && - !isNaN(amount) && - amount >= payments.MIN_AMOUNT && - !errors.customAmount - ); - } + const isButtonEnabled = + values.paymentType === "due" || + (values.paymentType === "custom" && + !isNaN(paymentAmount) && + paymentAmount >= payments.MIN_AMOUNT); - return false; - }; + // 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"; + } + + const { t } = useTranslation(); return (
@@ -224,7 +261,9 @@ const SelectAmountScreen = () => { )} - Pay amount due + + {t("payment.pay-amount-due")} + ₹{dueAmount?.toFixed(2)} @@ -255,7 +294,9 @@ const SelectAmountScreen = () => { )} - Enter custom amount + + {t("payment.enter-custom-amount")} + @@ -267,17 +308,7 @@ const SelectAmountScreen = () => { styles.errorInput, ]} value={values.customAmount} - onChangeText={(text) => { - const numericText = text.replace(/[^0-9.]/g, ""); - const parts = numericText.split("."); - const formattedText = - parts.length > 2 - ? parts[0] + "." + parts.slice(1).join("") - : numericText; - - setFieldValue("customAmount", formattedText, true); - setFieldValue("paymentType", "custom"); - }} + onChangeText={handleCustomAmountChange} onBlur={handleBlur("customAmount")} placeholder="₹" placeholderTextColor="#94A3B8" @@ -297,7 +328,9 @@ const SelectAmountScreen = () => { > {touched.customAmount && errors.customAmount ? errors.customAmount - : `Minimum: ₹${payments.MIN_AMOUNT}`} + : `${t("payment.minimum")}: ₹${ + payments.MIN_AMOUNT + }`} @@ -324,14 +357,19 @@ const SelectAmountScreen = () => { - + handleSubmit()} - disabled={!isPayButtonEnabled()} + disabled={!isButtonEnabled} > {getPaymentAmount() < payments.MIN_AMOUNT ? ( Select Amount diff --git a/app/user/MyVechicle.tsx b/app/user/MyVechicle.tsx index a96f6bb..f918ab9 100644 --- a/app/user/MyVechicle.tsx +++ b/app/user/MyVechicle.tsx @@ -13,6 +13,7 @@ import { RootState } from "../../store/rootReducer"; // Adjust path as needed import { getUserDetails } from "../../store/userSlice"; import { AppDispatch } from "@/store"; import Header from "@/components/common/Header"; +import { useTranslation } from "react-i18next"; interface MyVehicleScreenProps { navigation?: any; // Replace with proper navigation type @@ -59,14 +60,15 @@ const MyVehicleScreen: React.FC = ({ navigation }) => { ); } + const { t } = useTranslation(); return ( <> -
+
{/* OEM - Model */} - OEM - Model + {t("profile.oem-model")} {vehicle?.model ? `Yatri - ${vehicle.model}` : "--"} @@ -76,7 +78,7 @@ const MyVehicleScreen: React.FC = ({ navigation }) => { {/* Chassis Number */} - Chassis Number + {t("profile.chassis-number")} {vehicle?.chasis_number || "--"} @@ -84,7 +86,7 @@ const MyVehicleScreen: React.FC = ({ navigation }) => { {/* Vehicle ID */} - Vehicle ID + {t("profile.vehicle-id")} {vehicle?.vehicle_id ? vehicle.vehicle_id.toString() : "--"} diff --git a/app/user/edit_name.tsx b/app/user/edit_name.tsx index d153ff3..bac801f 100644 --- a/app/user/edit_name.tsx +++ b/app/user/edit_name.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useTransition } from "react"; import { View, Text, @@ -20,6 +20,8 @@ import { setUserData } from "@/store/userSlice"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { router } from "expo-router"; import { useSnackbar } from "@/contexts/Snackbar"; +import Header from "@/components/common/Header"; +import { useTranslation } from "react-i18next"; export default function EditName() { const { data } = useSelector((state: RootState) => state.user); @@ -47,73 +49,82 @@ export default function EditName() { } }; + const { t } = useTranslation(); + return ( - - - - - {({ - handleChange, - handleBlur, - handleSubmit, - values, - touched, - errors, - }) => { - const hasChanged = values.name !== originalName; - const hasError = !!errors.name; + <> +
+ + + + + {({ + handleChange, + handleBlur, + handleSubmit, + values, + touched, + errors, + }) => { + const hasChanged = values.name !== originalName; + const hasError = !!errors.name; - return ( - - - Enter Name - + + + {t("profile.enter-name")} + + + {touched.name && errors.name && ( + {errors.name} + )} + + + void} style={[ - styles.input, - { - borderColor: - touched.name && errors.name ? "#D51D10" : "#D8DDE7", - }, + styles.button, + hasChanged && !hasError + ? styles.buttonEnabled + : styles.buttonDisabled, + { marginBottom: insets.bottom }, ]} - value={values.name} - onChangeText={handleChange("name")} - onBlur={handleBlur("name")} - placeholder="Enter your name" - placeholderTextColor="#949CAC" - /> - {touched.name && errors.name && ( - {errors.name} - )} + disabled={!hasChanged || hasError} + > + {t("profile.save")} + - - void} - style={[ - styles.button, - hasChanged && !hasError - ? styles.buttonEnabled - : styles.buttonDisabled, - { marginBottom: insets.bottom }, - ]} - disabled={!hasChanged || hasError} - > - Save - - - ); - }} - - - - + ); + }} + + + + + ); } @@ -126,7 +137,6 @@ const styles = StyleSheet.create({ inner: { flex: 1, justifyContent: "space-between", - paddingVertical: 24, backgroundColor: "#F3F5F8", }, formContainer: { diff --git a/app/user/profile.tsx b/app/user/profile.tsx index a8ccf66..9ccc7ee 100644 --- a/app/user/profile.tsx +++ b/app/user/profile.tsx @@ -22,6 +22,7 @@ import { bytesToMB, updateUserProfile, uploadImage } from "@/utils/User"; import { setUserData } from "@/store/userSlice"; import { Overlay } from "@/components/common/Overlay"; import Header from "@/components/common/Header"; +import { useTranslation } from "react-i18next"; export default function ProfileScreen() { const [isLangaugeModalVisible, setLanguageModalVisible] = @@ -90,9 +91,11 @@ export default function ProfileScreen() { } } }; + + const { t } = useTranslation(); return ( <> -
+
- Name + {t("profile.name")} {userName} - Mobile Number + {t("profile.mobile-number")} {mobileNumber} - {menuItem("My Vehicle", () => router.push("/user/MyVechicle"))} + {menuItem(`${t("profile.my-vehicle")}`, () => + router.push("/user/MyVechicle") + )} - {menuItem("Language", () => toggleLanguageModal())} + {menuItem(`${t("profile.language")}`, () => toggleLanguageModal())} - {menuItem("About App")} + {menuItem(`${t("profile.about-app")}`)} - {menuItem("Logout", handleLogout)} + {menuItem(`${t("profile.logout")}`, handleLogout)} + + + + + + + + diff --git a/assets/images/user_image.jpg b/assets/images/user_image.jpg index efd2f94..8cecc30 100644 Binary files a/assets/images/user_image.jpg and b/assets/images/user_image.jpg differ diff --git a/assets/images/user_image.png b/assets/images/user_image.png new file mode 100644 index 0000000..800c305 Binary files /dev/null and b/assets/images/user_image.png differ diff --git a/assets/images/user_image.webp b/assets/images/user_image.webp new file mode 100644 index 0000000..f71e2ad Binary files /dev/null and b/assets/images/user_image.webp differ diff --git a/components/Profile/LangaugeModal.tsx b/components/Profile/LangaugeModal.tsx index a064b39..dd18de1 100644 --- a/components/Profile/LangaugeModal.tsx +++ b/components/Profile/LangaugeModal.tsx @@ -3,6 +3,7 @@ import BottomSheetModal from "@/components/common/BottomSheetModal"; // adjust p import { StyleSheet, Text, TouchableOpacity } from "react-native"; import { useEffect, useState } from "react"; import { getLanguage, setLanguage } from "@/services/i18n"; +import { useTranslation } from "react-i18next"; interface CustomerSupportProps { visible: boolean; @@ -22,6 +23,8 @@ export default function LanguageModal({ })(); }, []); + const { t } = useTranslation(); + const handleLanguagePress = (lang: "en" | "hi") => { setSelectedLang(lang); setLanguage(lang); @@ -31,7 +34,7 @@ export default function LanguageModal({ = ({ (remaining % (30.44 * 24 * 60 * 60 * 1000)) / (24 * 60 * 60 * 1000) ); + const { t } = useTranslation(); + return ( - Battery Warranty Left + {t("home.battery-warranty-left")} - {`${yearsLeft} year${ - yearsLeft !== 1 ? "s" : "" - }, ${monthsLeft} month${ - monthsLeft !== 1 ? "s" : "" - }, ${daysLeft} day${daysLeft !== 1 ? "s" : ""}`} + {(() => { + const parts: string[] = []; + + if (yearsLeft > 0) { + parts.push(`${yearsLeft} ${t("home.year")}`); + } + + if (monthsLeft > 0) { + parts.push(`${monthsLeft} ${t("home.month")}`); + } + + if (daysLeft > 0 || parts.length === 0) { + parts.push(`${daysLeft} ${t("home.day")}`); + } + + return parts.join(", "); + })()} diff --git a/components/home/ChargingStateLabel.tsx b/components/home/ChargingStateLabel.tsx index a49cd16..4a1f150 100644 --- a/components/home/ChargingStateLabel.tsx +++ b/components/home/ChargingStateLabel.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { useTranslation } from "react-i18next"; import { View, Text, StyleSheet, ViewStyle } from "react-native"; interface BatteryStatusProps { @@ -15,19 +16,19 @@ const BatteryStatus: React.FC = ({ status, style }) => { switch (status) { case 1: return { - text: "Charging", + text: "charging", backgroundColor: "#DAF5ED", textColor: "#006C4D", }; case -1: return { - text: "Discharging", + text: "discharging", backgroundColor: "#E5EBFD", textColor: "#1249ED", }; case 0: return { - text: "Idle", + text: "idle", backgroundColor: "#D8DDE7", textColor: "#565F70", }; @@ -42,6 +43,8 @@ const BatteryStatus: React.FC = ({ status, style }) => { const config = getStatusConfig(); + const { t } = useTranslation(); + return ( = ({ status, style }) => { }, ]} > - {config.text} + {status !== null && status !== undefined + ? t(`home.${config.text}`) + : "---"} ); diff --git a/components/home/PaymentDueCard.tsx b/components/home/PaymentDueCard.tsx index 911dd8b..ee45540 100644 --- a/components/home/PaymentDueCard.tsx +++ b/components/home/PaymentDueCard.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useTransition } from "react"; import { View, Text, @@ -7,6 +7,7 @@ import { Dimensions, } from "react-native"; import { MaterialIcons } from "@expo/vector-icons"; +import { useTranslation } from "react-i18next"; interface PaymentDueCardProps { label: string; @@ -24,6 +25,7 @@ const PaymentDueCard: React.FC = ({ amount, onPress, }) => { + const { t } = useTranslation(); return ( @@ -40,7 +42,7 @@ const PaymentDueCard: React.FC = ({ onPress={onPress} activeOpacity={0.8} > - Pay Now + {t("home.pay-now")} ); diff --git a/components/service/IssueSelectorModal.tsx b/components/service/IssueSelectorModal.tsx index 4ba4d15..b51982d 100644 --- a/components/service/IssueSelectorModal.tsx +++ b/components/service/IssueSelectorModal.tsx @@ -11,18 +11,23 @@ import { import Checkbox from "expo-checkbox"; import { issueConfig } from "@/constants/config"; import CloseIcon from "@/assets/icons/close.svg"; + interface IssueSelectorModalProps { visible: boolean; onClose: () => void; onSelect: (selectedValues: string[]) => void; + initialSelectedValues?: string[]; // Previously saved selections } export default function IssueSelectorModal({ visible, onClose, onSelect, + initialSelectedValues = [], }: IssueSelectorModalProps) { - const [selectedValues, setSelectedValues] = useState([]); + const [selectedValues, setSelectedValues] = useState( + initialSelectedValues + ); const [search, setSearch] = useState(""); const toggleValue = (value: string) => { @@ -42,13 +47,37 @@ export default function IssueSelectorModal({ const clearSelection = () => setSelectedValues([]); + const hasSelection = selectedValues.length > 0; + + const handleSelect = () => { + if (hasSelection) { + onSelect(selectedValues); + onClose(); + } + }; + + const handleClose = () => { + // Reset to initial values when closing without saving + setSelectedValues(initialSelectedValues); + setSearch(""); // Also clear search + onClose(); + }; + + // Reset selectedValues when modal becomes visible with new initial values + React.useEffect(() => { + if (visible) { + setSelectedValues(initialSelectedValues); + setSearch(""); + } + }, [visible, initialSelectedValues]); + return ( {/* Header */} Select Issue - + @@ -67,8 +96,15 @@ export default function IssueSelectorModal({ {`${selectedValues.length}/23 Selected`} - - Clear + + + Clear + @@ -98,15 +134,26 @@ export default function IssueSelectorModal({ ))} - + + + Cancel + { - onSelect(selectedValues); - onClose(); - }} + style={[ + styles.doneButton, + !hasSelection && styles.doneButtonDisabled, + ]} + onPress={handleSelect} + disabled={!hasSelection} > - Done + + Select + @@ -115,6 +162,17 @@ export default function IssueSelectorModal({ } const styles = StyleSheet.create({ + buttonsContainer: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: 16, + paddingVertical: 20, + backgroundColor: "#FCFCFC", + height: 80, + borderTopWidth: 1, + borderTopColor: "#E5E9F0", + }, headerBar: { flexDirection: "row", justifyContent: "space-between", @@ -123,6 +181,49 @@ const styles = StyleSheet.create({ paddingVertical: 12, backgroundColor: "#F9F9F9", }, + cancelButton: { + flex: 1, + height: 40, + borderWidth: 1, + borderColor: "#D8DDE7", + backgroundColor: "#F3F5F8", + borderRadius: 4, + justifyContent: "center", + alignItems: "center", + marginRight: 8, + }, + doneButton: { + flex: 1, + height: 40, + borderRadius: 4, + justifyContent: "center", + alignItems: "center", + backgroundColor: "#00875F", // primary green + marginLeft: 8, + }, + doneButtonDisabled: { + backgroundColor: "#E5E9F0", // disabled gray background + }, + doneText: { + fontSize: 14, + fontWeight: "600", + color: "#FFFFFF", + }, + doneTextDisabled: { + color: "#ADB4BD", // disabled gray text + }, + cancelText: { + fontSize: 14, + fontWeight: "600", + color: "#252A34", + }, + clearText: { + fontSize: 14, + color: "#ADB4BD", + }, + clearTextDisabled: { + color: "#D8DDE7", // more muted when disabled + }, container: { flex: 1, backgroundColor: "#fff" }, header: { paddingHorizontal: 16, @@ -155,10 +256,6 @@ const styles = StyleSheet.create({ color: "#555", fontWeight: "600", }, - clearText: { - fontSize: 14, - color: "#ADB4BD", - }, scrollArea: { paddingHorizontal: 0, backgroundColor: "#FCFCFC" }, category: { fontSize: 14, @@ -179,14 +276,4 @@ const styles = StyleSheet.create({ fontSize: 12, color: "#252A34", }, - doneButton: { - backgroundColor: "#00875F", - padding: 16, - alignItems: "center", - }, - doneText: { - color: "#fff", - fontSize: 14, - fontWeight: "bold", - }, }); diff --git a/services/axiosClient.ts b/services/axiosClient.ts index 1172aa3..08783f7 100644 --- a/services/axiosClient.ts +++ b/services/axiosClient.ts @@ -11,6 +11,7 @@ const api = axios.create({ // Request interceptor to add auth token api.interceptors.request.use(async (config) => { const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN); + // const token = ""; // Debug log for request console.log("🚀 [API Request]"); @@ -47,11 +48,9 @@ api.interceptors.response.use( console.log("Status:", status); console.log("Response data:", error.response?.data); - // If token is expired or not present if (status === 401 || status === 403) { console.log("Token expired or not present"); await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN); - router.replace("/auth/login"); } return Promise.reject(error); diff --git a/services/i18n/locals/en.json b/services/i18n/locals/en.json index 226dfc8..9724401 100644 --- a/services/i18n/locals/en.json +++ b/services/i18n/locals/en.json @@ -3,7 +3,7 @@ "welcome": "Welcome to Driver Saathi", "enter-mobile-number": "Enter Mobile Number", "enter-registered-mobile-number": "Enter your registered mobile number", - "for-any-queries-contact-us": "For any queries, ontact us", + "for-any-queries-contact-us": "For any queries, contact us", "number-not-registered": "Number not registered.", "enter-otp": "Please enter OTP sent to your mobile number", "verify-otp": "Verify OTP", @@ -19,8 +19,6 @@ "my-battery": "My Battery" }, "home": { - "vehicle-name": "Yatri - NBX 600", - "vehicle-id": "DL253C3602", "battery-health": "SoH", "total-distance": "Total Distance", "payment-due": "Payment Due", @@ -35,14 +33,17 @@ "all-emi-paid": "All EMI Paid! Contact your dealer", "regular-service-due": "Regular service due in 3 days! Schedule now.", "view-vehicle-location": "View Vehicle Location", - "battery-age": "7 years, 11 month, 29 days", + "year": "year(s)", + "month": "month(s)", + "day": "day(s)", "alerts": "Alerts", - "emi-alert": "14 days left to pay the EMI!" + "emi-alert": "14 days left to pay the EMI!", + "km": "km" }, "profile": { "my-account": "My Account", "enter-name": "Enter Name", - "name": "Amar Kesari", + "name": "Name", "mobile-number": "Mobile Number", "my-vehicle": "My Vehicle", "language": "Language", @@ -58,7 +59,8 @@ "customer-support": "Customer Support", "whatsapp": "Whatsapp", "call-us": "Call Us", - "email": "Email" + "email": "Email", + "view-plan": "View Plan" }, "payment": { "last-emi-details": "Last EMI Details", @@ -93,11 +95,22 @@ "contact-dealer": "For further queries, contact your dealer!", "view-all": "View all", "emi-completed": "EMI Completed", - "plan-name": "18 Month Plan" + "plan-name": "18 Month Plan", + "advance-amount": "Advance Amount", + "pay-amount-due": "Pay Amount Due", + "failed": "Failed", + "select-amount": "Select Amount", + "enter-custom-amount": "Enter Custom Amount", + "minimum": "Minimum", + "enter-amount": "Enter Amount", + "amount-to-be-paid": "Amount to be paid", + "pay-using-upi": "Pay using UPI App", + "confirm-payment": "Confirm once your payment is completed", + "payment-done": "Payment Done" }, "service": { "schedule-maintenance": "Schedule Maintenance", - "service-type": "Service Type: Regular", + "service-type": "Service Type", "issue": "Issue", "select-issue": "Select Issue", "select-datetime": "Select Date and Time", @@ -106,11 +119,13 @@ "supported-formats": "Supported formats include JPG, JPEG and PNG.", "comments": "Comments", "submit": "Submit", - "select-issues-tba": "Select issues ------(TBA)", + "select-issues-tba": "Select issues", "clear": "Clear", - "issues-selected": "3 issue selected", + "issues-selected": "issue selected", "service-request-success": "Service request submitted successfully", - "something-went-wrong": "Something went wrong!" + "something-went-wrong": "Something went wrong!", + "select-valid-time": "Select valid", + "words": "words" }, "battery": { "battery-and-warranty": "My Battery and Warranty", diff --git a/services/i18n/locals/hi.json b/services/i18n/locals/hi.json index 0a8062e..ded0623 100644 --- a/services/i18n/locals/hi.json +++ b/services/i18n/locals/hi.json @@ -3,6 +3,7 @@ "welcome": "ड्राइवर साथी में आपका स्वागत है", "enter-mobile-number": "मोबाइल नंबर दर्ज करें", "enter-registered-mobile-number": "अपना पंजीकृत मोबाइल नंबर दर्ज करें", + "for-any-queries-contact-us": "किसी भी प्रकार की सहायता के लिए, हमसे संपर्क करें", "number-not-registered": "नंबर पंजीकृत नहीं है।", "enter-otp": "कृपया अपने मोबाइल नंबर पर भेजा गया OTP दर्ज करें।", "verify-otp": "ओटीपी वेरिफाई करें", @@ -18,8 +19,6 @@ "my-battery": "मेरी बैटरी" }, "home": { - "vehicle-name": "यात्री - NBX 600", - "vehicle-id": "DL253C3602", "battery-health": "बैटरी हेल्थ", "total-distance": "कुल दूरी", "payment-due": "भुगतान बकाया", @@ -34,14 +33,17 @@ "all-emi-paid": "सभी EMI का भुगतान किया गया! अपने डीलर से संपर्क करें", "regular-service-due": "रेगुलर सर्विस 3 दिनों में होने वाली है! अभी शिड्यूल कर लो.", "view-vehicle-location": "वाहन का स्थान देखें", - "battery-age": "7 वर्ष, 11 महीने, 29 दिन", + "year": "साल", + "month": "महीना", + "day": "दिन", "alerts": "अलर्ट", - "emi-alert": "EMI का भुगतान करने के लिए 14 दिन शेष!" + "emi-alert": "EMI का भुगतान करने के लिए 14 दिन शेष!", + "km": "कि.मी." }, "profile": { "my-account": "मेरा खाता", "enter-name": "नाम दर्ज करें", - "name": "अमर केसरी", + "name": "नाम", "mobile-number": "मोबाइल नंबर", "my-vehicle": "मेरा वाहन", "language": "भाषा", @@ -52,7 +54,7 @@ "name-changed": "नाम सफलतापूर्वक बदल दिया गया", "oem-model": "OEM - मॉडल", "chassis-number": "चेसिस नंबर", - "vehicle-id": "वाहन आईडी", + "vehicle-id": "वाहन ID", "select-language": "भाषा चुनें", "customer-support": "ग्राहक सहायता", "whatsapp": "व्हाट्सएप", @@ -65,7 +67,7 @@ "amount-paid": "भुगतान की गई राशि", "due-date": "ड्यू डेट", "payment-status": "भुगतान की स्थिति", - "pay-emi": "ईएमआई भुगतान करो", + "pay-emi": "EMI भुगतान करो", "payment-history": "पेमेंट हिस्ट्री", "no-payments": "कोई भुगतान नहीं मिला!", "pending": "पेंडिंग है", @@ -92,11 +94,23 @@ "contact-dealer": "अधिक जानकारी के लिए, अपने डीलर से संपर्क करें!", "view-all": "सभी देखें", "emi-completed": "EMI पूर्ण", - "plan-name": "18 महीने की योजना" + "plan-name": "18 महीने की योजना", + "view-plan": "प्लान देखें", + "advance-amount": "अतिरिक्त राशि", + "failed": "फेल हो गया", + "select-amount": "राशि का चयन करें", + "pay-amount-due": "बकाया राशि भुगतान करें", + "enter-custom-amount": "इच्छानुसार राशि दर्ज करें", + "minimum": "न्यूनतम", + "enter-amount": "राशि दर्ज करें", + "amount-to-be-paid": "भुगतान की जाने वाली राशि", + "pay-using-upi": "UPI ऐप का उपयोग करके भुगतान करें", + "confirm-payment": "भुगतान पूरा होने पर पुष्टि करें।", + "payment-done": "भुगतान हो गया" }, "service": { "schedule-maintenance": "शेड्यूल मेंटेनेंस", - "service-type": "सेवा प्रकार: नियमित", + "service-type": "सेवा प्रकार", "issue": "समस्या", "select-issue": "समस्या का चयन करें", "select-datetime": "दिनांक और समय का चयन करें", @@ -105,11 +119,13 @@ "supported-formats": "समर्थित प्रारूपों में JPG, JPEG और PNG शामिल हैं।", "comments": "टिप्पणियाँ", "submit": "सबमिट करें", - "select-issues-tba": "समस्याओं का चयन करें ------(TBA)", + "select-issues-tba": "समस्याओं का चयन करें", "clear": "साफ़ करें", - "issues-selected": "3 समस्याएँ चुनी गई", + "issues-selected": "समस्याएँ चुनी गई", "service-request-success": "सेवा अनुरोध सफलतापूर्वक सबमिट किया गया", - "something-went-wrong": "कुछ गलत हो गया!" + "something-went-wrong": "कुछ गलत हो गया!", + "words": "शब्द", + "select-valid-time": "सही समय चुनें" }, "battery": { "battery-and-warranty": "मेरी बैटरी और वारंटी", diff --git a/store/paymentSlice.ts b/store/paymentSlice.ts index f0859e4..608ad49 100644 --- a/store/paymentSlice.ts +++ b/store/paymentSlice.ts @@ -3,17 +3,17 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { MyPlan } from "@/app/(tabs)/payments"; interface PaymentOrder { - amount: number; // kept as number for math - id?: number; // optional if not always present + amount: number; + id?: number; expiry_date?: string; order_id: string; payment_link?: string; qr_code_url?: string; - status: string; // "confirmed", "pending", etc. + status: string; transaction_id?: string; transaction_order_id?: string; transaction_date?: string; - payment_mode?: string[]; // e.g. ["UPI"] + payment_mode?: string[]; payment_reference_id?: string; paid_by_upi_handle?: string; upi_handle: string; @@ -23,12 +23,14 @@ interface EmiState { due_amount: number | null; myPlan: MyPlan | null; paymentOrder: PaymentOrder | null; + advance_balance: number | null; } const initialState: EmiState = { due_amount: null, myPlan: null, paymentOrder: null, + advance_balance: null, }; const emiSlice = createSlice({ @@ -41,6 +43,9 @@ const emiSlice = createSlice({ setMyPlan(state, action: PayloadAction) { state.myPlan = action.payload; }, + setAdvanceBalance(state, action: PayloadAction) { + state.advance_balance = action.payload; + }, setPaymentOrder(state, action: PayloadAction) { state.paymentOrder = action.payload; }, @@ -61,6 +66,7 @@ export const { setPaymentOrder, clearPaymentOrder, updatePaymentStatus, + setAdvanceBalance, } = emiSlice.actions; export default emiSlice.reducer;