Add Lang config
parent
911f2eb4f4
commit
51a2a2272e
|
|
@ -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() {
|
|||
</Pressable>
|
||||
<Pressable
|
||||
style={styles.supportButton}
|
||||
onPress={() => setIsSupportModalVisible(true)}
|
||||
onPress={() => {
|
||||
setIsSupportModalVisible(true);
|
||||
console.log("hkdlfjaldf");
|
||||
}}
|
||||
>
|
||||
<CustomerCareIcon width={50} height={50} />
|
||||
</Pressable>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
router.push("/user/profile");
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<ProfileImage
|
||||
username={data?.name || "Vec"}
|
||||
textSize={20}
|
||||
boxSize={40}
|
||||
onClick={() => {
|
||||
router.push("/user/profile");
|
||||
}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
),
|
||||
});
|
||||
|
|
@ -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() {
|
|||
<MetricCard
|
||||
heading={t("home.total-distance")}
|
||||
value={totalDistance}
|
||||
unit="km"
|
||||
unit={t("home.km")}
|
||||
/>
|
||||
</View>
|
||||
{due_amount && (
|
||||
<PaymentDueCard
|
||||
label="Payment Due"
|
||||
label={t("home.payment-due")}
|
||||
amount={due_amount}
|
||||
onPress={() => {
|
||||
router.push("/payments/selectAmount");
|
||||
|
|
@ -269,7 +272,7 @@ export default function HomeScreen() {
|
|||
<LocationOff />
|
||||
<Text style={styles.errorText}>Fetching Location...</Text>
|
||||
</View>
|
||||
) : lat != null && lon != null ? (
|
||||
) : lat != null && lon != null && !(lat == 0 && lon == 0) ? (
|
||||
<>
|
||||
<View style={styles.mapContainer}>
|
||||
<MapView
|
||||
|
|
@ -293,8 +296,12 @@ export default function HomeScreen() {
|
|||
rotation={bearing}
|
||||
anchor={{ x: 0.5, y: 0.5 }}
|
||||
tracksViewChanges={false}
|
||||
image={require("../../assets/images/marker.png")}
|
||||
/>
|
||||
>
|
||||
<Image
|
||||
source={require("../../assets/images/marker.png")}
|
||||
style={{ height: 35, width: 35 }}
|
||||
/>
|
||||
</Marker>
|
||||
</MapView>
|
||||
</View>
|
||||
<TouchableOpacity>
|
||||
|
|
@ -302,24 +309,24 @@ export default function HomeScreen() {
|
|||
style={styles.viewLocationText}
|
||||
onPress={openInGoogleMaps}
|
||||
>
|
||||
View Vehicle Location
|
||||
{t("home.view-vehicle-location")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
) : (
|
||||
error && (
|
||||
<View style={styles.errorContainer}>
|
||||
<LocationOff />
|
||||
<Text style={styles.errorText}>Error fetching location</Text>
|
||||
</View>
|
||||
)
|
||||
<View style={styles.errorContainer}>
|
||||
<LocationOff />
|
||||
<Text style={styles.errorText}>{t("home.error-location")}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
{warrantyEndDate && warrantyStartDate && (
|
||||
<BatteryWarrantyCard
|
||||
warrantyStartDate={warrantyStartDate}
|
||||
warrantyEndDate={warrantyEndDate}
|
||||
/>
|
||||
<TouchableOpacity onPress={() => router.push("/(tabs)/my-battery")}>
|
||||
<BatteryWarrantyCard
|
||||
warrantyStartDate={warrantyStartDate}
|
||||
warrantyEndDate={warrantyEndDate}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</ScrollView>
|
||||
<CustomerSupportModal
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { RootState } from "@/store";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View, Text, StyleSheet, ScrollView } from "react-native";
|
||||
import { useSelector } from "react-redux";
|
||||
const BatteryDetails = () => {
|
||||
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 (
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
{/* Battery Card */}
|
||||
{/* Battery Section */}
|
||||
<View style={styles.card}>
|
||||
<View style={styles.cardHeader}>
|
||||
<Text style={styles.cardTitle}>Battery</Text>
|
||||
{isInWarranty && (
|
||||
<Text style={styles.cardTitle}>{t("battery.battery")}</Text>
|
||||
{isInWarranty ? (
|
||||
<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 style={styles.divider} />
|
||||
<InfoRow label="Model" value={model} />
|
||||
<InfoRow label="Battery ID" value={batteryId} />
|
||||
<InfoRow label="BMS ID" value={bmsId} />
|
||||
<InfoRow label={t("battery.model")} value={model} />
|
||||
<InfoRow label={t("battery.battery-id")} value={batteryId} />
|
||||
<InfoRow label={t("battery.bms-id")} value={bmsId} />
|
||||
</View>
|
||||
|
||||
{/* Warranty Details */}
|
||||
{/* Battery Warranty Details Section */}
|
||||
<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} />
|
||||
<InfoRow label="Start Date" value={formatDate(start)} />
|
||||
<InfoRow label="End Date" value={formatDate(end)} />
|
||||
<InfoRow label="Duration Left" value={durationText} />
|
||||
<InfoRow label={t("battery.start-date")} value={formatDate(start)} />
|
||||
<InfoRow label={t("battery.end-date")} value={formatDate(end)} />
|
||||
<InfoRow label={t("battery.duration-left")} value={durationText} />
|
||||
{start && end && !isNaN(start.getTime()) && !isNaN(end.getTime()) && (
|
||||
<View style={styles.progressBarBackground}>
|
||||
<View
|
||||
|
|
@ -105,19 +126,19 @@ const BatteryDetails = () => {
|
|||
)}
|
||||
</View>
|
||||
|
||||
{/* VIM Details */}
|
||||
{/* VIM Details Section */}
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>VIM Details</Text>
|
||||
<Text style={styles.cardTitle}>{t("battery.vim-details")}</Text>
|
||||
<View style={styles.divider} />
|
||||
<InfoRow label="VIM ID" value={vimId} />
|
||||
<InfoRow label="Serial Number" value={serialNumber} />
|
||||
<InfoRow label={t("battery.vim-id")} value={vimId} />
|
||||
<InfoRow label={t("battery.serial-number")} value={serialNumber} />
|
||||
</View>
|
||||
|
||||
{/* Charger Details */}
|
||||
{/* Charger Details Section */}
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Charger Details</Text>
|
||||
<Text style={styles.cardTitle}>{t("battery.charger-details")}</Text>
|
||||
<View style={styles.divider} />
|
||||
<InfoRow label="UID" value={chargerUid} />
|
||||
<InfoRow label={t("battery.uid")} value={chargerUid} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
|
|
@ -138,9 +159,14 @@ const InfoRow: React.FC<InfoRowProps> = ({ 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,
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
>
|
||||
<CustomerCareIcon height={50} width={50} />
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
router.push("/user/profile");
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<ProfileImage
|
||||
username={data?.name || "User"}
|
||||
onClick={() => router.push("/user/profile")}
|
||||
textSize={20}
|
||||
boxSize={40}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
),
|
||||
});
|
||||
|
|
@ -367,6 +370,7 @@ export default function PaymentsTabScreen() {
|
|||
setIsEndReached(isAtBottom);
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<ScrollView
|
||||
|
|
@ -379,7 +383,9 @@ export default function PaymentsTabScreen() {
|
|||
<View style={styles.emiCard}>
|
||||
{/* Header */}
|
||||
<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>
|
||||
</View>
|
||||
|
||||
|
|
@ -389,14 +395,14 @@ export default function PaymentsTabScreen() {
|
|||
{/* EMI Details Content */}
|
||||
<View style={styles.cardContent}>
|
||||
<View style={styles.detailRow}>
|
||||
<Text style={styles.detailLabel}>Amount Due</Text>
|
||||
<Text style={styles.detailLabel}>{t("payment.amount-due")}</Text>
|
||||
<Text style={styles.detailValue}>
|
||||
{displayValue(emiDetails?.due_amount, formatCurrency)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.detailRow}>
|
||||
<Text style={styles.detailLabel}>Amount Paid</Text>
|
||||
<Text style={styles.detailLabel}>{t("payment.amount-paid")}</Text>
|
||||
<Text style={styles.detailValue}>
|
||||
{displayValue(
|
||||
emiDetails?.total_amount_paid_in_current_cycle,
|
||||
|
|
@ -406,14 +412,16 @@ export default function PaymentsTabScreen() {
|
|||
</View>
|
||||
|
||||
<View style={styles.detailRow}>
|
||||
<Text style={styles.detailLabel}>Due Date</Text>
|
||||
<Text style={styles.detailLabel}>{t("payment.due-date")}</Text>
|
||||
<Text style={styles.detailValue}>
|
||||
{displayValue(emiDetails?.due_date)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.detailRow}>
|
||||
<Text style={styles.detailLabel}>Payment Status</Text>
|
||||
<Text style={styles.detailLabel}>
|
||||
{t("payment.payment-status")}
|
||||
</Text>
|
||||
{emiDetails?.status ? (
|
||||
<StatusBadge
|
||||
label={emiDetails.status}
|
||||
|
|
@ -436,7 +444,9 @@ export default function PaymentsTabScreen() {
|
|||
onPress={() => router.push("/payments/selectAmount")}
|
||||
disabled={isLoading || !emiDetails}
|
||||
>
|
||||
<Text style={styles.primaryButtonText}>Pay EMI</Text>
|
||||
<Text style={styles.primaryButtonText}>
|
||||
{t("payment.pay-emi")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
|
|
@ -447,13 +457,17 @@ export default function PaymentsTabScreen() {
|
|||
onPress={() => router.push("/payments/myPlan")}
|
||||
disabled={isLoading || !emiDetails}
|
||||
>
|
||||
<Text style={styles.tertiaryButtonText}>View Plan</Text>
|
||||
<Text style={styles.tertiaryButtonText}>
|
||||
{t("payment.view-plan")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Text style={styles.sectionTitle}>Payment History</Text>
|
||||
<Text style={styles.sectionTitle}>
|
||||
{t("payment.payment-history")}
|
||||
</Text>
|
||||
|
||||
<View style={styles.paymentHistoryContainer}>
|
||||
{isHistoryLoading ? (
|
||||
|
|
@ -483,7 +497,9 @@ export default function PaymentsTabScreen() {
|
|||
style={styles.viewAllButton}
|
||||
onPress={handleViewAll}
|
||||
>
|
||||
<Text style={styles.viewAllText}>View all</Text>
|
||||
<Text style={styles.viewAllText}>
|
||||
{t("payment.view-all")}
|
||||
</Text>
|
||||
<Text style={styles.chevron}>›</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
|
|
@ -142,6 +151,8 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
uploadedPhotoUrls.push(uploadedUrl);
|
||||
}
|
||||
|
||||
console.log("IMAGES UPLOADED");
|
||||
|
||||
const payload = {
|
||||
service_type: values.serviceType,
|
||||
issue_types: values.issues,
|
||||
|
|
@ -156,16 +167,23 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
);
|
||||
|
||||
if (!response.data.success) {
|
||||
console.log(response.data?.message || "Submission failed");
|
||||
throw new Error(response.data?.message || "Submission failed");
|
||||
}
|
||||
|
||||
console.log("Submission successful:", response.data);
|
||||
|
||||
actions.resetForm();
|
||||
showSnackbar("Service request submitted successfully", "success");
|
||||
} catch (error) {
|
||||
showSnackbar(
|
||||
`${t("service.service-request-success")}`,
|
||||
"success"
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("Error during submission:", error);
|
||||
showSnackbar("Failed to submit service request", "error");
|
||||
showSnackbar(
|
||||
error.message || `${t("service.something-went-wrong")}`,
|
||||
"error"
|
||||
);
|
||||
} finally {
|
||||
actions.setSubmitting(false);
|
||||
}
|
||||
|
|
@ -184,7 +202,8 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
<View style={styles.formContainer}>
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.label}>
|
||||
Service Type <Text style={styles.required}>*</Text>
|
||||
{t("service.service-type")}{" "}
|
||||
<Text style={styles.required}>*</Text>
|
||||
</Text>
|
||||
<Dropdown
|
||||
style={[
|
||||
|
|
@ -202,7 +221,7 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
maxHeight={200}
|
||||
labelField="label"
|
||||
valueField="value"
|
||||
placeholder={"-Select-"}
|
||||
placeholder={`${t("service.select")}`}
|
||||
value={values.serviceType}
|
||||
onFocus={() => setIsFocus(true)}
|
||||
onBlur={() => setIsFocus(false)}
|
||||
|
|
@ -222,7 +241,7 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
</View>
|
||||
<View style={{ marginTop: 8 }}>
|
||||
<Text style={styles.label}>
|
||||
Issues <Text style={styles.required}>*</Text>
|
||||
{t("service.issue")} <Text style={styles.required}>*</Text>
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.inputBox}
|
||||
|
|
@ -231,7 +250,7 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
<Text style={styles.issueText}>
|
||||
{values.issues.length > 0
|
||||
? values.issues.length + " issues selected"
|
||||
: "Select Issue"}
|
||||
: `${t("service.select-issue")}`}
|
||||
</Text>
|
||||
<ChevronRight />
|
||||
</TouchableOpacity>
|
||||
|
|
@ -242,7 +261,8 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
|
||||
<View style={{ marginTop: 8 }}>
|
||||
<Text style={styles.label}>
|
||||
Select Date and Time <Text style={styles.required}>*</Text>
|
||||
{t("service.select-datetime")}{" "}
|
||||
<Text style={styles.required}>*</Text>
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => showPicker(values.date, setFieldValue)}
|
||||
|
|
@ -261,10 +281,12 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
onPress={() => handlePhotoPick(setFieldValue, values.photos)}
|
||||
>
|
||||
<AddPhoto />
|
||||
<Text style={styles.addPhotoText}>Add photos</Text>
|
||||
<Text style={styles.addPhotoText}>
|
||||
{t("service.add-photos")}{" "}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.helperText}>
|
||||
Supported formats include JPG, JPEG and PNG.
|
||||
{t("service.supported-formats")}
|
||||
</Text>
|
||||
|
||||
{/* Selected Images Preview */}
|
||||
|
|
@ -290,7 +312,7 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
<Text style={styles.error}>{errors.photos}</Text>
|
||||
)}
|
||||
<View style={{ marginTop: 16 }}>
|
||||
<Text style={styles.label}>Comments</Text>
|
||||
<Text style={styles.label}>{t("service.comments")} </Text>
|
||||
<TextInput
|
||||
style={styles.commentInput}
|
||||
multiline
|
||||
|
|
@ -300,7 +322,7 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
value={values.comments}
|
||||
/>
|
||||
<Text style={styles.wordCount}>
|
||||
{values.comments?.length || 0}/100 words
|
||||
{values.comments?.length || 0}/100 {t("service.words")}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
|
|
@ -308,7 +330,7 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
style={styles.submitButton}
|
||||
onPress={handleSubmit as (e?: GestureResponderEvent) => void}
|
||||
>
|
||||
<Text style={styles.submitText}>Submit</Text>
|
||||
<Text style={styles.submitText}>{t("service.submit")} </Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<IssueSelectorModal
|
||||
|
|
@ -317,6 +339,7 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
onSelect={(selectedIssues) => {
|
||||
setFieldValue("issues", selectedIssues);
|
||||
}}
|
||||
initialSelectedValues={values.issues}
|
||||
/>
|
||||
{isSubmitting && <Overlay isUploading={isSubmitting} />}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="My Plan" showBackButton={true} />
|
||||
<ScrollView style={styles.container}>
|
||||
<Header title={t("payment.my-plan")} showBackButton={true} />
|
||||
|
||||
<View style={styles.contentFrame}>
|
||||
{/* Plan Details 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.content}>
|
||||
{/* Plan Type 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}>
|
||||
{displayValue(myPlan?.no_of_emi)}
|
||||
</Text>
|
||||
|
|
@ -68,7 +67,7 @@ const MyPlanScreen: React.FC = () => {
|
|||
|
||||
{/* Total Cost 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}>
|
||||
{formatCurrency(myPlan?.total_amount)}
|
||||
</Text>
|
||||
|
|
@ -76,7 +75,7 @@ const MyPlanScreen: React.FC = () => {
|
|||
|
||||
{/* Down Payment 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}>
|
||||
{formatCurrency(myPlan?.down_payment)}
|
||||
</Text>
|
||||
|
|
@ -84,7 +83,7 @@ const MyPlanScreen: React.FC = () => {
|
|||
|
||||
{/* Total EMI 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}>
|
||||
{formatCurrency(myPlan?.total_emi)}
|
||||
</Text>
|
||||
|
|
@ -94,7 +93,9 @@ const MyPlanScreen: React.FC = () => {
|
|||
|
||||
{/* Total Amount Due 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}>
|
||||
<Text style={styles.dueAmount}>{formatCurrency(dueAmount)}</Text>
|
||||
</View>
|
||||
|
|
@ -103,14 +104,14 @@ const MyPlanScreen: React.FC = () => {
|
|||
<View style={styles.twoColumnContainer}>
|
||||
{/* Monthly EMI Card */}
|
||||
<View style={styles.halfCard}>
|
||||
<Text style={styles.label}>Monthly EMI</Text>
|
||||
<Text style={styles.label}>{t("payment.monthly-emi")}</Text>
|
||||
<Text style={styles.value}>
|
||||
{formatCurrency(myPlan?.emi_amount)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.halfCard}>
|
||||
<Text style={styles.label}>Installments Paid</Text>
|
||||
<Text style={styles.label}>{t("payment.installments-paid")}</Text>
|
||||
<View style={styles.installmentRow}>
|
||||
<Text style={styles.value}>
|
||||
{displayValue(myPlan?.installment_paid)}
|
||||
|
|
@ -123,7 +124,7 @@ const MyPlanScreen: React.FC = () => {
|
|||
</View>
|
||||
|
||||
<ProgressCard
|
||||
title="EMI Paid Till Now"
|
||||
title={t("payment.emi-paid-till-now")}
|
||||
firstText={formatCurrency(myPlan?.current_amount)}
|
||||
secondText={formatCurrency(myPlan?.total_emi)}
|
||||
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
|
||||
style={styles.payButton}
|
||||
onPress={() => router.push("/payments/selectAmount")}
|
||||
>
|
||||
<Text style={styles.payButtonText}>Pay EMI</Text>
|
||||
<Text style={styles.payButtonText}>{t("payment.pay-emi")}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
amount: {
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
color: "#252A34",
|
||||
height: 20,
|
||||
lineHeight: 20,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#F3F5F8",
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<ScrollView style={styles.container}>
|
||||
<Header title="Pay EMI" showBackButton={false} />
|
||||
|
||||
<View style={styles.content}>
|
||||
<View style={styles.qrFrame}>
|
||||
<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}>
|
||||
{formatAmount(paymentOrder?.amount)}
|
||||
</Text>
|
||||
|
|
@ -215,7 +221,9 @@ const UpiPaymentScreen = () => {
|
|||
<View style={styles.buttonsContainer}>
|
||||
<TouchableOpacity onPress={shareQR} style={styles.secondaryButton}>
|
||||
<ShareIcon />
|
||||
<Text style={styles.secondaryButtonText}>Share QR</Text>
|
||||
<Text style={styles.secondaryButtonText}>
|
||||
{t("payment.share-qr")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
|
|
@ -223,29 +231,33 @@ const UpiPaymentScreen = () => {
|
|||
style={styles.secondaryButton}
|
||||
>
|
||||
<DownloadIcon />
|
||||
<Text style={styles.secondaryButtonText}>Download QR</Text>
|
||||
<Text style={styles.secondaryButtonText}>
|
||||
{t("payment.download-qr")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={payUsingUpiApp}
|
||||
style={[styles.primaryButton]}
|
||||
>
|
||||
<Text style={[styles.primaryButtonText]}>Pay using UPI app</Text>
|
||||
<Text style={[styles.primaryButtonText]}>
|
||||
{t("payment.pay-using-upi")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={[styles.confirm, { marginBottom: insets.bottom }]}>
|
||||
<Text style={styles.confirmTitle}>
|
||||
Confirm once your payment is completed.
|
||||
{t("payment.confirm-payment")}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => handlePaymentDone()}
|
||||
style={styles.paymentDone}
|
||||
>
|
||||
<Text style={styles.doneText}>Payment Done</Text>
|
||||
<Text style={styles.doneText}>{t("payment.payment-done")}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
behavior={"height"}
|
||||
// Improved keyboard offset
|
||||
keyboardVerticalOffset={Platform.OS === "ios" ? 88 : 0}
|
||||
// keyboardVerticalOffset={Platform.OS === "ios" ? 88 : 0}
|
||||
>
|
||||
<Header title="Select Amount" showBackButton={true} />
|
||||
|
||||
|
|
@ -224,7 +261,9 @@ const SelectAmountScreen = () => {
|
|||
<View style={styles.radioInner} />
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.radioLabel}>Pay amount due</Text>
|
||||
<Text style={styles.radioLabel}>
|
||||
{t("payment.pay-amount-due")}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.amountText}>
|
||||
₹{dueAmount?.toFixed(2)}
|
||||
|
|
@ -255,7 +294,9 @@ const SelectAmountScreen = () => {
|
|||
<View style={styles.radioInner} />
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.radioLabel}>Enter custom amount</Text>
|
||||
<Text style={styles.radioLabel}>
|
||||
{t("payment.enter-custom-amount")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.inputContainer}>
|
||||
|
|
@ -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
|
||||
}`}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -324,14 +357,19 @@ const SelectAmountScreen = () => {
|
|||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.buttonContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.buttonContainer,
|
||||
!keyboardVisible && { marginBottom: insets.bottom },
|
||||
]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.payButton,
|
||||
!isPayButtonEnabled() && styles.disabledButton,
|
||||
!isButtonEnabled && styles.disabledButton,
|
||||
]}
|
||||
onPress={() => handleSubmit()}
|
||||
disabled={!isPayButtonEnabled()}
|
||||
disabled={!isButtonEnabled}
|
||||
>
|
||||
{getPaymentAmount() < payments.MIN_AMOUNT ? (
|
||||
<Text style={styles.payButtonText}>Select Amount</Text>
|
||||
|
|
|
|||
|
|
@ -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<MyVehicleScreenProps> = ({ navigation }) => {
|
|||
);
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Header title="My Vehicle" showBackButton={true} />
|
||||
<Header title={t("profile.my-vehicle")} showBackButton={true} />
|
||||
<View style={styles.content}>
|
||||
<View style={styles.itemContainer}>
|
||||
{/* OEM - Model */}
|
||||
<View style={styles.item}>
|
||||
<Text style={styles.label}>OEM - Model</Text>
|
||||
<Text style={styles.label}>{t("profile.oem-model")}</Text>
|
||||
<Text style={styles.value}>
|
||||
{vehicle?.model ? `Yatri - ${vehicle.model}` : "--"}
|
||||
</Text>
|
||||
|
|
@ -76,7 +78,7 @@ const MyVehicleScreen: React.FC<MyVehicleScreenProps> = ({ navigation }) => {
|
|||
|
||||
{/* Chassis Number */}
|
||||
<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>
|
||||
</View>
|
||||
|
||||
|
|
@ -84,7 +86,7 @@ const MyVehicleScreen: React.FC<MyVehicleScreenProps> = ({ navigation }) => {
|
|||
|
||||
{/* Vehicle ID */}
|
||||
<View style={styles.item}>
|
||||
<Text style={styles.label}>Vehicle ID</Text>
|
||||
<Text style={styles.label}>{t("profile.vehicle-id")}</Text>
|
||||
<Text style={styles.value}>
|
||||
{vehicle?.vehicle_id ? vehicle.vehicle_id.toString() : "--"}
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View style={styles.inner}>
|
||||
<Formik
|
||||
initialValues={{ name: originalName }}
|
||||
validationSchema={nameSchema}
|
||||
onSubmit={handleSave}
|
||||
enableReinitialize
|
||||
>
|
||||
{({
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
values,
|
||||
touched,
|
||||
errors,
|
||||
}) => {
|
||||
const hasChanged = values.name !== originalName;
|
||||
const hasError = !!errors.name;
|
||||
<>
|
||||
<Header title={t("profile.edit-name")} showBackButton={true} />
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View style={styles.inner}>
|
||||
<Formik
|
||||
initialValues={{ name: originalName }}
|
||||
validationSchema={nameSchema}
|
||||
onSubmit={handleSave}
|
||||
enableReinitialize
|
||||
>
|
||||
{({
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
values,
|
||||
touched,
|
||||
errors,
|
||||
}) => {
|
||||
const hasChanged = values.name !== originalName;
|
||||
const hasError = !!errors.name;
|
||||
|
||||
return (
|
||||
<View style={styles.formContainer}>
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.label}>Enter Name</Text>
|
||||
<TextInput
|
||||
return (
|
||||
<View style={styles.formContainer}>
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.label}>
|
||||
{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={[
|
||||
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 && (
|
||||
<Text style={styles.error}>{errors.name}</Text>
|
||||
)}
|
||||
disabled={!hasChanged || hasError}
|
||||
>
|
||||
<Text style={styles.buttonText}>{t("profile.save")}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={handleSubmit as unknown as () => void}
|
||||
style={[
|
||||
styles.button,
|
||||
hasChanged && !hasError
|
||||
? styles.buttonEnabled
|
||||
: styles.buttonDisabled,
|
||||
{ marginBottom: insets.bottom },
|
||||
]}
|
||||
disabled={!hasChanged || hasError}
|
||||
>
|
||||
<Text style={styles.buttonText}>Save</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAvoidingView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +137,6 @@ const styles = StyleSheet.create({
|
|||
inner: {
|
||||
flex: 1,
|
||||
justifyContent: "space-between",
|
||||
paddingVertical: 24,
|
||||
backgroundColor: "#F3F5F8",
|
||||
},
|
||||
formContainer: {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<Header title="My Account" showBackButton={true} />
|
||||
<Header title={t("profile.my-account")} showBackButton={true} />
|
||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||
<View style={styles.avatarContainer}>
|
||||
<Image
|
||||
|
|
@ -114,7 +117,7 @@ export default function ProfileScreen() {
|
|||
<View style={styles.card}>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.textGroup}>
|
||||
<Text style={styles.label}>Name</Text>
|
||||
<Text style={styles.label}>{t("profile.name")}</Text>
|
||||
<Text style={styles.value}>{userName}</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
|
|
@ -129,22 +132,24 @@ export default function ProfileScreen() {
|
|||
<View style={styles.divider} />
|
||||
<View style={styles.row}>
|
||||
<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>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.card}>
|
||||
{menuItem("My Vehicle", () => router.push("/user/MyVechicle"))}
|
||||
{menuItem(`${t("profile.my-vehicle")}`, () =>
|
||||
router.push("/user/MyVechicle")
|
||||
)}
|
||||
<View style={styles.divider} />
|
||||
{menuItem("Language", () => toggleLanguageModal())}
|
||||
{menuItem(`${t("profile.language")}`, () => toggleLanguageModal())}
|
||||
</View>
|
||||
|
||||
<View style={styles.card}>
|
||||
{menuItem("About App")}
|
||||
{menuItem(`${t("profile.about-app")}`)}
|
||||
<View style={styles.divider} />
|
||||
{menuItem("Logout", handleLogout)}
|
||||
{menuItem(`${t("profile.logout")}`, handleLogout)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
<LanguageModal
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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({
|
|||
<BottomSheetModal
|
||||
visible={visible}
|
||||
onClose={onClose}
|
||||
heading="Select Language"
|
||||
heading={t("profile.select-language")}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { View, Text, StyleSheet, Pressable } from "react-native";
|
||||
import ChevronRight from "../../assets/icons/chevron_rightside.svg";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
warrantyStartDate: string;
|
||||
|
|
@ -29,17 +30,31 @@ const BatteryWarrantyCard: React.FC<Props> = ({
|
|||
(remaining % (30.44 * 24 * 60 * 60 * 1000)) / (24 * 60 * 60 * 1000)
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
<View style={styles.topRow}>
|
||||
<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}>
|
||||
{`${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(", ");
|
||||
})()}
|
||||
</Text>
|
||||
</View>
|
||||
<Pressable style={styles.iconButton}>
|
||||
|
|
|
|||
|
|
@ -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<BatteryStatusProps> = ({ 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<BatteryStatusProps> = ({ status, style }) => {
|
|||
|
||||
const config = getStatusConfig();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
|
|
@ -60,7 +63,9 @@ const BatteryStatus: React.FC<BatteryStatusProps> = ({ status, style }) => {
|
|||
},
|
||||
]}
|
||||
>
|
||||
{config.text}
|
||||
{status !== null && status !== undefined
|
||||
? t(`home.${config.text}`)
|
||||
: "---"}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<PaymentDueCardProps> = ({
|
|||
amount,
|
||||
onPress,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.leftSection}>
|
||||
|
|
@ -40,7 +42,7 @@ const PaymentDueCard: React.FC<PaymentDueCardProps> = ({
|
|||
onPress={onPress}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Text style={styles.buttonText}>Pay Now</Text>
|
||||
<Text style={styles.buttonText}>{t("home.pay-now")}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<string[]>([]);
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>(
|
||||
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 (
|
||||
<Modal visible={visible} animationType="slide">
|
||||
<View style={styles.container}>
|
||||
{/* Header */}
|
||||
<View style={styles.headerBar}>
|
||||
<Text>Select Issue</Text>
|
||||
<TouchableOpacity onPress={onClose}>
|
||||
<TouchableOpacity onPress={handleClose}>
|
||||
<CloseIcon />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
|
@ -67,8 +96,15 @@ export default function IssueSelectorModal({
|
|||
<Text
|
||||
style={styles.counterText}
|
||||
>{`${selectedValues.length}/23 Selected`}</Text>
|
||||
<TouchableOpacity onPress={clearSelection}>
|
||||
<Text style={styles.clearText}>Clear</Text>
|
||||
<TouchableOpacity onPress={clearSelection} disabled={!hasSelection}>
|
||||
<Text
|
||||
style={[
|
||||
styles.clearText,
|
||||
!hasSelection && styles.clearTextDisabled,
|
||||
]}
|
||||
>
|
||||
Clear
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
|
|
@ -98,15 +134,26 @@ export default function IssueSelectorModal({
|
|||
))}
|
||||
</ScrollView>
|
||||
|
||||
<View>
|
||||
<View style={styles.buttonsContainer}>
|
||||
<TouchableOpacity style={styles.cancelButton} onPress={onClose}>
|
||||
<Text style={styles.cancelText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={styles.doneButton}
|
||||
onPress={() => {
|
||||
onSelect(selectedValues);
|
||||
onClose();
|
||||
}}
|
||||
style={[
|
||||
styles.doneButton,
|
||||
!hasSelection && styles.doneButtonDisabled,
|
||||
]}
|
||||
onPress={handleSelect}
|
||||
disabled={!hasSelection}
|
||||
>
|
||||
<Text style={styles.doneText}>Done</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.doneText,
|
||||
!hasSelection && styles.doneTextDisabled,
|
||||
]}
|
||||
>
|
||||
Select
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -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",
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": "मेरी बैटरी और वारंटी",
|
||||
|
|
|
|||
|
|
@ -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<MyPlan | null>) {
|
||||
state.myPlan = action.payload;
|
||||
},
|
||||
setAdvanceBalance(state, action: PayloadAction<number | null>) {
|
||||
state.advance_balance = action.payload;
|
||||
},
|
||||
setPaymentOrder(state, action: PayloadAction<PaymentOrder | null>) {
|
||||
state.paymentOrder = action.payload;
|
||||
},
|
||||
|
|
@ -61,6 +66,7 @@ export const {
|
|||
setPaymentOrder,
|
||||
clearPaymentOrder,
|
||||
updatePaymentStatus,
|
||||
setAdvanceBalance,
|
||||
} = emiSlice.actions;
|
||||
|
||||
export default emiSlice.reducer;
|
||||
|
|
|
|||
Loading…
Reference in New Issue