Add Lang config

feature/app-setup
vinay kumar 2025-08-18 20:20:32 +05:30
parent 911f2eb4f4
commit 51a2a2272e
24 changed files with 606 additions and 292 deletions

1
App.js
View File

@ -1 +0,0 @@
import "expo-router/entry";

View File

@ -38,6 +38,7 @@ import {
import api from "@/services/axiosClient"; import api from "@/services/axiosClient";
import { setDueAmount } from "@/store/paymentSlice"; import { setDueAmount } from "@/store/paymentSlice";
import { EmiResponse } from "./payments"; import { EmiResponse } from "./payments";
import { Image } from "expo-image";
export default function HomeScreen() { export default function HomeScreen() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -137,21 +138,23 @@ export default function HomeScreen() {
</Pressable> </Pressable>
<Pressable <Pressable
style={styles.supportButton} style={styles.supportButton}
onPress={() => setIsSupportModalVisible(true)} onPress={() => {
setIsSupportModalVisible(true);
console.log("hkdlfjaldf");
}}
> >
<CustomerCareIcon width={50} height={50} /> <CustomerCareIcon width={50} height={50} />
</Pressable> </Pressable>
<TouchableOpacity <View>
onPress={() => {
router.push("/user/profile");
}}
>
<ProfileImage <ProfileImage
username={data?.name || "Vec"} username={data?.name || "Vec"}
textSize={20} textSize={20}
boxSize={40} boxSize={40}
onClick={() => {
router.push("/user/profile");
}}
/> />
</TouchableOpacity> </View>
</View> </View>
), ),
}); });
@ -229,7 +232,7 @@ export default function HomeScreen() {
: "danger" : "danger"
} }
message={daysLeftToPayEmiText} message={daysLeftToPayEmiText}
subMessage="Pay now" subMessage={t("home.pay-now")}
redirectPath="/(tabs)/payments" redirectPath="/(tabs)/payments"
/> />
)} )}
@ -251,12 +254,12 @@ export default function HomeScreen() {
<MetricCard <MetricCard
heading={t("home.total-distance")} heading={t("home.total-distance")}
value={totalDistance} value={totalDistance}
unit="km" unit={t("home.km")}
/> />
</View> </View>
{due_amount && ( {due_amount && (
<PaymentDueCard <PaymentDueCard
label="Payment Due" label={t("home.payment-due")}
amount={due_amount} amount={due_amount}
onPress={() => { onPress={() => {
router.push("/payments/selectAmount"); router.push("/payments/selectAmount");
@ -269,7 +272,7 @@ export default function HomeScreen() {
<LocationOff /> <LocationOff />
<Text style={styles.errorText}>Fetching Location...</Text> <Text style={styles.errorText}>Fetching Location...</Text>
</View> </View>
) : lat != null && lon != null ? ( ) : lat != null && lon != null && !(lat == 0 && lon == 0) ? (
<> <>
<View style={styles.mapContainer}> <View style={styles.mapContainer}>
<MapView <MapView
@ -293,8 +296,12 @@ export default function HomeScreen() {
rotation={bearing} rotation={bearing}
anchor={{ x: 0.5, y: 0.5 }} anchor={{ x: 0.5, y: 0.5 }}
tracksViewChanges={false} tracksViewChanges={false}
image={require("../../assets/images/marker.png")} >
/> <Image
source={require("../../assets/images/marker.png")}
style={{ height: 35, width: 35 }}
/>
</Marker>
</MapView> </MapView>
</View> </View>
<TouchableOpacity> <TouchableOpacity>
@ -302,24 +309,24 @@ export default function HomeScreen() {
style={styles.viewLocationText} style={styles.viewLocationText}
onPress={openInGoogleMaps} onPress={openInGoogleMaps}
> >
View Vehicle Location {t("home.view-vehicle-location")}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</> </>
) : ( ) : (
error && ( <View style={styles.errorContainer}>
<View style={styles.errorContainer}> <LocationOff />
<LocationOff /> <Text style={styles.errorText}>{t("home.error-location")}</Text>
<Text style={styles.errorText}>Error fetching location</Text> </View>
</View>
)
)} )}
</View> </View>
{warrantyEndDate && warrantyStartDate && ( {warrantyEndDate && warrantyStartDate && (
<BatteryWarrantyCard <TouchableOpacity onPress={() => router.push("/(tabs)/my-battery")}>
warrantyStartDate={warrantyStartDate} <BatteryWarrantyCard
warrantyEndDate={warrantyEndDate} warrantyStartDate={warrantyStartDate}
/> warrantyEndDate={warrantyEndDate}
/>
</TouchableOpacity>
)} )}
</ScrollView> </ScrollView>
<CustomerSupportModal <CustomerSupportModal

View File

@ -1,10 +1,11 @@
import { RootState } from "@/store"; import { RootState } from "@/store";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next";
import { View, Text, StyleSheet, ScrollView } from "react-native"; import { View, Text, StyleSheet, ScrollView } from "react-native";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
const BatteryDetails = () => { const BatteryDetails = () => {
const { data } = useSelector((state: RootState) => state.user); const { data } = useSelector((state: RootState) => state.user);
const { t } = useTranslation();
const model = data?.batteries[0]?.battery_model ?? "---"; const model = data?.batteries[0]?.battery_model ?? "---";
const batteryId = data?.batteries[0]?.battery_id ?? "---"; const batteryId = data?.batteries[0]?.battery_id ?? "---";
const bmsId = data?.batteries[0]?.bms_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) (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 => const formatDate = (date?: Date | null): string =>
@ -70,29 +83,37 @@ const BatteryDetails = () => {
return ( return (
<ScrollView contentContainerStyle={styles.container}> <ScrollView contentContainerStyle={styles.container}>
{/* Battery Card */} {/* Battery Section */}
<View style={styles.card}> <View style={styles.card}>
<View style={styles.cardHeader}> <View style={styles.cardHeader}>
<Text style={styles.cardTitle}>Battery</Text> <Text style={styles.cardTitle}>{t("battery.battery")}</Text>
{isInWarranty && ( {isInWarranty ? (
<View style={styles.badge}> <View style={styles.badge}>
<Text style={styles.badgeText}>In Warranty</Text> <Text style={styles.badgeText}>{t("battery.in-warranty")}</Text>
</View>
) : (
<View style={styles.expiredBadge}>
<Text style={styles.expiredBadgeText}>
{t("battery.warranty-expired")}
</Text>
</View> </View>
)} )}
</View> </View>
<View style={styles.divider} /> <View style={styles.divider} />
<InfoRow label="Model" value={model} /> <InfoRow label={t("battery.model")} value={model} />
<InfoRow label="Battery ID" value={batteryId} /> <InfoRow label={t("battery.battery-id")} value={batteryId} />
<InfoRow label="BMS ID" value={bmsId} /> <InfoRow label={t("battery.bms-id")} value={bmsId} />
</View> </View>
{/* Warranty Details */} {/* Battery Warranty Details Section */}
<View style={styles.card}> <View style={styles.card}>
<Text style={styles.cardTitle}>Battery Warranty Details</Text> <Text style={styles.cardTitle}>
{t("battery.battery-warranty-details")}
</Text>
<View style={styles.divider} /> <View style={styles.divider} />
<InfoRow label="Start Date" value={formatDate(start)} /> <InfoRow label={t("battery.start-date")} value={formatDate(start)} />
<InfoRow label="End Date" value={formatDate(end)} /> <InfoRow label={t("battery.end-date")} value={formatDate(end)} />
<InfoRow label="Duration Left" value={durationText} /> <InfoRow label={t("battery.duration-left")} value={durationText} />
{start && end && !isNaN(start.getTime()) && !isNaN(end.getTime()) && ( {start && end && !isNaN(start.getTime()) && !isNaN(end.getTime()) && (
<View style={styles.progressBarBackground}> <View style={styles.progressBarBackground}>
<View <View
@ -105,19 +126,19 @@ const BatteryDetails = () => {
)} )}
</View> </View>
{/* VIM Details */} {/* VIM Details Section */}
<View style={styles.card}> <View style={styles.card}>
<Text style={styles.cardTitle}>VIM Details</Text> <Text style={styles.cardTitle}>{t("battery.vim-details")}</Text>
<View style={styles.divider} /> <View style={styles.divider} />
<InfoRow label="VIM ID" value={vimId} /> <InfoRow label={t("battery.vim-id")} value={vimId} />
<InfoRow label="Serial Number" value={serialNumber} /> <InfoRow label={t("battery.serial-number")} value={serialNumber} />
</View> </View>
{/* Charger Details */} {/* Charger Details Section */}
<View style={styles.card}> <View style={styles.card}>
<Text style={styles.cardTitle}>Charger Details</Text> <Text style={styles.cardTitle}>{t("battery.charger-details")}</Text>
<View style={styles.divider} /> <View style={styles.divider} />
<InfoRow label="UID" value={chargerUid} /> <InfoRow label={t("battery.uid")} value={chargerUid} />
</View> </View>
</ScrollView> </ScrollView>
); );
@ -138,9 +159,14 @@ const InfoRow: React.FC<InfoRowProps> = ({ label, value }) => (
export default BatteryDetails; export default BatteryDetails;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
expiredBadgeText: {
fontSize: 12,
fontWeight: "500",
color: "#D51D10",
},
container: { container: {
backgroundColor: "#F3F5F8", backgroundColor: "#F3F5F8",
padding: 16, paddingHorizontal: 16,
}, },
card: { card: {
backgroundColor: "#FCFCFC", backgroundColor: "#FCFCFC",
@ -158,6 +184,12 @@ const styles = StyleSheet.create({
fontWeight: "600", fontWeight: "600",
color: "#252A34", color: "#252A34",
}, },
expiredBadge: {
backgroundColor: "#FDE9E7",
paddingVertical: 2,
paddingHorizontal: 8,
borderRadius: 4,
},
badge: { badge: {
backgroundColor: "#DAF5ED", backgroundColor: "#DAF5ED",
paddingVertical: 2, paddingVertical: 2,

View File

@ -27,12 +27,17 @@ import api from "@/services/axiosClient";
import PaymentHistoryCard from "@/components/Payments/PaymentHistoryCard"; import PaymentHistoryCard from "@/components/Payments/PaymentHistoryCard";
import { BASE_URL } from "@/constants/config"; import { BASE_URL } from "@/constants/config";
import { useDispatch } from "react-redux"; 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 { ActivityIndicator } from "react-native-paper";
import { useFocusEffect } from "@react-navigation/native"; import { useFocusEffect } from "@react-navigation/native";
import { displayValue } from "@/utils/Common"; import { displayValue } from "@/utils/Common";
import RefreshIcon from "@/assets/icons/refresh.svg"; import RefreshIcon from "@/assets/icons/refresh.svg";
import CustomerSupport from "@/components/home/CustomerSupportModal"; import CustomerSupport from "@/components/home/CustomerSupportModal";
import { useTranslation } from "react-i18next";
export interface MyPlan { export interface MyPlan {
no_of_emi: number; no_of_emi: number;
@ -145,6 +150,7 @@ export default function PaymentsTabScreen() {
try { try {
setIsLoading(true); setIsLoading(true);
setEmiDetails(null); setEmiDetails(null);
setAdvanceBalance(null);
const response = await api.get(`/api/v1/emi-details`); const response = await api.get(`/api/v1/emi-details`);
const result: EmiResponse = response.data; const result: EmiResponse = response.data;
@ -156,6 +162,7 @@ export default function PaymentsTabScreen() {
dispatch(setDueAmount(details.due_amount)); dispatch(setDueAmount(details.due_amount));
dispatch(setMyPlan(details.myPlain)); dispatch(setMyPlan(details.myPlain));
dispatch(setAdvanceBalance(details.advance_balance));
} else { } else {
showSnackbar("No EMI details found", "error"); showSnackbar("No EMI details found", "error");
} }
@ -246,18 +253,14 @@ export default function PaymentsTabScreen() {
> >
<CustomerCareIcon height={50} width={50} /> <CustomerCareIcon height={50} width={50} />
</Pressable> </Pressable>
<Pressable <View>
onPress={() => {
router.push("/user/profile");
}}
>
<ProfileImage <ProfileImage
username={data?.name || "User"} username={data?.name || "User"}
onClick={() => router.push("/user/profile")} onClick={() => router.push("/user/profile")}
textSize={20} textSize={20}
boxSize={40} boxSize={40}
/> />
</Pressable> </View>
</View> </View>
), ),
}); });
@ -367,6 +370,7 @@ export default function PaymentsTabScreen() {
setIsEndReached(isAtBottom); setIsEndReached(isAtBottom);
}; };
const { t } = useTranslation();
return ( return (
<> <>
<ScrollView <ScrollView
@ -379,7 +383,9 @@ export default function PaymentsTabScreen() {
<View style={styles.emiCard}> <View style={styles.emiCard}>
{/* Header */} {/* Header */}
<View style={styles.cardHeader}> <View style={styles.cardHeader}>
<Text style={styles.headerTitle}>Last EMI Details</Text> <Text style={styles.headerTitle}>
{t("payment.last-emi-details")}
</Text>
<Text style={styles.headerDate}>{getCurrentMonthYear()}</Text> <Text style={styles.headerDate}>{getCurrentMonthYear()}</Text>
</View> </View>
@ -389,14 +395,14 @@ export default function PaymentsTabScreen() {
{/* EMI Details Content */} {/* EMI Details Content */}
<View style={styles.cardContent}> <View style={styles.cardContent}>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Amount Due</Text> <Text style={styles.detailLabel}>{t("payment.amount-due")}</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{displayValue(emiDetails?.due_amount, formatCurrency)} {displayValue(emiDetails?.due_amount, formatCurrency)}
</Text> </Text>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Amount Paid</Text> <Text style={styles.detailLabel}>{t("payment.amount-paid")}</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{displayValue( {displayValue(
emiDetails?.total_amount_paid_in_current_cycle, emiDetails?.total_amount_paid_in_current_cycle,
@ -406,14 +412,16 @@ export default function PaymentsTabScreen() {
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Due Date</Text> <Text style={styles.detailLabel}>{t("payment.due-date")}</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{displayValue(emiDetails?.due_date)} {displayValue(emiDetails?.due_date)}
</Text> </Text>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Payment Status</Text> <Text style={styles.detailLabel}>
{t("payment.payment-status")}
</Text>
{emiDetails?.status ? ( {emiDetails?.status ? (
<StatusBadge <StatusBadge
label={emiDetails.status} label={emiDetails.status}
@ -436,7 +444,9 @@ export default function PaymentsTabScreen() {
onPress={() => router.push("/payments/selectAmount")} onPress={() => router.push("/payments/selectAmount")}
disabled={isLoading || !emiDetails} disabled={isLoading || !emiDetails}
> >
<Text style={styles.primaryButtonText}>Pay EMI</Text> <Text style={styles.primaryButtonText}>
{t("payment.pay-emi")}
</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
@ -447,13 +457,17 @@ export default function PaymentsTabScreen() {
onPress={() => router.push("/payments/myPlan")} onPress={() => router.push("/payments/myPlan")}
disabled={isLoading || !emiDetails} disabled={isLoading || !emiDetails}
> >
<Text style={styles.tertiaryButtonText}>View Plan</Text> <Text style={styles.tertiaryButtonText}>
{t("payment.view-plan")}
</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
<View> <View>
<Text style={styles.sectionTitle}>Payment History</Text> <Text style={styles.sectionTitle}>
{t("payment.payment-history")}
</Text>
<View style={styles.paymentHistoryContainer}> <View style={styles.paymentHistoryContainer}>
{isHistoryLoading ? ( {isHistoryLoading ? (
@ -483,7 +497,9 @@ export default function PaymentsTabScreen() {
style={styles.viewAllButton} style={styles.viewAllButton}
onPress={handleViewAll} onPress={handleViewAll}
> >
<Text style={styles.viewAllText}>View all</Text> <Text style={styles.viewAllText}>
{t("payment.view-all")}
</Text>
<Text style={styles.chevron}></Text> <Text style={styles.chevron}></Text>
</TouchableOpacity> </TouchableOpacity>
)} )}

View File

@ -25,6 +25,7 @@ import { useSnackbar } from "@/contexts/Snackbar";
import { BASE_URL } from "@/constants/config"; import { BASE_URL } from "@/constants/config";
import { Overlay } from "@/components/common/Overlay"; import { Overlay } from "@/components/common/Overlay";
import CrossIcon from "@/assets/icons/close_white.svg"; import CrossIcon from "@/assets/icons/close_white.svg";
import { useTranslation } from "react-i18next";
interface FormValues { interface FormValues {
serviceType: string | null; serviceType: string | null;
@ -66,7 +67,7 @@ export default function ServiceFormScreen(): JSX.Element {
let result = await ImagePicker.launchImageLibraryAsync({ let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images, mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true, allowsEditing: true,
quality: 1, quality: 0.5,
allowsMultipleSelection: true, allowsMultipleSelection: true,
}); });
@ -89,6 +90,7 @@ export default function ServiceFormScreen(): JSX.Element {
mode: "date", mode: "date",
is24Hour: false, is24Hour: false,
display: "default", display: "default",
minimumDate: now,
onChange: (event, selectedDate) => { onChange: (event, selectedDate) => {
if (event.type === "set" && selectedDate) { if (event.type === "set" && selectedDate) {
// When date is selected, show time picker next // When date is selected, show time picker next
@ -107,6 +109,11 @@ export default function ServiceFormScreen(): JSX.Element {
selectedTime.getHours(), selectedTime.getHours(),
selectedTime.getMinutes() selectedTime.getMinutes()
); );
if (combinedDate < now) {
showSnackbar(`${t("service.select-valid-time")}`, "error");
return;
}
setFieldValue("date", combinedDate); setFieldValue("date", combinedDate);
} }
}, },
@ -116,6 +123,8 @@ export default function ServiceFormScreen(): JSX.Element {
}); });
}; };
const { t } = useTranslation();
return ( return (
<KeyboardAvoidingView <KeyboardAvoidingView
style={styles.container} style={styles.container}
@ -142,6 +151,8 @@ export default function ServiceFormScreen(): JSX.Element {
uploadedPhotoUrls.push(uploadedUrl); uploadedPhotoUrls.push(uploadedUrl);
} }
console.log("IMAGES UPLOADED");
const payload = { const payload = {
service_type: values.serviceType, service_type: values.serviceType,
issue_types: values.issues, issue_types: values.issues,
@ -156,16 +167,23 @@ export default function ServiceFormScreen(): JSX.Element {
); );
if (!response.data.success) { if (!response.data.success) {
console.log(response.data?.message || "Submission failed");
throw new Error(response.data?.message || "Submission failed"); throw new Error(response.data?.message || "Submission failed");
} }
console.log("Submission successful:", response.data); console.log("Submission successful:", response.data);
actions.resetForm(); actions.resetForm();
showSnackbar("Service request submitted successfully", "success"); showSnackbar(
} catch (error) { `${t("service.service-request-success")}`,
"success"
);
} catch (error: any) {
console.error("Error during submission:", error); console.error("Error during submission:", error);
showSnackbar("Failed to submit service request", "error"); showSnackbar(
error.message || `${t("service.something-went-wrong")}`,
"error"
);
} finally { } finally {
actions.setSubmitting(false); actions.setSubmitting(false);
} }
@ -184,7 +202,8 @@ export default function ServiceFormScreen(): JSX.Element {
<View style={styles.formContainer}> <View style={styles.formContainer}>
<View style={styles.inputContainer}> <View style={styles.inputContainer}>
<Text style={styles.label}> <Text style={styles.label}>
Service Type <Text style={styles.required}>*</Text> {t("service.service-type")}{" "}
<Text style={styles.required}>*</Text>
</Text> </Text>
<Dropdown <Dropdown
style={[ style={[
@ -202,7 +221,7 @@ export default function ServiceFormScreen(): JSX.Element {
maxHeight={200} maxHeight={200}
labelField="label" labelField="label"
valueField="value" valueField="value"
placeholder={"-Select-"} placeholder={`${t("service.select")}`}
value={values.serviceType} value={values.serviceType}
onFocus={() => setIsFocus(true)} onFocus={() => setIsFocus(true)}
onBlur={() => setIsFocus(false)} onBlur={() => setIsFocus(false)}
@ -222,7 +241,7 @@ export default function ServiceFormScreen(): JSX.Element {
</View> </View>
<View style={{ marginTop: 8 }}> <View style={{ marginTop: 8 }}>
<Text style={styles.label}> <Text style={styles.label}>
Issues <Text style={styles.required}>*</Text> {t("service.issue")} <Text style={styles.required}>*</Text>
</Text> </Text>
<TouchableOpacity <TouchableOpacity
style={styles.inputBox} style={styles.inputBox}
@ -231,7 +250,7 @@ export default function ServiceFormScreen(): JSX.Element {
<Text style={styles.issueText}> <Text style={styles.issueText}>
{values.issues.length > 0 {values.issues.length > 0
? values.issues.length + " issues selected" ? values.issues.length + " issues selected"
: "Select Issue"} : `${t("service.select-issue")}`}
</Text> </Text>
<ChevronRight /> <ChevronRight />
</TouchableOpacity> </TouchableOpacity>
@ -242,7 +261,8 @@ export default function ServiceFormScreen(): JSX.Element {
<View style={{ marginTop: 8 }}> <View style={{ marginTop: 8 }}>
<Text style={styles.label}> <Text style={styles.label}>
Select Date and Time <Text style={styles.required}>*</Text> {t("service.select-datetime")}{" "}
<Text style={styles.required}>*</Text>
</Text> </Text>
<TouchableOpacity <TouchableOpacity
onPress={() => showPicker(values.date, setFieldValue)} onPress={() => showPicker(values.date, setFieldValue)}
@ -261,10 +281,12 @@ export default function ServiceFormScreen(): JSX.Element {
onPress={() => handlePhotoPick(setFieldValue, values.photos)} onPress={() => handlePhotoPick(setFieldValue, values.photos)}
> >
<AddPhoto /> <AddPhoto />
<Text style={styles.addPhotoText}>Add photos</Text> <Text style={styles.addPhotoText}>
{t("service.add-photos")}{" "}
</Text>
</TouchableOpacity> </TouchableOpacity>
<Text style={styles.helperText}> <Text style={styles.helperText}>
Supported formats include JPG, JPEG and PNG. {t("service.supported-formats")}
</Text> </Text>
{/* Selected Images Preview */} {/* Selected Images Preview */}
@ -290,7 +312,7 @@ export default function ServiceFormScreen(): JSX.Element {
<Text style={styles.error}>{errors.photos}</Text> <Text style={styles.error}>{errors.photos}</Text>
)} )}
<View style={{ marginTop: 16 }}> <View style={{ marginTop: 16 }}>
<Text style={styles.label}>Comments</Text> <Text style={styles.label}>{t("service.comments")} </Text>
<TextInput <TextInput
style={styles.commentInput} style={styles.commentInput}
multiline multiline
@ -300,7 +322,7 @@ export default function ServiceFormScreen(): JSX.Element {
value={values.comments} value={values.comments}
/> />
<Text style={styles.wordCount}> <Text style={styles.wordCount}>
{values.comments?.length || 0}/100 words {values.comments?.length || 0}/100 {t("service.words")}
</Text> </Text>
</View> </View>
@ -308,7 +330,7 @@ export default function ServiceFormScreen(): JSX.Element {
style={styles.submitButton} style={styles.submitButton}
onPress={handleSubmit as (e?: GestureResponderEvent) => void} onPress={handleSubmit as (e?: GestureResponderEvent) => void}
> >
<Text style={styles.submitText}>Submit</Text> <Text style={styles.submitText}>{t("service.submit")} </Text>
</TouchableOpacity> </TouchableOpacity>
<IssueSelectorModal <IssueSelectorModal
@ -317,6 +339,7 @@ export default function ServiceFormScreen(): JSX.Element {
onSelect={(selectedIssues) => { onSelect={(selectedIssues) => {
setFieldValue("issues", selectedIssues); setFieldValue("issues", selectedIssues);
}} }}
initialSelectedValues={values.issues}
/> />
{isSubmitting && <Overlay isUploading={isSubmitting} />} {isSubmitting && <Overlay isUploading={isSubmitting} />}
</View> </View>

View File

@ -3,23 +3,22 @@ import ProgressCard from "@/components/common/ProgressCard";
import { RootState } from "@/store/rootReducer"; import { RootState } from "@/store/rootReducer";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import React from "react"; 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"; 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 MyPlanScreen: React.FC = () => {
const myPlan = useSelector((state: RootState) => state.payments.myPlan); const myPlan = useSelector((state: RootState) => state.payments.myPlan);
const advance_balance = useSelector(
(state: RootState) => state.payments.advance_balance
);
const dueAmount = useSelector( const dueAmount = useSelector(
(state: RootState) => state.payments.due_amount (state: RootState) => state.payments.due_amount
); );
@ -46,21 +45,21 @@ const MyPlanScreen: React.FC = () => {
if (!firstValue || !secondValue) return 0; if (!firstValue || !secondValue) return 0;
return (firstValue / secondValue) * 100; return (firstValue / secondValue) * 100;
}; };
const { t } = useTranslation();
return ( return (
<View style={styles.container}> <ScrollView style={styles.container}>
<Header title="My Plan" showBackButton={true} /> <Header title={t("payment.my-plan")} showBackButton={true} />
<View style={styles.contentFrame}> <View style={styles.contentFrame}>
{/* Plan Details Card */} {/* Plan Details Card */}
<View style={styles.card}> <View style={styles.card}>
<Text style={styles.cardTitle}>Plan Details</Text> <Text style={styles.cardTitle}>{t("payment.plan-details")}</Text>
<View style={styles.divider} /> <View style={styles.divider} />
<View style={styles.content}> <View style={styles.content}>
{/* Plan Type Row */} {/* Plan Type Row */}
<View style={styles.row}> <View style={styles.row}>
<Text style={styles.label}>Plan Type</Text> <Text style={styles.label}>{t("payment.plan-type")}</Text>
<Text style={styles.value}> <Text style={styles.value}>
{displayValue(myPlan?.no_of_emi)} {displayValue(myPlan?.no_of_emi)}
</Text> </Text>
@ -68,7 +67,7 @@ const MyPlanScreen: React.FC = () => {
{/* Total Cost Row */} {/* Total Cost Row */}
<View style={styles.row}> <View style={styles.row}>
<Text style={styles.label}>Total Cost</Text> <Text style={styles.label}>{t("payment.total-cost")}</Text>
<Text style={styles.value}> <Text style={styles.value}>
{formatCurrency(myPlan?.total_amount)} {formatCurrency(myPlan?.total_amount)}
</Text> </Text>
@ -76,7 +75,7 @@ const MyPlanScreen: React.FC = () => {
{/* Down Payment Row */} {/* Down Payment Row */}
<View style={styles.row}> <View style={styles.row}>
<Text style={styles.label}>Down Payment</Text> <Text style={styles.label}>{t("payment.down-payment")}</Text>
<Text style={styles.value}> <Text style={styles.value}>
{formatCurrency(myPlan?.down_payment)} {formatCurrency(myPlan?.down_payment)}
</Text> </Text>
@ -84,7 +83,7 @@ const MyPlanScreen: React.FC = () => {
{/* Total EMI Row */} {/* Total EMI Row */}
<View style={styles.row}> <View style={styles.row}>
<Text style={styles.label}>Total EMI</Text> <Text style={styles.label}>{t("payment.total-emi")}</Text>
<Text style={styles.value}> <Text style={styles.value}>
{formatCurrency(myPlan?.total_emi)} {formatCurrency(myPlan?.total_emi)}
</Text> </Text>
@ -94,7 +93,9 @@ const MyPlanScreen: React.FC = () => {
{/* Total Amount Due Card */} {/* Total Amount Due Card */}
<View style={styles.card}> <View style={styles.card}>
<Text style={styles.centerLabel}>Total Amount Due</Text> <Text style={styles.centerLabel}>
{t("payment.total-amount-due")}
</Text>
<View style={styles.amountContainer}> <View style={styles.amountContainer}>
<Text style={styles.dueAmount}>{formatCurrency(dueAmount)}</Text> <Text style={styles.dueAmount}>{formatCurrency(dueAmount)}</Text>
</View> </View>
@ -103,14 +104,14 @@ const MyPlanScreen: React.FC = () => {
<View style={styles.twoColumnContainer}> <View style={styles.twoColumnContainer}>
{/* Monthly EMI Card */} {/* Monthly EMI Card */}
<View style={styles.halfCard}> <View style={styles.halfCard}>
<Text style={styles.label}>Monthly EMI</Text> <Text style={styles.label}>{t("payment.monthly-emi")}</Text>
<Text style={styles.value}> <Text style={styles.value}>
{formatCurrency(myPlan?.emi_amount)} {formatCurrency(myPlan?.emi_amount)}
</Text> </Text>
</View> </View>
<View style={styles.halfCard}> <View style={styles.halfCard}>
<Text style={styles.label}>Installments Paid</Text> <Text style={styles.label}>{t("payment.installments-paid")}</Text>
<View style={styles.installmentRow}> <View style={styles.installmentRow}>
<Text style={styles.value}> <Text style={styles.value}>
{displayValue(myPlan?.installment_paid)} {displayValue(myPlan?.installment_paid)}
@ -123,7 +124,7 @@ const MyPlanScreen: React.FC = () => {
</View> </View>
<ProgressCard <ProgressCard
title="EMI Paid Till Now" title={t("payment.emi-paid-till-now")}
firstText={formatCurrency(myPlan?.current_amount)} firstText={formatCurrency(myPlan?.current_amount)}
secondText={formatCurrency(myPlan?.total_emi)} secondText={formatCurrency(myPlan?.total_emi)}
percentage={getProgressPercentage( percentage={getProgressPercentage(
@ -132,18 +133,30 @@ const MyPlanScreen: React.FC = () => {
)} )}
/> />
<View style={styles.card}>
<Text style={styles.label}>{t("payment.advance-amount")}</Text>
<Text style={styles.amount}>{formatCurrency(advance_balance)}</Text>
</View>
<TouchableOpacity <TouchableOpacity
style={styles.payButton} style={styles.payButton}
onPress={() => router.push("/payments/selectAmount")} onPress={() => router.push("/payments/selectAmount")}
> >
<Text style={styles.payButtonText}>Pay EMI</Text> <Text style={styles.payButtonText}>{t("payment.pay-emi")}</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </ScrollView>
); );
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
amount: {
fontSize: 14,
fontWeight: "600",
color: "#252A34",
height: 20,
lineHeight: 20,
},
container: { container: {
flex: 1, flex: 1,
backgroundColor: "#F3F5F8", backgroundColor: "#F3F5F8",

View File

@ -9,6 +9,7 @@ import {
Share, Share,
Linking, Linking,
BackHandler, BackHandler,
ScrollView,
} from "react-native"; } from "react-native";
import * as FileSystem from "expo-file-system"; import * as FileSystem from "expo-file-system";
import * as MediaLibrary from "expo-media-library"; import * as MediaLibrary from "expo-media-library";
@ -26,6 +27,7 @@ import DownloadIcon from "@/assets/icons/download.svg";
import { payments } from "@/constants/config"; import { payments } from "@/constants/config";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import { useSocket } from "@/contexts/SocketContext"; import { useSocket } from "@/contexts/SocketContext";
import { useTranslation } from "react-i18next";
const UpiPaymentScreen = () => { const UpiPaymentScreen = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -191,14 +193,18 @@ const UpiPaymentScreen = () => {
router.push("/(tabs)/payments"); router.push("/(tabs)/payments");
} }
const { t } = useTranslation();
return ( return (
<SafeAreaView style={styles.container}> <ScrollView style={styles.container}>
<Header title="Pay EMI" showBackButton={false} /> <Header title="Pay EMI" showBackButton={false} />
<View style={styles.content}> <View style={styles.content}>
<View style={styles.qrFrame}> <View style={styles.qrFrame}>
<View style={styles.amountSection}> <View style={styles.amountSection}>
<Text style={styles.amountLabel}>Amount to be paid</Text> <Text style={styles.amountLabel}>
{t("payment.amount-to-be-paid")}
</Text>
<Text style={styles.amount}> <Text style={styles.amount}>
{formatAmount(paymentOrder?.amount)} {formatAmount(paymentOrder?.amount)}
</Text> </Text>
@ -215,7 +221,9 @@ const UpiPaymentScreen = () => {
<View style={styles.buttonsContainer}> <View style={styles.buttonsContainer}>
<TouchableOpacity onPress={shareQR} style={styles.secondaryButton}> <TouchableOpacity onPress={shareQR} style={styles.secondaryButton}>
<ShareIcon /> <ShareIcon />
<Text style={styles.secondaryButtonText}>Share QR</Text> <Text style={styles.secondaryButtonText}>
{t("payment.share-qr")}
</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
@ -223,29 +231,33 @@ const UpiPaymentScreen = () => {
style={styles.secondaryButton} style={styles.secondaryButton}
> >
<DownloadIcon /> <DownloadIcon />
<Text style={styles.secondaryButtonText}>Download QR</Text> <Text style={styles.secondaryButtonText}>
{t("payment.download-qr")}
</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<TouchableOpacity <TouchableOpacity
onPress={payUsingUpiApp} onPress={payUsingUpiApp}
style={[styles.primaryButton]} style={[styles.primaryButton]}
> >
<Text style={[styles.primaryButtonText]}>Pay using UPI app</Text> <Text style={[styles.primaryButtonText]}>
{t("payment.pay-using-upi")}
</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={[styles.confirm, { marginBottom: insets.bottom }]}> <View style={[styles.confirm, { marginBottom: insets.bottom }]}>
<Text style={styles.confirmTitle}> <Text style={styles.confirmTitle}>
Confirm once your payment is completed. {t("payment.confirm-payment")}
</Text> </Text>
<TouchableOpacity <TouchableOpacity
onPress={() => handlePaymentDone()} onPress={() => handlePaymentDone()}
style={styles.paymentDone} style={styles.paymentDone}
> >
<Text style={styles.doneText}>Payment Done</Text> <Text style={styles.doneText}>{t("payment.payment-done")}</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
</SafeAreaView> </ScrollView>
); );
}; };

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { import {
View, View,
Text, Text,
@ -6,9 +6,8 @@ import {
TouchableOpacity, TouchableOpacity,
TextInput, TextInput,
ScrollView, ScrollView,
Platform,
KeyboardAvoidingView, KeyboardAvoidingView,
Alert, Keyboard,
} from "react-native"; } from "react-native";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Formik } from "formik"; import { Formik } from "formik";
@ -22,6 +21,8 @@ import { Overlay } from "@/components/common/Overlay";
import { useFocusEffect, useRouter } from "expo-router"; import { useFocusEffect, useRouter } from "expo-router";
import { useSocket } from "@/contexts/SocketContext"; import { useSocket } from "@/contexts/SocketContext";
import { useSnackbar } from "@/contexts/Snackbar"; import { useSnackbar } from "@/contexts/Snackbar";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTranslation } from "react-i18next";
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
paymentType: Yup.string().required("Please select a payment option"), paymentType: Yup.string().required("Please select a payment option"),
@ -71,6 +72,24 @@ const SelectAmountScreen = () => {
const dispatch = useDispatch(); 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( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
console.log( console.log(
@ -153,6 +172,7 @@ const SelectAmountScreen = () => {
handleSubmit, handleSubmit,
setFieldValue, setFieldValue,
isValid, isValid,
validateField,
dirty, dirty,
}) => { }) => {
const handleQuickAmountPress = (amount: number) => { const handleQuickAmountPress = (amount: number) => {
@ -161,6 +181,20 @@ const SelectAmountScreen = () => {
: 0; : 0;
const newAmount = currentAmount + amount; const newAmount = currentAmount + amount;
setFieldValue("customAmount", newAmount.toString()); 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 = () => { const getPaymentAmount = () => {
@ -170,30 +204,33 @@ const SelectAmountScreen = () => {
return values.customAmount ? parseFloat(values.customAmount) : 0; return values.customAmount ? parseFloat(values.customAmount) : 0;
}; };
// Improved button state logic const paymentAmount = getPaymentAmount();
const isPayButtonEnabled = () => {
if (values.paymentType === "due") return true;
// For custom amount, check if it's valid and meets minimum requirement const isButtonEnabled =
if (values.paymentType === "custom") { values.paymentType === "due" ||
const amount = parseFloat(values.customAmount); (values.paymentType === "custom" &&
return ( !isNaN(paymentAmount) &&
values.customAmount && paymentAmount >= payments.MIN_AMOUNT);
!isNaN(amount) &&
amount >= payments.MIN_AMOUNT &&
!errors.customAmount
);
}
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 ( return (
<KeyboardAvoidingView <KeyboardAvoidingView
style={styles.container} style={styles.container}
behavior={Platform.OS === "ios" ? "padding" : "height"} behavior={"height"}
// Improved keyboard offset // Improved keyboard offset
keyboardVerticalOffset={Platform.OS === "ios" ? 88 : 0} // keyboardVerticalOffset={Platform.OS === "ios" ? 88 : 0}
> >
<Header title="Select Amount" showBackButton={true} /> <Header title="Select Amount" showBackButton={true} />
@ -224,7 +261,9 @@ const SelectAmountScreen = () => {
<View style={styles.radioInner} /> <View style={styles.radioInner} />
)} )}
</View> </View>
<Text style={styles.radioLabel}>Pay amount due</Text> <Text style={styles.radioLabel}>
{t("payment.pay-amount-due")}
</Text>
</View> </View>
<Text style={styles.amountText}> <Text style={styles.amountText}>
{dueAmount?.toFixed(2)} {dueAmount?.toFixed(2)}
@ -255,7 +294,9 @@ const SelectAmountScreen = () => {
<View style={styles.radioInner} /> <View style={styles.radioInner} />
)} )}
</View> </View>
<Text style={styles.radioLabel}>Enter custom amount</Text> <Text style={styles.radioLabel}>
{t("payment.enter-custom-amount")}
</Text>
</TouchableOpacity> </TouchableOpacity>
<View style={styles.inputContainer}> <View style={styles.inputContainer}>
@ -267,17 +308,7 @@ const SelectAmountScreen = () => {
styles.errorInput, styles.errorInput,
]} ]}
value={values.customAmount} value={values.customAmount}
onChangeText={(text) => { onChangeText={handleCustomAmountChange}
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");
}}
onBlur={handleBlur("customAmount")} onBlur={handleBlur("customAmount")}
placeholder="₹" placeholder="₹"
placeholderTextColor="#94A3B8" placeholderTextColor="#94A3B8"
@ -297,7 +328,9 @@ const SelectAmountScreen = () => {
> >
{touched.customAmount && errors.customAmount {touched.customAmount && errors.customAmount
? errors.customAmount ? errors.customAmount
: `Minimum: ₹${payments.MIN_AMOUNT}`} : `${t("payment.minimum")}: ₹${
payments.MIN_AMOUNT
}`}
</Text> </Text>
</View> </View>
</View> </View>
@ -324,14 +357,19 @@ const SelectAmountScreen = () => {
</View> </View>
</ScrollView> </ScrollView>
<View style={styles.buttonContainer}> <View
style={[
styles.buttonContainer,
!keyboardVisible && { marginBottom: insets.bottom },
]}
>
<TouchableOpacity <TouchableOpacity
style={[ style={[
styles.payButton, styles.payButton,
!isPayButtonEnabled() && styles.disabledButton, !isButtonEnabled && styles.disabledButton,
]} ]}
onPress={() => handleSubmit()} onPress={() => handleSubmit()}
disabled={!isPayButtonEnabled()} disabled={!isButtonEnabled}
> >
{getPaymentAmount() < payments.MIN_AMOUNT ? ( {getPaymentAmount() < payments.MIN_AMOUNT ? (
<Text style={styles.payButtonText}>Select Amount</Text> <Text style={styles.payButtonText}>Select Amount</Text>

View File

@ -13,6 +13,7 @@ import { RootState } from "../../store/rootReducer"; // Adjust path as needed
import { getUserDetails } from "../../store/userSlice"; import { getUserDetails } from "../../store/userSlice";
import { AppDispatch } from "@/store"; import { AppDispatch } from "@/store";
import Header from "@/components/common/Header"; import Header from "@/components/common/Header";
import { useTranslation } from "react-i18next";
interface MyVehicleScreenProps { interface MyVehicleScreenProps {
navigation?: any; // Replace with proper navigation type navigation?: any; // Replace with proper navigation type
@ -59,14 +60,15 @@ const MyVehicleScreen: React.FC<MyVehicleScreenProps> = ({ navigation }) => {
); );
} }
const { t } = useTranslation();
return ( return (
<> <>
<Header title="My Vehicle" showBackButton={true} /> <Header title={t("profile.my-vehicle")} showBackButton={true} />
<View style={styles.content}> <View style={styles.content}>
<View style={styles.itemContainer}> <View style={styles.itemContainer}>
{/* OEM - Model */} {/* OEM - Model */}
<View style={styles.item}> <View style={styles.item}>
<Text style={styles.label}>OEM - Model</Text> <Text style={styles.label}>{t("profile.oem-model")}</Text>
<Text style={styles.value}> <Text style={styles.value}>
{vehicle?.model ? `Yatri - ${vehicle.model}` : "--"} {vehicle?.model ? `Yatri - ${vehicle.model}` : "--"}
</Text> </Text>
@ -76,7 +78,7 @@ const MyVehicleScreen: React.FC<MyVehicleScreenProps> = ({ navigation }) => {
{/* Chassis Number */} {/* Chassis Number */}
<View style={styles.item}> <View style={styles.item}>
<Text style={styles.label}>Chassis Number</Text> <Text style={styles.label}>{t("profile.chassis-number")}</Text>
<Text style={styles.value}>{vehicle?.chasis_number || "--"}</Text> <Text style={styles.value}>{vehicle?.chasis_number || "--"}</Text>
</View> </View>
@ -84,7 +86,7 @@ const MyVehicleScreen: React.FC<MyVehicleScreenProps> = ({ navigation }) => {
{/* Vehicle ID */} {/* Vehicle ID */}
<View style={styles.item}> <View style={styles.item}>
<Text style={styles.label}>Vehicle ID</Text> <Text style={styles.label}>{t("profile.vehicle-id")}</Text>
<Text style={styles.value}> <Text style={styles.value}>
{vehicle?.vehicle_id ? vehicle.vehicle_id.toString() : "--"} {vehicle?.vehicle_id ? vehicle.vehicle_id.toString() : "--"}
</Text> </Text>

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useTransition } from "react";
import { import {
View, View,
Text, Text,
@ -20,6 +20,8 @@ import { setUserData } from "@/store/userSlice";
import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useSafeAreaInsets } from "react-native-safe-area-context";
import { router } from "expo-router"; import { router } from "expo-router";
import { useSnackbar } from "@/contexts/Snackbar"; import { useSnackbar } from "@/contexts/Snackbar";
import Header from "@/components/common/Header";
import { useTranslation } from "react-i18next";
export default function EditName() { export default function EditName() {
const { data } = useSelector((state: RootState) => state.user); const { data } = useSelector((state: RootState) => state.user);
@ -47,73 +49,82 @@ export default function EditName() {
} }
}; };
const { t } = useTranslation();
return ( return (
<KeyboardAvoidingView <>
style={styles.container} <Header title={t("profile.edit-name")} showBackButton={true} />
behavior={Platform.OS === "ios" ? "padding" : "height"} <KeyboardAvoidingView
> style={styles.container}
<TouchableWithoutFeedback onPress={Keyboard.dismiss}> behavior={Platform.OS === "ios" ? "padding" : "height"}
<View style={styles.inner}> >
<Formik <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
initialValues={{ name: originalName }} <View style={styles.inner}>
validationSchema={nameSchema} <Formik
onSubmit={handleSave} initialValues={{ name: originalName }}
enableReinitialize validationSchema={nameSchema}
> onSubmit={handleSave}
{({ enableReinitialize
handleChange, >
handleBlur, {({
handleSubmit, handleChange,
values, handleBlur,
touched, handleSubmit,
errors, values,
}) => { touched,
const hasChanged = values.name !== originalName; errors,
const hasError = !!errors.name; }) => {
const hasChanged = values.name !== originalName;
const hasError = !!errors.name;
return ( return (
<View style={styles.formContainer}> <View style={styles.formContainer}>
<View style={styles.inputContainer}> <View style={styles.inputContainer}>
<Text style={styles.label}>Enter Name</Text> <Text style={styles.label}>
<TextInput {t("profile.enter-name")}
</Text>
<TextInput
style={[
styles.input,
{
borderColor:
touched.name && errors.name
? "#D51D10"
: "#D8DDE7",
},
]}
value={values.name}
onChangeText={handleChange("name")}
onBlur={handleBlur("name")}
placeholder="Enter your name"
placeholderTextColor="#949CAC"
/>
{touched.name && errors.name && (
<Text style={styles.error}>{errors.name}</Text>
)}
</View>
<TouchableOpacity
onPress={handleSubmit as unknown as () => void}
style={[ style={[
styles.input, styles.button,
{ hasChanged && !hasError
borderColor: ? styles.buttonEnabled
touched.name && errors.name ? "#D51D10" : "#D8DDE7", : styles.buttonDisabled,
}, { marginBottom: insets.bottom },
]} ]}
value={values.name} disabled={!hasChanged || hasError}
onChangeText={handleChange("name")} >
onBlur={handleBlur("name")} <Text style={styles.buttonText}>{t("profile.save")}</Text>
placeholder="Enter your name" </TouchableOpacity>
placeholderTextColor="#949CAC"
/>
{touched.name && errors.name && (
<Text style={styles.error}>{errors.name}</Text>
)}
</View> </View>
);
<TouchableOpacity }}
onPress={handleSubmit as unknown as () => void} </Formik>
style={[ </View>
styles.button, </TouchableWithoutFeedback>
hasChanged && !hasError </KeyboardAvoidingView>
? styles.buttonEnabled </>
: styles.buttonDisabled,
{ marginBottom: insets.bottom },
]}
disabled={!hasChanged || hasError}
>
<Text style={styles.buttonText}>Save</Text>
</TouchableOpacity>
</View>
);
}}
</Formik>
</View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
); );
} }
@ -126,7 +137,6 @@ const styles = StyleSheet.create({
inner: { inner: {
flex: 1, flex: 1,
justifyContent: "space-between", justifyContent: "space-between",
paddingVertical: 24,
backgroundColor: "#F3F5F8", backgroundColor: "#F3F5F8",
}, },
formContainer: { formContainer: {

View File

@ -22,6 +22,7 @@ import { bytesToMB, updateUserProfile, uploadImage } from "@/utils/User";
import { setUserData } from "@/store/userSlice"; import { setUserData } from "@/store/userSlice";
import { Overlay } from "@/components/common/Overlay"; import { Overlay } from "@/components/common/Overlay";
import Header from "@/components/common/Header"; import Header from "@/components/common/Header";
import { useTranslation } from "react-i18next";
export default function ProfileScreen() { export default function ProfileScreen() {
const [isLangaugeModalVisible, setLanguageModalVisible] = const [isLangaugeModalVisible, setLanguageModalVisible] =
@ -90,9 +91,11 @@ export default function ProfileScreen() {
} }
} }
}; };
const { t } = useTranslation();
return ( return (
<> <>
<Header title="My Account" showBackButton={true} /> <Header title={t("profile.my-account")} showBackButton={true} />
<ScrollView contentContainerStyle={styles.scrollContent}> <ScrollView contentContainerStyle={styles.scrollContent}>
<View style={styles.avatarContainer}> <View style={styles.avatarContainer}>
<Image <Image
@ -114,7 +117,7 @@ export default function ProfileScreen() {
<View style={styles.card}> <View style={styles.card}>
<View style={styles.row}> <View style={styles.row}>
<View style={styles.textGroup}> <View style={styles.textGroup}>
<Text style={styles.label}>Name</Text> <Text style={styles.label}>{t("profile.name")}</Text>
<Text style={styles.value}>{userName}</Text> <Text style={styles.value}>{userName}</Text>
</View> </View>
<TouchableOpacity <TouchableOpacity
@ -129,22 +132,24 @@ export default function ProfileScreen() {
<View style={styles.divider} /> <View style={styles.divider} />
<View style={styles.row}> <View style={styles.row}>
<View style={styles.textGroup}> <View style={styles.textGroup}>
<Text style={styles.label}>Mobile Number</Text> <Text style={styles.label}>{t("profile.mobile-number")}</Text>
<Text style={styles.value}>{mobileNumber}</Text> <Text style={styles.value}>{mobileNumber}</Text>
</View> </View>
</View> </View>
</View> </View>
<View style={styles.card}> <View style={styles.card}>
{menuItem("My Vehicle", () => router.push("/user/MyVechicle"))} {menuItem(`${t("profile.my-vehicle")}`, () =>
router.push("/user/MyVechicle")
)}
<View style={styles.divider} /> <View style={styles.divider} />
{menuItem("Language", () => toggleLanguageModal())} {menuItem(`${t("profile.language")}`, () => toggleLanguageModal())}
</View> </View>
<View style={styles.card}> <View style={styles.card}>
{menuItem("About App")} {menuItem(`${t("profile.about-app")}`)}
<View style={styles.divider} /> <View style={styles.divider} />
{menuItem("Logout", handleLogout)} {menuItem(`${t("profile.logout")}`, handleLogout)}
</View> </View>
</ScrollView> </ScrollView>
<LanguageModal <LanguageModal

View File

@ -0,0 +1,9 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="27.6632" width="28" height="28" rx="14" transform="rotate(-81.0919 0 27.6632)" fill="#1249ED"/>
<mask id="mask0_14_6042" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="4" y="4" width="24" height="24">
<rect x="4.57031" y="24.3309" width="20" height="20" transform="rotate(-81.0919 4.57031 24.3309)" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_14_6042)">
<path d="M17.3516 17.857L14.3549 22.849C14.2583 23.0167 14.1315 23.1303 13.9746 23.1901C13.8176 23.2498 13.6636 23.2679 13.5127 23.2442C13.3618 23.2206 13.2212 23.1528 13.0912 23.0411C12.9611 22.9293 12.8757 22.7789 12.835 22.5897L10.1231 9.21681C10.0803 9.0414 10.0914 8.88145 10.1562 8.73697C10.221 8.59249 10.3169 8.47396 10.4439 8.3814C10.5708 8.28883 10.713 8.2338 10.8704 8.21629C11.0278 8.19879 11.1834 8.23724 11.3374 8.33166L23.2392 15.0052C23.4068 15.1017 23.5239 15.229 23.5905 15.3871C23.6571 15.5451 23.6786 15.6996 23.6549 15.8506C23.6313 16.0015 23.567 16.1426 23.4621 16.2737C23.3572 16.4049 23.2102 16.4908 23.021 16.5315L17.3516 17.857Z" fill="#FCFCFC"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 707 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -3,6 +3,7 @@ import BottomSheetModal from "@/components/common/BottomSheetModal"; // adjust p
import { StyleSheet, Text, TouchableOpacity } from "react-native"; import { StyleSheet, Text, TouchableOpacity } from "react-native";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getLanguage, setLanguage } from "@/services/i18n"; import { getLanguage, setLanguage } from "@/services/i18n";
import { useTranslation } from "react-i18next";
interface CustomerSupportProps { interface CustomerSupportProps {
visible: boolean; visible: boolean;
@ -22,6 +23,8 @@ export default function LanguageModal({
})(); })();
}, []); }, []);
const { t } = useTranslation();
const handleLanguagePress = (lang: "en" | "hi") => { const handleLanguagePress = (lang: "en" | "hi") => {
setSelectedLang(lang); setSelectedLang(lang);
setLanguage(lang); setLanguage(lang);
@ -31,7 +34,7 @@ export default function LanguageModal({
<BottomSheetModal <BottomSheetModal
visible={visible} visible={visible}
onClose={onClose} onClose={onClose}
heading="Select Language" heading={t("profile.select-language")}
> >
<TouchableOpacity <TouchableOpacity
style={[ style={[

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { View, Text, StyleSheet, Pressable } from "react-native"; import { View, Text, StyleSheet, Pressable } from "react-native";
import ChevronRight from "../../assets/icons/chevron_rightside.svg"; import ChevronRight from "../../assets/icons/chevron_rightside.svg";
import { useTranslation } from "react-i18next";
type Props = { type Props = {
warrantyStartDate: string; warrantyStartDate: string;
@ -29,17 +30,31 @@ const BatteryWarrantyCard: React.FC<Props> = ({
(remaining % (30.44 * 24 * 60 * 60 * 1000)) / (24 * 60 * 60 * 1000) (remaining % (30.44 * 24 * 60 * 60 * 1000)) / (24 * 60 * 60 * 1000)
); );
const { t } = useTranslation();
return ( return (
<View style={styles.card}> <View style={styles.card}>
<View style={styles.topRow}> <View style={styles.topRow}>
<View style={styles.textColumn}> <View style={styles.textColumn}>
<Text style={styles.title}>Battery Warranty Left</Text> <Text style={styles.title}>{t("home.battery-warranty-left")}</Text>
<Text style={styles.time}> <Text style={styles.time}>
{`${yearsLeft} year${ {(() => {
yearsLeft !== 1 ? "s" : "" const parts: string[] = [];
}, ${monthsLeft} month${
monthsLeft !== 1 ? "s" : "" if (yearsLeft > 0) {
}, ${daysLeft} day${daysLeft !== 1 ? "s" : ""}`} 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(", ");
})()}
</Text> </Text>
</View> </View>
<Pressable style={styles.iconButton}> <Pressable style={styles.iconButton}>

View File

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next";
import { View, Text, StyleSheet, ViewStyle } from "react-native"; import { View, Text, StyleSheet, ViewStyle } from "react-native";
interface BatteryStatusProps { interface BatteryStatusProps {
@ -15,19 +16,19 @@ const BatteryStatus: React.FC<BatteryStatusProps> = ({ status, style }) => {
switch (status) { switch (status) {
case 1: case 1:
return { return {
text: "Charging", text: "charging",
backgroundColor: "#DAF5ED", backgroundColor: "#DAF5ED",
textColor: "#006C4D", textColor: "#006C4D",
}; };
case -1: case -1:
return { return {
text: "Discharging", text: "discharging",
backgroundColor: "#E5EBFD", backgroundColor: "#E5EBFD",
textColor: "#1249ED", textColor: "#1249ED",
}; };
case 0: case 0:
return { return {
text: "Idle", text: "idle",
backgroundColor: "#D8DDE7", backgroundColor: "#D8DDE7",
textColor: "#565F70", textColor: "#565F70",
}; };
@ -42,6 +43,8 @@ const BatteryStatus: React.FC<BatteryStatusProps> = ({ status, style }) => {
const config = getStatusConfig(); const config = getStatusConfig();
const { t } = useTranslation();
return ( return (
<View <View
style={[ style={[
@ -60,7 +63,9 @@ const BatteryStatus: React.FC<BatteryStatusProps> = ({ status, style }) => {
}, },
]} ]}
> >
{config.text} {status !== null && status !== undefined
? t(`home.${config.text}`)
: "---"}
</Text> </Text>
</View> </View>
); );

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useTransition } from "react";
import { import {
View, View,
Text, Text,
@ -7,6 +7,7 @@ import {
Dimensions, Dimensions,
} from "react-native"; } from "react-native";
import { MaterialIcons } from "@expo/vector-icons"; import { MaterialIcons } from "@expo/vector-icons";
import { useTranslation } from "react-i18next";
interface PaymentDueCardProps { interface PaymentDueCardProps {
label: string; label: string;
@ -24,6 +25,7 @@ const PaymentDueCard: React.FC<PaymentDueCardProps> = ({
amount, amount,
onPress, onPress,
}) => { }) => {
const { t } = useTranslation();
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.leftSection}> <View style={styles.leftSection}>
@ -40,7 +42,7 @@ const PaymentDueCard: React.FC<PaymentDueCardProps> = ({
onPress={onPress} onPress={onPress}
activeOpacity={0.8} activeOpacity={0.8}
> >
<Text style={styles.buttonText}>Pay Now</Text> <Text style={styles.buttonText}>{t("home.pay-now")}</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
); );

View File

@ -11,18 +11,23 @@ import {
import Checkbox from "expo-checkbox"; import Checkbox from "expo-checkbox";
import { issueConfig } from "@/constants/config"; import { issueConfig } from "@/constants/config";
import CloseIcon from "@/assets/icons/close.svg"; import CloseIcon from "@/assets/icons/close.svg";
interface IssueSelectorModalProps { interface IssueSelectorModalProps {
visible: boolean; visible: boolean;
onClose: () => void; onClose: () => void;
onSelect: (selectedValues: string[]) => void; onSelect: (selectedValues: string[]) => void;
initialSelectedValues?: string[]; // Previously saved selections
} }
export default function IssueSelectorModal({ export default function IssueSelectorModal({
visible, visible,
onClose, onClose,
onSelect, onSelect,
initialSelectedValues = [],
}: IssueSelectorModalProps) { }: IssueSelectorModalProps) {
const [selectedValues, setSelectedValues] = useState<string[]>([]); const [selectedValues, setSelectedValues] = useState<string[]>(
initialSelectedValues
);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const toggleValue = (value: string) => { const toggleValue = (value: string) => {
@ -42,13 +47,37 @@ export default function IssueSelectorModal({
const clearSelection = () => setSelectedValues([]); 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 ( return (
<Modal visible={visible} animationType="slide"> <Modal visible={visible} animationType="slide">
<View style={styles.container}> <View style={styles.container}>
{/* Header */} {/* Header */}
<View style={styles.headerBar}> <View style={styles.headerBar}>
<Text>Select Issue</Text> <Text>Select Issue</Text>
<TouchableOpacity onPress={onClose}> <TouchableOpacity onPress={handleClose}>
<CloseIcon /> <CloseIcon />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@ -67,8 +96,15 @@ export default function IssueSelectorModal({
<Text <Text
style={styles.counterText} style={styles.counterText}
>{`${selectedValues.length}/23 Selected`}</Text> >{`${selectedValues.length}/23 Selected`}</Text>
<TouchableOpacity onPress={clearSelection}> <TouchableOpacity onPress={clearSelection} disabled={!hasSelection}>
<Text style={styles.clearText}>Clear</Text> <Text
style={[
styles.clearText,
!hasSelection && styles.clearTextDisabled,
]}
>
Clear
</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@ -98,15 +134,26 @@ export default function IssueSelectorModal({
))} ))}
</ScrollView> </ScrollView>
<View> <View style={styles.buttonsContainer}>
<TouchableOpacity style={styles.cancelButton} onPress={onClose}>
<Text style={styles.cancelText}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={styles.doneButton} style={[
onPress={() => { styles.doneButton,
onSelect(selectedValues); !hasSelection && styles.doneButtonDisabled,
onClose(); ]}
}} onPress={handleSelect}
disabled={!hasSelection}
> >
<Text style={styles.doneText}>Done</Text> <Text
style={[
styles.doneText,
!hasSelection && styles.doneTextDisabled,
]}
>
Select
</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
@ -115,6 +162,17 @@ export default function IssueSelectorModal({
} }
const styles = StyleSheet.create({ 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: { headerBar: {
flexDirection: "row", flexDirection: "row",
justifyContent: "space-between", justifyContent: "space-between",
@ -123,6 +181,49 @@ const styles = StyleSheet.create({
paddingVertical: 12, paddingVertical: 12,
backgroundColor: "#F9F9F9", 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" }, container: { flex: 1, backgroundColor: "#fff" },
header: { header: {
paddingHorizontal: 16, paddingHorizontal: 16,
@ -155,10 +256,6 @@ const styles = StyleSheet.create({
color: "#555", color: "#555",
fontWeight: "600", fontWeight: "600",
}, },
clearText: {
fontSize: 14,
color: "#ADB4BD",
},
scrollArea: { paddingHorizontal: 0, backgroundColor: "#FCFCFC" }, scrollArea: { paddingHorizontal: 0, backgroundColor: "#FCFCFC" },
category: { category: {
fontSize: 14, fontSize: 14,
@ -179,14 +276,4 @@ const styles = StyleSheet.create({
fontSize: 12, fontSize: 12,
color: "#252A34", color: "#252A34",
}, },
doneButton: {
backgroundColor: "#00875F",
padding: 16,
alignItems: "center",
},
doneText: {
color: "#fff",
fontSize: 14,
fontWeight: "bold",
},
}); });

View File

@ -11,6 +11,7 @@ const api = axios.create({
// Request interceptor to add auth token // Request interceptor to add auth token
api.interceptors.request.use(async (config) => { api.interceptors.request.use(async (config) => {
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN); const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
// const token = "";
// Debug log for request // Debug log for request
console.log("🚀 [API Request]"); console.log("🚀 [API Request]");
@ -47,11 +48,9 @@ api.interceptors.response.use(
console.log("Status:", status); console.log("Status:", status);
console.log("Response data:", error.response?.data); console.log("Response data:", error.response?.data);
// If token is expired or not present
if (status === 401 || status === 403) { if (status === 401 || status === 403) {
console.log("Token expired or not present"); console.log("Token expired or not present");
await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN); await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
router.replace("/auth/login");
} }
return Promise.reject(error); return Promise.reject(error);

View File

@ -3,7 +3,7 @@
"welcome": "Welcome to Driver Saathi", "welcome": "Welcome to Driver Saathi",
"enter-mobile-number": "Enter Mobile Number", "enter-mobile-number": "Enter Mobile Number",
"enter-registered-mobile-number": "Enter your registered 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.", "number-not-registered": "Number not registered.",
"enter-otp": "Please enter OTP sent to your mobile number", "enter-otp": "Please enter OTP sent to your mobile number",
"verify-otp": "Verify OTP", "verify-otp": "Verify OTP",
@ -19,8 +19,6 @@
"my-battery": "My Battery" "my-battery": "My Battery"
}, },
"home": { "home": {
"vehicle-name": "Yatri - NBX 600",
"vehicle-id": "DL253C3602",
"battery-health": "SoH", "battery-health": "SoH",
"total-distance": "Total Distance", "total-distance": "Total Distance",
"payment-due": "Payment Due", "payment-due": "Payment Due",
@ -35,14 +33,17 @@
"all-emi-paid": "All EMI Paid! Contact your dealer", "all-emi-paid": "All EMI Paid! Contact your dealer",
"regular-service-due": "Regular service due in 3 days! Schedule now.", "regular-service-due": "Regular service due in 3 days! Schedule now.",
"view-vehicle-location": "View Vehicle Location", "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", "alerts": "Alerts",
"emi-alert": "14 days left to pay the EMI!" "emi-alert": "14 days left to pay the EMI!",
"km": "km"
}, },
"profile": { "profile": {
"my-account": "My Account", "my-account": "My Account",
"enter-name": "Enter Name", "enter-name": "Enter Name",
"name": "Amar Kesari", "name": "Name",
"mobile-number": "Mobile Number", "mobile-number": "Mobile Number",
"my-vehicle": "My Vehicle", "my-vehicle": "My Vehicle",
"language": "Language", "language": "Language",
@ -58,7 +59,8 @@
"customer-support": "Customer Support", "customer-support": "Customer Support",
"whatsapp": "Whatsapp", "whatsapp": "Whatsapp",
"call-us": "Call Us", "call-us": "Call Us",
"email": "Email" "email": "Email",
"view-plan": "View Plan"
}, },
"payment": { "payment": {
"last-emi-details": "Last EMI Details", "last-emi-details": "Last EMI Details",
@ -93,11 +95,22 @@
"contact-dealer": "For further queries, contact your dealer!", "contact-dealer": "For further queries, contact your dealer!",
"view-all": "View all", "view-all": "View all",
"emi-completed": "EMI Completed", "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": { "service": {
"schedule-maintenance": "Schedule Maintenance", "schedule-maintenance": "Schedule Maintenance",
"service-type": "Service Type: Regular", "service-type": "Service Type",
"issue": "Issue", "issue": "Issue",
"select-issue": "Select Issue", "select-issue": "Select Issue",
"select-datetime": "Select Date and Time", "select-datetime": "Select Date and Time",
@ -106,11 +119,13 @@
"supported-formats": "Supported formats include JPG, JPEG and PNG.", "supported-formats": "Supported formats include JPG, JPEG and PNG.",
"comments": "Comments", "comments": "Comments",
"submit": "Submit", "submit": "Submit",
"select-issues-tba": "Select issues ------(TBA)", "select-issues-tba": "Select issues",
"clear": "Clear", "clear": "Clear",
"issues-selected": "3 issue selected", "issues-selected": "issue selected",
"service-request-success": "Service request submitted successfully", "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": {
"battery-and-warranty": "My Battery and Warranty", "battery-and-warranty": "My Battery and Warranty",

View File

@ -3,6 +3,7 @@
"welcome": "ड्राइवर साथी में आपका स्वागत है", "welcome": "ड्राइवर साथी में आपका स्वागत है",
"enter-mobile-number": "मोबाइल नंबर दर्ज करें", "enter-mobile-number": "मोबाइल नंबर दर्ज करें",
"enter-registered-mobile-number": "अपना पंजीकृत मोबाइल नंबर दर्ज करें", "enter-registered-mobile-number": "अपना पंजीकृत मोबाइल नंबर दर्ज करें",
"for-any-queries-contact-us": "किसी भी प्रकार की सहायता के लिए, हमसे संपर्क करें",
"number-not-registered": "नंबर पंजीकृत नहीं है।", "number-not-registered": "नंबर पंजीकृत नहीं है।",
"enter-otp": "कृपया अपने मोबाइल नंबर पर भेजा गया OTP दर्ज करें।", "enter-otp": "कृपया अपने मोबाइल नंबर पर भेजा गया OTP दर्ज करें।",
"verify-otp": "ओटीपी वेरिफाई करें", "verify-otp": "ओटीपी वेरिफाई करें",
@ -18,8 +19,6 @@
"my-battery": "मेरी बैटरी" "my-battery": "मेरी बैटरी"
}, },
"home": { "home": {
"vehicle-name": "यात्री - NBX 600",
"vehicle-id": "DL253C3602",
"battery-health": "बैटरी हेल्थ", "battery-health": "बैटरी हेल्थ",
"total-distance": "कुल दूरी", "total-distance": "कुल दूरी",
"payment-due": "भुगतान बकाया", "payment-due": "भुगतान बकाया",
@ -34,14 +33,17 @@
"all-emi-paid": "सभी EMI का भुगतान किया गया! अपने डीलर से संपर्क करें", "all-emi-paid": "सभी EMI का भुगतान किया गया! अपने डीलर से संपर्क करें",
"regular-service-due": "रेगुलर सर्विस 3 दिनों में होने वाली है! अभी शिड्यूल कर लो.", "regular-service-due": "रेगुलर सर्विस 3 दिनों में होने वाली है! अभी शिड्यूल कर लो.",
"view-vehicle-location": "वाहन का स्थान देखें", "view-vehicle-location": "वाहन का स्थान देखें",
"battery-age": "7 वर्ष, 11 महीने, 29 दिन", "year": "साल",
"month": "महीना",
"day": "दिन",
"alerts": "अलर्ट", "alerts": "अलर्ट",
"emi-alert": "EMI का भुगतान करने के लिए 14 दिन शेष!" "emi-alert": "EMI का भुगतान करने के लिए 14 दिन शेष!",
"km": "कि.मी."
}, },
"profile": { "profile": {
"my-account": "मेरा खाता", "my-account": "मेरा खाता",
"enter-name": "नाम दर्ज करें", "enter-name": "नाम दर्ज करें",
"name": "अमर केसरी", "name": "नाम",
"mobile-number": "मोबाइल नंबर", "mobile-number": "मोबाइल नंबर",
"my-vehicle": "मेरा वाहन", "my-vehicle": "मेरा वाहन",
"language": "भाषा", "language": "भाषा",
@ -52,7 +54,7 @@
"name-changed": "नाम सफलतापूर्वक बदल दिया गया", "name-changed": "नाम सफलतापूर्वक बदल दिया गया",
"oem-model": "OEM - मॉडल", "oem-model": "OEM - मॉडल",
"chassis-number": "चेसिस नंबर", "chassis-number": "चेसिस नंबर",
"vehicle-id": "वाहन आईडी", "vehicle-id": "वाहन ID",
"select-language": "भाषा चुनें", "select-language": "भाषा चुनें",
"customer-support": "ग्राहक सहायता", "customer-support": "ग्राहक सहायता",
"whatsapp": "व्हाट्सएप", "whatsapp": "व्हाट्सएप",
@ -65,7 +67,7 @@
"amount-paid": "भुगतान की गई राशि", "amount-paid": "भुगतान की गई राशि",
"due-date": "ड्यू डेट", "due-date": "ड्यू डेट",
"payment-status": "भुगतान की स्थिति", "payment-status": "भुगतान की स्थिति",
"pay-emi": "ईएमआई भुगतान करो", "pay-emi": "EMI भुगतान करो",
"payment-history": "पेमेंट हिस्ट्री", "payment-history": "पेमेंट हिस्ट्री",
"no-payments": "कोई भुगतान नहीं मिला!", "no-payments": "कोई भुगतान नहीं मिला!",
"pending": "पेंडिंग है", "pending": "पेंडिंग है",
@ -92,11 +94,23 @@
"contact-dealer": "अधिक जानकारी के लिए, अपने डीलर से संपर्क करें!", "contact-dealer": "अधिक जानकारी के लिए, अपने डीलर से संपर्क करें!",
"view-all": "सभी देखें", "view-all": "सभी देखें",
"emi-completed": "EMI पूर्ण", "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": { "service": {
"schedule-maintenance": "शेड्यूल मेंटेनेंस", "schedule-maintenance": "शेड्यूल मेंटेनेंस",
"service-type": "सेवा प्रकार: नियमित", "service-type": "सेवा प्रकार",
"issue": "समस्या", "issue": "समस्या",
"select-issue": "समस्या का चयन करें", "select-issue": "समस्या का चयन करें",
"select-datetime": "दिनांक और समय का चयन करें", "select-datetime": "दिनांक और समय का चयन करें",
@ -105,11 +119,13 @@
"supported-formats": "समर्थित प्रारूपों में JPG, JPEG और PNG शामिल हैं।", "supported-formats": "समर्थित प्रारूपों में JPG, JPEG और PNG शामिल हैं।",
"comments": "टिप्पणियाँ", "comments": "टिप्पणियाँ",
"submit": "सबमिट करें", "submit": "सबमिट करें",
"select-issues-tba": "समस्याओं का चयन करें ------(TBA)", "select-issues-tba": "समस्याओं का चयन करें",
"clear": "साफ़ करें", "clear": "साफ़ करें",
"issues-selected": "3 समस्याएँ चुनी गई", "issues-selected": "समस्याएँ चुनी गई",
"service-request-success": "सेवा अनुरोध सफलतापूर्वक सबमिट किया गया", "service-request-success": "सेवा अनुरोध सफलतापूर्वक सबमिट किया गया",
"something-went-wrong": "कुछ गलत हो गया!" "something-went-wrong": "कुछ गलत हो गया!",
"words": "शब्द",
"select-valid-time": "सही समय चुनें"
}, },
"battery": { "battery": {
"battery-and-warranty": "मेरी बैटरी और वारंटी", "battery-and-warranty": "मेरी बैटरी और वारंटी",

View File

@ -3,17 +3,17 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { MyPlan } from "@/app/(tabs)/payments"; import { MyPlan } from "@/app/(tabs)/payments";
interface PaymentOrder { interface PaymentOrder {
amount: number; // kept as number for math amount: number;
id?: number; // optional if not always present id?: number;
expiry_date?: string; expiry_date?: string;
order_id: string; order_id: string;
payment_link?: string; payment_link?: string;
qr_code_url?: string; qr_code_url?: string;
status: string; // "confirmed", "pending", etc. status: string;
transaction_id?: string; transaction_id?: string;
transaction_order_id?: string; transaction_order_id?: string;
transaction_date?: string; transaction_date?: string;
payment_mode?: string[]; // e.g. ["UPI"] payment_mode?: string[];
payment_reference_id?: string; payment_reference_id?: string;
paid_by_upi_handle?: string; paid_by_upi_handle?: string;
upi_handle: string; upi_handle: string;
@ -23,12 +23,14 @@ interface EmiState {
due_amount: number | null; due_amount: number | null;
myPlan: MyPlan | null; myPlan: MyPlan | null;
paymentOrder: PaymentOrder | null; paymentOrder: PaymentOrder | null;
advance_balance: number | null;
} }
const initialState: EmiState = { const initialState: EmiState = {
due_amount: null, due_amount: null,
myPlan: null, myPlan: null,
paymentOrder: null, paymentOrder: null,
advance_balance: null,
}; };
const emiSlice = createSlice({ const emiSlice = createSlice({
@ -41,6 +43,9 @@ const emiSlice = createSlice({
setMyPlan(state, action: PayloadAction<MyPlan | null>) { setMyPlan(state, action: PayloadAction<MyPlan | null>) {
state.myPlan = action.payload; state.myPlan = action.payload;
}, },
setAdvanceBalance(state, action: PayloadAction<number | null>) {
state.advance_balance = action.payload;
},
setPaymentOrder(state, action: PayloadAction<PaymentOrder | null>) { setPaymentOrder(state, action: PayloadAction<PaymentOrder | null>) {
state.paymentOrder = action.payload; state.paymentOrder = action.payload;
}, },
@ -61,6 +66,7 @@ export const {
setPaymentOrder, setPaymentOrder,
clearPaymentOrder, clearPaymentOrder,
updatePaymentStatus, updatePaymentStatus,
setAdvanceBalance,
} = emiSlice.actions; } = emiSlice.actions;
export default emiSlice.reducer; export default emiSlice.reducer;