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;