Compare commits

...

2 Commits

Author SHA1 Message Date
vinay kumar 45d2f6a481 Fix UI issues 2025-08-20 11:54:13 +05:30
vinay kumar b4e9521630 add emi notification 2025-08-19 10:00:31 +05:30
23 changed files with 512 additions and 248 deletions

View File

@ -39,6 +39,7 @@ 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"; import { Image } from "expo-image";
import EMINotification from "@/components/Payments/EmiNotification";
export default function HomeScreen() { export default function HomeScreen() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -231,7 +232,7 @@ export default function HomeScreen() {
? "warning" ? "warning"
: "danger" : "danger"
} }
message={daysLeftToPayEmiText} message={`${daysLeftToPayEmi} ${t("home.days-left-to-pay-emi")}`}
subMessage={t("home.pay-now")} subMessage={t("home.pay-now")}
redirectPath="/(tabs)/payments" redirectPath="/(tabs)/payments"
/> />
@ -257,7 +258,7 @@ export default function HomeScreen() {
unit={t("home.km")} unit={t("home.km")}
/> />
</View> </View>
{due_amount && ( {due_amount && due_amount > 0 ? (
<PaymentDueCard <PaymentDueCard
label={t("home.payment-due")} label={t("home.payment-due")}
amount={due_amount} amount={due_amount}
@ -265,12 +266,21 @@ export default function HomeScreen() {
router.push("/payments/selectAmount"); router.push("/payments/selectAmount");
}} }}
/> />
)} ) : due_amount == 0 ? (
<EMINotification
message={`${t("home.payment-complete")}`}
actionText={`${t("home.view-details")}`}
onActionPress={() => router.push("/(tabs)/payments")}
/>
) : null}
<View style={styles.map}> <View style={styles.map}>
{loading ? ( {loading ? (
<View style={styles.errorContainer}> <View style={styles.errorContainer}>
<LocationOff /> <LocationOff />
<Text style={styles.errorText}>Fetching Location...</Text> <Text style={styles.errorText}>
{t("home.fetching-location")}
</Text>
</View> </View>
) : lat != null && lon != null && !(lat == 0 && lon == 0) ? ( ) : lat != null && lon != null && !(lat == 0 && lon == 0) ? (
<> <>

View File

@ -49,19 +49,23 @@ const BatteryDetails = () => {
(remaining % (30.44 * 24 * 60 * 60 * 1000)) / (24 * 60 * 60 * 1000) (remaining % (30.44 * 24 * 60 * 60 * 1000)) / (24 * 60 * 60 * 1000)
); );
const parts: string[] = []; durationText = (() => {
const parts: string[] = [];
if (yearsLeft > 0) { if (yearsLeft > 0) {
parts.push(`${yearsLeft} year${yearsLeft !== 1 ? "s" : ""}`); parts.push(`${yearsLeft} ${t("home.year")}`);
} }
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(", "); if (monthsLeft > 0) {
parts.push(`${monthsLeft} ${t("home.month")}`);
}
if (daysLeft > 0 || parts.length === 0) {
parts.push(`${daysLeft} ${t("home.day")}`);
}
return parts.join(", ");
})();
} }
const formatDate = (date?: Date | null): string => const formatDate = (date?: Date | null): string =>

View File

@ -169,7 +169,9 @@ export default function PaymentsTabScreen() {
} catch (err) { } catch (err) {
console.error("Error fetching EMI details:", err); console.error("Error fetching EMI details:", err);
const errorMessage = const errorMessage =
err instanceof Error ? err.message : "Something went wrong"; err instanceof Error
? err.message
: `${t("service.something-went-wrong")}`;
showSnackbar(errorMessage, "error"); showSnackbar(errorMessage, "error");
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@ -255,7 +257,7 @@ export default function PaymentsTabScreen() {
</Pressable> </Pressable>
<View> <View>
<ProfileImage <ProfileImage
username={data?.name || "User"} username={data?.name || "--"}
onClick={() => router.push("/user/profile")} onClick={() => router.push("/user/profile")}
textSize={20} textSize={20}
boxSize={40} boxSize={40}
@ -268,6 +270,7 @@ export default function PaymentsTabScreen() {
// Format currency // Format currency
const formatCurrency = (amount: number) => { const formatCurrency = (amount: number) => {
console.log(amount, "amount format current");
return `${amount.toLocaleString()}`; return `${amount.toLocaleString()}`;
}; };
@ -337,7 +340,9 @@ export default function PaymentsTabScreen() {
} catch (err) { } catch (err) {
console.error("Error fetching payment history:", err); console.error("Error fetching payment history:", err);
const errorMessage = const errorMessage =
err instanceof Error ? err.message : "Something went wrong"; err instanceof Error
? err.message
: `${t("service.something-went-wrong")}`;
showSnackbar(errorMessage, "error"); showSnackbar(errorMessage, "error");
} finally { } finally {
if (isLoadMore) { if (isLoadMore) {

View File

@ -26,6 +26,7 @@ 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"; import { useTranslation } from "react-i18next";
import CalendarIcon from "@/assets/icons/calendar.svg";
interface FormValues { interface FormValues {
serviceType: string | null; serviceType: string | null;
@ -173,17 +174,14 @@ export default function ServiceFormScreen(): JSX.Element {
console.log("Submission successful:", response.data); console.log("Submission successful:", response.data);
actions.resetForm();
showSnackbar( showSnackbar(
`${t("service.service-request-success")}`, `${t("service.service-request-success")}`,
"success" "success"
); );
actions.resetForm();
} catch (error: any) { } catch (error: any) {
console.error("Error during submission:", error); console.error("Error during submission:", error);
showSnackbar( showSnackbar(`${t("service.something-went-wrong")}`, "error");
error.message || `${t("service.something-went-wrong")}`,
"error"
);
} finally { } finally {
actions.setSubmitting(false); actions.setSubmitting(false);
} }
@ -269,8 +267,11 @@ export default function ServiceFormScreen(): JSX.Element {
style={styles.inputBoxDate} style={styles.inputBoxDate}
> >
<Text style={styles.dateText}> <Text style={styles.dateText}>
{values.date && values.date.toLocaleString()} {values.date
? values.date.toLocaleString()
: `${t("service.select")}`}
</Text> </Text>
<CalendarIcon width={20} height={20} />
</TouchableOpacity> </TouchableOpacity>
{touched.date && errors.date && ( {touched.date && errors.date && (
<Text style={styles.error}>{`${errors.date}`}</Text> <Text style={styles.error}>{`${errors.date}`}</Text>

View File

@ -114,9 +114,11 @@ export default function WelcomeScreen() {
<View style={styles.bottomSection}> <View style={styles.bottomSection}>
<View style={styles.contactContainer}> <View style={styles.contactContainer}>
<View style={{ flexDirection: "row" }}> <View style={{ flexDirection: "row" }}>
<Text>For any queries, </Text> <Text>{t("onboarding.for-any-queries")}</Text>
<TouchableOpacity onPress={makePhoneCall}> <TouchableOpacity onPress={makePhoneCall}>
<Text style={styles.link}>contact us.</Text> <Text style={styles.link}>
{t("onboarding.contact-us")}
</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>

View File

@ -8,6 +8,7 @@ import CheckCircle from "@/assets/icons/check_circle.svg";
import Pending from "@/assets/icons/pending.svg"; import Pending from "@/assets/icons/pending.svg";
import Failed from "@/assets/icons/cancel.svg"; import Failed from "@/assets/icons/cancel.svg";
import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTranslation } from "react-i18next";
const PaymentConfirmationScreen = () => { const PaymentConfirmationScreen = () => {
const router = useRouter(); const router = useRouter();
@ -86,10 +87,10 @@ const PaymentConfirmationScreen = () => {
const displayValue = (value: any) => { const displayValue = (value: any) => {
return value ? String(value) : "--"; return value ? String(value) : "--";
}; };
const { t } = useTranslation();
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Header title="Payment Status" /> <Header title={`${t("payment.payment-status")}`} />
<View style={styles.contentFrame}> <View style={styles.contentFrame}>
<View style={styles.qrFrame}> <View style={styles.qrFrame}>
@ -112,37 +113,49 @@ const PaymentConfirmationScreen = () => {
<View style={styles.divider} /> <View style={styles.divider} />
<View style={styles.transactionContainer}> <View style={styles.transactionContainer}>
<Text style={styles.sectionHeader}>Transaction Details</Text> <Text style={styles.sectionHeader}>{`${t(
"payment.transaction-details"
)}`}</Text>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Payment mode</Text> <Text style={styles.detailLabel}>{`${t(
"payment.payment-mode"
)}`}</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{displayValue(paymentOrder?.payment_mode?.[0])} {displayValue(paymentOrder?.payment_mode?.[0])}
</Text> </Text>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Paid to</Text> <Text style={styles.detailLabel}>{`${t(
"payment.paid-to"
)}`}</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{displayValue(paymentOrder?.upi_handle)} {displayValue(paymentOrder?.upi_handle)}
</Text> </Text>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Paid by</Text> <Text style={styles.detailLabel}>{`${t(
"payment.paid-by"
)}`}</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{displayValue(paymentOrder?.paid_by_upi_handle)} {displayValue(paymentOrder?.paid_by_upi_handle)}
</Text> </Text>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Order ID</Text> <Text style={styles.detailLabel}>{`${t(
"payment.order-id"
)}`}</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{displayValue(paymentOrder?.order_id)} {displayValue(paymentOrder?.order_id)}
</Text> </Text>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={styles.detailLabel}>Transaction ID</Text> <Text style={styles.detailLabel}>{`${t(
"payment.transaction-id"
)}`}</Text>
<Text style={styles.detailValue}> <Text style={styles.detailValue}>
{displayValue( {displayValue(
paymentOrder?.transaction_id || paymentOrder?.transaction_id ||

View File

@ -18,6 +18,7 @@ import SuccessIcon from "@/assets/icons/check_circle.svg";
import FailureIcon from "@/assets/icons/cancel.svg"; import FailureIcon from "@/assets/icons/cancel.svg";
import PendingIcon from "@/assets/icons/pending.svg"; import PendingIcon from "@/assets/icons/pending.svg";
import Header from "@/components/common/Header"; import Header from "@/components/common/Header";
import { useTranslation } from "react-i18next";
interface TransactionDetailData { interface TransactionDetailData {
id: number; id: number;
@ -61,6 +62,8 @@ export default function TransactionDetailScreen() {
fetchTransactionDetail(); fetchTransactionDetail();
}, [paymentId]); }, [paymentId]);
const { t } = useTranslation();
const fetchTransactionDetail = async () => { const fetchTransactionDetail = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
@ -78,7 +81,9 @@ export default function TransactionDetailScreen() {
} catch (err) { } catch (err) {
console.error("Error fetching transaction details:", err); console.error("Error fetching transaction details:", err);
const errorMessage = const errorMessage =
err instanceof Error ? err.message : "Something went wrong"; err instanceof Error
? err.message
: `${t("service.something-went-wrong")}`;
showSnackbar(errorMessage, "error"); showSnackbar(errorMessage, "error");
router.back(); router.back();
} finally { } finally {
@ -132,7 +137,10 @@ export default function TransactionDetailScreen() {
return ( return (
<> <>
<Header title="Transaction Details" showBackButton={true} /> <Header
title={`${t("payment.transaction-details")}`}
showBackButton={true}
/>
{isLoading ? ( {isLoading ? (
<View style={styles.loadingContainer}> <View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#00BE88" /> <ActivityIndicator size="large" color="#00BE88" />
@ -160,30 +168,32 @@ export default function TransactionDetailScreen() {
<View style={styles.divider} /> <View style={styles.divider} />
<View style={styles.detailsCard}> <View style={styles.detailsCard}>
<Text style={styles.detailsTitle}>Transaction Details</Text> <Text style={styles.detailsTitle}>
{`${t("payment.transaction-details")}`}
</Text>
<DetailRow <DetailRow
label="Payment mode" label={`${t("payment.payment-mode")}`}
value={transactionData.payment_mode.join(", ") || "--"} value={transactionData.payment_mode.join(", ") || "--"}
/> />
<DetailRow <DetailRow
label="Paid to" label={`${t("payment.paid-to")}`}
value={transactionData.upi_handle || "--"} value={transactionData.upi_handle || "--"}
/> />
<DetailRow <DetailRow
label="Paid by" label={`${t("payment.paid-by")}`}
value={transactionData.paid_by_upi_handle || "--"} value={transactionData.paid_by_upi_handle || "--"}
/> />
<DetailRow <DetailRow
label="Order ID" label={`${t("payment.order-id")}`}
value={transactionData.order_id || "--"} value={transactionData.order_id || "--"}
/> />
<DetailRow <DetailRow
label="Transaction ID" label={`${t("payment.transaction-id")}`}
value={transactionData.transaction_order_id || "--"} value={transactionData.transaction_order_id || "--"}
/> />

View File

@ -4,12 +4,12 @@ import {
Text, Text,
TouchableOpacity, TouchableOpacity,
StyleSheet, StyleSheet,
SafeAreaView,
Alert, Alert,
Share, Share,
Linking, Linking,
BackHandler, BackHandler,
ScrollView, ScrollView,
Dimensions,
} 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";
@ -22,79 +22,85 @@ import Header from "@/components/common/Header";
import ShareIcon from "@/assets/icons/share.svg"; import ShareIcon from "@/assets/icons/share.svg";
import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useSnackbar } from "@/contexts/Snackbar"; import { useSnackbar } from "@/contexts/Snackbar";
import DownloadIcon from "@/assets/icons/download.svg"; import DownloadIcon from "@/assets/icons/download.svg";
import { payments } from "@/constants/config"; import { payments } from "@/constants/config";
import { useRouter } from "expo-router"; import { useFocusEffect, useRouter } from "expo-router";
import { useSocket } from "@/contexts/SocketContext"; import { useSocket } from "@/contexts/SocketContext";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const { height: screenHeight } = Dimensions.get("window");
const UpiPaymentScreen = () => { const UpiPaymentScreen = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
//paymentorder.amount is undefined
console.log("inside payemi ✅✅");
useEffect(() => {
console.log("inside pay emi useeffect 🔥🔥🔥🔥🔥");
}, []);
const paymentOrder = useSelector( const paymentOrder = useSelector(
(state: RootState) => state.payments.paymentOrder (state: RootState) => state.payments.paymentOrder
); );
const router = useRouter(); const router = useRouter();
const { onPaymentConfirmation, offPaymentConfirmation, disconnect } = const { onPaymentConfirmation, offPaymentConfirmation, disconnect } =
useSocket(); useSocket();
const { showSnackbar } = useSnackbar(); const { showSnackbar } = useSnackbar();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
useEffect(() => { useFocusEffect(
let backPressCount = 0; React.useCallback(() => {
let backPressTimer: NodeJS.Timeout | null = null; let backPressCount = 0;
const handlePaymentConfirmation = (data: any) => { let backPressTimer: NodeJS.Timeout | null = null;
console.log("Payment confirmation received:", data); const backAction = () => {
if (backPressCount === 0) {
dispatch( backPressCount++;
setPaymentOrder({ showSnackbar("Press back again to cancel payment", "info");
...paymentOrder, backPressTimer = setTimeout(() => {
...data, backPressCount = 0;
}) }, 2000);
return true;
} else {
if (backPressTimer) clearTimeout(backPressTimer);
offPaymentConfirmation();
disconnect();
router.back();
return true;
}
};
const backHandler = BackHandler.addEventListener(
"hardwareBackPress",
backAction
); );
// ✅ Cleanup when screen loses focus
offPaymentConfirmation(); return () => {
disconnect();
router.replace("/payments/Confirmation");
};
onPaymentConfirmation(handlePaymentConfirmation);
const backAction = () => {
if (backPressCount === 0) {
backPressCount++;
console.log("Press back again to cancel payment");
showSnackbar("Press back again to cancel payment", "success");
backPressTimer = setTimeout(() => {
backPressCount = 0;
}, 2000);
return true;
} else {
if (backPressTimer) clearTimeout(backPressTimer); if (backPressTimer) clearTimeout(backPressTimer);
backHandler.remove();
};
}, [offPaymentConfirmation, disconnect, router])
);
useFocusEffect(
React.useCallback(() => {
const handlePaymentConfirmation = (data: any) => {
dispatch(setPaymentOrder({ ...paymentOrder, ...data }));
offPaymentConfirmation(); offPaymentConfirmation();
disconnect(); disconnect();
router.back(); router.replace("/payments/Confirmation");
return true; };
} onPaymentConfirmation(handlePaymentConfirmation);
}; return () => {
offPaymentConfirmation();
const backHandler = BackHandler.addEventListener( };
"hardwareBackPress", }, [
backAction paymentOrder,
); onPaymentConfirmation,
return () => { offPaymentConfirmation,
offPaymentConfirmation(); disconnect,
backHandler.remove(); router,
}; ])
}, [onPaymentConfirmation, offPaymentConfirmation, router]); );
const formatAmount = (amount: number): string => { const formatAmount = (amount: number): string => {
if (amount == null || amount == undefined) return `${0}`;
return `${amount.toLocaleString("en-IN")}`; return `${amount.toLocaleString("en-IN")}`;
}; };
@ -104,14 +110,8 @@ const UpiPaymentScreen = () => {
return currentDate > expiryDate; return currentDate > expiryDate;
}; };
const getExpiryTime = (): string => {
const expiryDate = new Date(paymentOrder.expiry_date);
return expiryDate.toLocaleString("en-IN");
};
const getUpiUrl = (): string => { const getUpiUrl = (): string => {
const upiString = paymentOrder.payment_link; const upiString = paymentOrder.payment_link;
const upiMatch = upiString.match(/upi_string=([^&]+)/); const upiMatch = upiString.match(/upi_string=([^&]+)/);
if (upiMatch) { if (upiMatch) {
return decodeURIComponent(upiMatch[1]); return decodeURIComponent(upiMatch[1]);
@ -125,14 +125,10 @@ const UpiPaymentScreen = () => {
showSnackbar(payments.LINK_EXPIRED, "error"); showSnackbar(payments.LINK_EXPIRED, "error");
return; return;
} }
const upiUrl = getUpiUrl(); const upiUrl = getUpiUrl();
console.log("Opening UPI URL:", upiUrl); console.log("Opening UPI URL:", upiUrl);
dispatch(updatePaymentStatus("processing")); dispatch(updatePaymentStatus("processing"));
const canOpenUrl = await Linking.canOpenURL(upiUrl); const canOpenUrl = await Linking.canOpenURL(upiUrl);
if (canOpenUrl) { if (canOpenUrl) {
await Linking.openURL(upiUrl); await Linking.openURL(upiUrl);
} else { } else {
@ -169,14 +165,12 @@ const UpiPaymentScreen = () => {
showSnackbar("Please grant permission to save images", "error"); showSnackbar("Please grant permission to save images", "error");
return; return;
} }
const fileUri = const fileUri =
FileSystem.documentDirectory + `qr-${paymentOrder.order_id}.png`; FileSystem.documentDirectory + `qr-${paymentOrder.order_id}.png`;
const downloadResult = await FileSystem.downloadAsync( const downloadResult = await FileSystem.downloadAsync(
paymentOrder.qr_code_url, paymentOrder.qr_code_url,
fileUri fileUri
); );
if (downloadResult.status === 200) { if (downloadResult.status === 200) {
const asset = await MediaLibrary.createAssetAsync(downloadResult.uri); const asset = await MediaLibrary.createAssetAsync(downloadResult.uri);
await MediaLibrary.createAlbumAsync("Payment QR Codes", asset, false); await MediaLibrary.createAlbumAsync("Payment QR Codes", asset, false);
@ -190,78 +184,114 @@ const UpiPaymentScreen = () => {
}; };
function handlePaymentDone() { function handlePaymentDone() {
router.push("/(tabs)/payments"); router.navigate("/(tabs)/payments");
} }
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<ScrollView style={styles.container}> <View style={styles.container}>
<Header title="Pay EMI" showBackButton={false} /> <Header title={t("payment.pay-emi")} showBackButton={false} />
<ScrollView
<View style={styles.content}> style={styles.scrollView}
<View style={styles.qrFrame}> contentContainerStyle={styles.scrollContent}
<View style={styles.amountSection}> showsVerticalScrollIndicator={false}
<Text style={styles.amountLabel}> >
{t("payment.amount-to-be-paid")} <View style={styles.content}>
</Text> <View style={styles.qrFrame}>
<Text style={styles.amount}> <View style={styles.amountSection}>
{formatAmount(paymentOrder?.amount)} <Text style={styles.amountLabel}>
</Text> {t("payment.amount-to-be-paid")}
</View>
<View style={styles.qrCodeContainer}>
<Image
source={{ uri: paymentOrder?.qr_code_url }}
style={styles.qrCode}
contentFit="contain"
/>
</View>
<View style={styles.buttonsContainer}>
<TouchableOpacity onPress={shareQR} style={styles.secondaryButton}>
<ShareIcon />
<Text style={styles.secondaryButtonText}>
{t("payment.share-qr")}
</Text> </Text>
</TouchableOpacity> <Text style={styles.amount}>
{formatAmount(paymentOrder?.amount)}
</Text>
</View>
<View style={styles.qrCodeContainer}>
<Image
source={{ uri: paymentOrder?.qr_code_url }}
style={styles.qrCode}
contentFit="contain"
/>
</View>
<View style={styles.buttonsContainer}>
<TouchableOpacity
onPress={shareQR}
style={styles.secondaryButton}
>
<ShareIcon />
<Text style={styles.secondaryButtonText}>
{t("payment.share-qr")}
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={downloadQR}
style={styles.secondaryButton}
>
<DownloadIcon />
<Text style={styles.secondaryButtonText}>
{t("payment.download-qr")}
</Text>
</TouchableOpacity>
</View>
<TouchableOpacity <TouchableOpacity
onPress={downloadQR} onPress={payUsingUpiApp}
style={styles.secondaryButton} style={[styles.primaryButton]}
> >
<DownloadIcon /> <Text style={[styles.primaryButtonText]}>
<Text style={styles.secondaryButtonText}> {t("payment.pay-using-upi")}
{t("payment.download-qr")}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<TouchableOpacity
onPress={payUsingUpiApp} <View style={[styles.confirm, { marginBottom: insets.bottom }]}>
style={[styles.primaryButton]} <Text style={styles.confirmTitle}>
> {t("payment.confirm-payment")}
<Text style={[styles.primaryButtonText]}>
{t("payment.pay-using-upi")}
</Text> </Text>
</TouchableOpacity> <TouchableOpacity
onPress={() => handlePaymentDone()}
style={styles.paymentDone}
>
<Text style={styles.doneText}>{t("payment.payment-done")}</Text>
</TouchableOpacity>
</View>
</View> </View>
<View style={[styles.confirm, { marginBottom: insets.bottom }]}> </ScrollView>
<Text style={styles.confirmTitle}> </View>
{t("payment.confirm-payment")}
</Text>
<TouchableOpacity
onPress={() => handlePaymentDone()}
style={styles.paymentDone}
>
<Text style={styles.doneText}>{t("payment.payment-done")}</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
); );
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#F3F5F8",
},
scrollView: {
flex: 1,
},
scrollContent: {
flexGrow: 1,
},
content: {
flex: 1,
paddingHorizontal: 16,
paddingVertical: 16,
justifyContent: "space-between",
minHeight: screenHeight * 0.8, // Ensures minimum height for space-between to work
},
qrFrame: {
backgroundColor: "#FCFCFC",
borderRadius: 8,
padding: 16,
alignItems: "center",
},
confirm: {
padding: 16,
flexDirection: "column",
backgroundColor: "#FCFCFC",
gap: 16,
borderRadius: 8,
},
doneText: { doneText: {
textAlign: "center", textAlign: "center",
fontWeight: "500", fontWeight: "500",
@ -272,6 +302,7 @@ const styles = StyleSheet.create({
padding: 16, padding: 16,
borderWidth: 1, borderWidth: 1,
borderColor: "#DCE1E9", borderColor: "#DCE1E9",
borderRadius: 4,
}, },
confirmTitle: { confirmTitle: {
fontWeight: "400", fontWeight: "400",
@ -279,16 +310,6 @@ const styles = StyleSheet.create({
lineHeight: 14, lineHeight: 14,
color: "#252A34", color: "#252A34",
}, },
confirm: {
padding: 16,
flexDirection: "column",
backgroundColor: "#FCFCFC",
gap: 16,
},
container: {
flex: 1,
backgroundColor: "#F3F5F8",
},
loadingContainer: { loadingContainer: {
flex: 1, flex: 1,
justifyContent: "center", justifyContent: "center",
@ -315,19 +336,6 @@ const styles = StyleSheet.create({
fontWeight: "600", fontWeight: "600",
color: "#253342", color: "#253342",
}, },
content: {
flex: 1,
paddingHorizontal: 16,
paddingBottom: 16,
justifyContent: "space-between",
},
qrFrame: {
backgroundColor: "#FCFCFC",
borderRadius: 8,
padding: 16,
alignItems: "center",
marginTop: 16,
},
amountSection: { amountSection: {
alignItems: "center", alignItems: "center",
marginBottom: 16, marginBottom: 16,

View File

@ -90,6 +90,8 @@ const SelectAmountScreen = () => {
}; };
}, []); }, []);
const { t } = useTranslation();
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
console.log( console.log(
@ -146,7 +148,7 @@ const SelectAmountScreen = () => {
} }
} catch (err) { } catch (err) {
console.error(err, "Error in creating order."); console.error(err, "Error in creating order.");
showSnackbar("Something went wrong.", "error"); showSnackbar(`${t("service.something-went-wrong")}`, "error");
} finally { } finally {
setIsFetching(false); setIsFetching(false);
} }
@ -204,7 +206,7 @@ const SelectAmountScreen = () => {
return values.customAmount ? parseFloat(values.customAmount) : 0; return values.customAmount ? parseFloat(values.customAmount) : 0;
}; };
const paymentAmount = getPaymentAmount(); const paymentAmount = getPaymentAmount() || 0;
const isButtonEnabled = const isButtonEnabled =
values.paymentType === "due" || values.paymentType === "due" ||
@ -223,8 +225,6 @@ const SelectAmountScreen = () => {
: "Select Amount"; : "Select Amount";
} }
const { t } = useTranslation();
return ( return (
<KeyboardAvoidingView <KeyboardAvoidingView
style={styles.container} style={styles.container}
@ -232,7 +232,10 @@ const SelectAmountScreen = () => {
// 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={t("payment.select-amount")}
showBackButton={true}
/>
<ScrollView <ScrollView
style={styles.content} style={styles.content}
@ -372,7 +375,9 @@ const SelectAmountScreen = () => {
disabled={!isButtonEnabled} disabled={!isButtonEnabled}
> >
{getPaymentAmount() < payments.MIN_AMOUNT ? ( {getPaymentAmount() < payments.MIN_AMOUNT ? (
<Text style={styles.payButtonText}>Select Amount</Text> <Text style={styles.payButtonText}>
{t("payment.select-amount")}
</Text>
) : ( ) : (
<Text style={styles.payButtonText}> <Text style={styles.payButtonText}>
Pay {getPaymentAmount().toFixed(2)} Pay {getPaymentAmount().toFixed(2)}

View File

@ -1,4 +1,4 @@
import React, { useTransition } from "react"; import React, { useState } from "react";
import { import {
View, View,
Text, Text,
@ -22,9 +22,11 @@ import { router } from "expo-router";
import { useSnackbar } from "@/contexts/Snackbar"; import { useSnackbar } from "@/contexts/Snackbar";
import Header from "@/components/common/Header"; import Header from "@/components/common/Header";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Overlay } from "@/components/common/Overlay";
export default function EditName() { export default function EditName() {
const { data } = useSelector((state: RootState) => state.user); const { data } = useSelector((state: RootState) => state.user);
const [isLoading, setIsLoading] = useState<boolean>();
const originalName = data?.name || ""; const originalName = data?.name || "";
const dispatch = useDispatch(); const dispatch = useDispatch();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
@ -34,23 +36,27 @@ export default function EditName() {
.max(57, "Name cannot exceed 57 characters"), .max(57, "Name cannot exceed 57 characters"),
}); });
const { showSnackbar } = useSnackbar(); const { showSnackbar } = useSnackbar();
const { t } = useTranslation();
const handleSave = async (values: { name: string }) => { const handleSave = async (values: { name: string }) => {
try { try {
setIsLoading(true);
await api.put(`${BASE_URL}/api/v1/update-user-information`, { await api.put(`${BASE_URL}/api/v1/update-user-information`, {
name: values.name, name: values.name,
mobile: data?.mobile, mobile: data?.mobile,
}); });
dispatch(setUserData({ name: values.name })); dispatch(setUserData({ name: values.name }));
showSnackbar("Name updated successfully", "success"); showSnackbar(`${t("profile.name-changed")}`, "success");
router.back(); router.back();
} catch (error) { } catch (error) {
showSnackbar(`${t("service.something-went-wrong")}`, "error");
console.error("Error updating name:", error); console.error("Error updating name:", error);
} finally {
setIsLoading(false);
} }
}; };
const { t } = useTranslation();
return ( return (
<> <>
<Header title={t("profile.edit-name")} showBackButton={true} /> <Header title={t("profile.edit-name")} showBackButton={true} />
@ -124,6 +130,7 @@ export default function EditName() {
</View> </View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
</KeyboardAvoidingView> </KeyboardAvoidingView>
<Overlay isUploading={isLoading ?? false} />
</> </>
); );
} }

View File

@ -0,0 +1,8 @@
<svg width="29" height="28" viewBox="0 0 29 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_901_1089" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="4" y="4" width="21" height="20">
<rect x="4.57031" y="4" width="20" height="20" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_901_1089)">
<path d="M16.6536 19C16.0703 19 15.5773 18.7986 15.1745 18.3958C14.7717 17.993 14.5703 17.5 14.5703 16.9167C14.5703 16.3333 14.7717 15.8403 15.1745 15.4375C15.5773 15.0347 16.0703 14.8333 16.6536 14.8333C17.237 14.8333 17.73 15.0347 18.1328 15.4375C18.5356 15.8403 18.737 16.3333 18.737 16.9167C18.737 17.5 18.5356 17.993 18.1328 18.3958C17.73 18.7986 17.237 19 16.6536 19ZM8.73698 22.3333C8.27865 22.3333 7.88628 22.1701 7.5599 21.8437C7.23351 21.5174 7.07031 21.125 7.07031 20.6667V8.99999C7.07031 8.54166 7.23351 8.1493 7.5599 7.82291C7.88628 7.49652 8.27865 7.33332 8.73698 7.33332H9.57031V6.49999C9.57031 6.26388 9.65017 6.06596 9.8099 5.90624C9.96962 5.74652 10.1675 5.66666 10.4036 5.66666C10.6398 5.66666 10.8377 5.74652 10.9974 5.90624C11.1571 6.06596 11.237 6.26388 11.237 6.49999V7.33332H17.9036V6.49999C17.9036 6.26388 17.9835 6.06596 18.1432 5.90624C18.303 5.74652 18.5009 5.66666 18.737 5.66666C18.9731 5.66666 19.171 5.74652 19.3307 5.90624C19.4905 6.06596 19.5703 6.26388 19.5703 6.49999V7.33332H20.4036C20.862 7.33332 21.2543 7.49652 21.5807 7.82291C21.9071 8.1493 22.0703 8.54166 22.0703 8.99999V20.6667C22.0703 21.125 21.9071 21.5174 21.5807 21.8437C21.2543 22.1701 20.862 22.3333 20.4036 22.3333H8.73698ZM8.73698 20.6667H20.4036V12.3333H8.73698V20.6667ZM8.73698 10.6667H20.4036V8.99999H8.73698V10.6667Z" fill="#565F70"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,8 @@
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_14_4184" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="21">
<rect y="0.5" width="20" height="20" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_14_4184)">
<path d="M8.83366 12L7.04199 10.2083C6.88921 10.0556 6.69477 9.97917 6.45866 9.97917C6.22255 9.97917 6.0281 10.0556 5.87533 10.2083C5.72255 10.3611 5.64616 10.5556 5.64616 10.7917C5.64616 11.0278 5.72255 11.2222 5.87533 11.375L8.25033 13.75C8.41699 13.9167 8.61144 14 8.83366 14C9.05588 14 9.25033 13.9167 9.41699 13.75L14.1253 9.04167C14.2781 8.88889 14.3545 8.69445 14.3545 8.45834C14.3545 8.22223 14.2781 8.02778 14.1253 7.87501C13.9725 7.72223 13.7781 7.64584 13.542 7.64584C13.3059 7.64584 13.1114 7.72223 12.9587 7.87501L8.83366 12ZM10.0003 18.8333C8.84755 18.8333 7.76421 18.6146 6.75033 18.1771C5.73644 17.7396 4.85449 17.1458 4.10449 16.3958C3.35449 15.6458 2.76074 14.7639 2.32324 13.75C1.88574 12.7361 1.66699 11.6528 1.66699 10.5C1.66699 9.34723 1.88574 8.26389 2.32324 7.25001C2.76074 6.23612 3.35449 5.35417 4.10449 4.60417C4.85449 3.85417 5.73644 3.26042 6.75033 2.82292C7.76421 2.38542 8.84755 2.16667 10.0003 2.16667C11.1531 2.16667 12.2364 2.38542 13.2503 2.82292C14.2642 3.26042 15.1462 3.85417 15.8962 4.60417C16.6462 5.35417 17.2399 6.23612 17.6774 7.25001C18.1149 8.26389 18.3337 9.34723 18.3337 10.5C18.3337 11.6528 18.1149 12.7361 17.6774 13.75C17.2399 14.7639 16.6462 15.6458 15.8962 16.3958C15.1462 17.1458 14.2642 17.7396 13.2503 18.1771C12.2364 18.6146 11.1531 18.8333 10.0003 18.8333ZM10.0003 17.1667C11.8614 17.1667 13.4378 16.5208 14.7295 15.2292C16.0212 13.9375 16.667 12.3611 16.667 10.5C16.667 8.63889 16.0212 7.06251 14.7295 5.77084C13.4378 4.47917 11.8614 3.83334 10.0003 3.83334C8.13921 3.83334 6.56283 4.47917 5.27116 5.77084C3.97949 7.06251 3.33366 8.63889 3.33366 10.5C3.33366 12.3611 3.97949 13.9375 5.27116 15.2292C6.56283 16.5208 8.13921 17.1667 10.0003 17.1667Z" fill="#008761"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,98 @@
import React from "react";
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
import CheckIcon from "@/assets/icons/check_circle.svg";
interface EMINotificationProps {
message: string;
actionText?: string;
onActionPress?: () => void;
}
const EMINotification: React.FC<EMINotificationProps> = ({
message,
actionText,
onActionPress,
}) => {
return (
<View style={styles.container}>
<View style={styles.content}>
<View style={styles.checkIconContainer}>
<CheckIcon width={20} height={20} />
</View>
<View style={styles.textContainer}>
<Text style={styles.messageText}>{message}</Text>
</View>
{actionText && onActionPress && (
<TouchableOpacity style={styles.actionButton} onPress={onActionPress}>
<Text style={styles.actionText}>{actionText}</Text>
</TouchableOpacity>
)}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
height: 72,
backgroundColor: "#FCFCFC",
borderRadius: 10,
padding: 16,
justifyContent: "center",
alignItems: "center",
},
content: {
flexDirection: "row",
alignItems: "center",
height: 40,
gap: 12,
},
checkIconContainer: {
width: 40,
height: 40,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#EBFAF6",
borderRadius: "50%",
},
checkIcon: {
width: 24,
height: 24,
backgroundColor: "#10B981",
borderRadius: 12,
justifyContent: "center",
alignItems: "center",
},
checkMark: {
color: "white",
fontSize: 16,
fontWeight: "bold",
},
textContainer: {
flex: 1,
height: 40,
justifyContent: "center",
paddingRight: 8,
},
messageText: {
fontSize: 12,
fontWeight: "500",
color: "#565F70",
fontFamily: "Inter-Medium",
lineHeight: 16,
},
actionButton: {
paddingVertical: 8,
borderRadius: 4,
},
actionText: {
fontSize: 14,
fontWeight: "500",
color: "#006C4D",
fontFamily: "Inter-Medium",
lineHeight: 20,
textAlign: "center",
},
});
export default EMINotification;

View File

@ -4,6 +4,7 @@ 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"; import { useTranslation } from "react-i18next";
import { useSnackbar } from "@/contexts/Snackbar";
interface CustomerSupportProps { interface CustomerSupportProps {
visible: boolean; visible: boolean;
@ -23,12 +24,15 @@ export default function LanguageModal({
})(); })();
}, []); }, []);
const { showSnackbar } = useSnackbar();
const { t } = useTranslation(); const { t } = useTranslation();
const handleLanguagePress = (lang: "en" | "hi") => { const handleLanguagePress = (lang: "en" | "hi") => {
setSelectedLang(lang); setSelectedLang(lang);
setLanguage(lang); setLanguage(lang);
onClose(); onClose();
showSnackbar(`${t("profile.lang-changed")}`, "success");
}; };
return ( return (
<BottomSheetModal <BottomSheetModal
@ -42,6 +46,7 @@ export default function LanguageModal({
selectedLang === "en" && styles.selectedCard, selectedLang === "en" && styles.selectedCard,
]} ]}
onPress={() => handleLanguagePress("en")} onPress={() => handleLanguagePress("en")}
disabled={selectedLang === "en"}
> >
<Text style={styles.languageText}>English</Text> <Text style={styles.languageText}>English</Text>
</TouchableOpacity> </TouchableOpacity>
@ -52,6 +57,7 @@ export default function LanguageModal({
selectedLang === "hi" && styles.selectedCard, selectedLang === "hi" && styles.selectedCard,
]} ]}
onPress={() => handleLanguagePress("hi")} onPress={() => handleLanguagePress("hi")}
disabled={selectedLang === "hi"}
> >
<Text style={styles.languageText}>ि</Text> <Text style={styles.languageText}>ि</Text>
</TouchableOpacity> </TouchableOpacity>

View File

@ -1,11 +1,15 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Snackbar, Portal } from "react-native-paper"; import { Snackbar, Portal, Checkbox } from "react-native-paper";
import { StyleSheet, Text } from "react-native"; import { StyleSheet, Text, View } from "react-native";
import CheckBox from "@/assets/icons/check_circle.svg";
import CrossIcon from "@/assets/icons/cancel.svg";
interface CustomSnackbarProps { interface CustomSnackbarProps {
message: string; message: string;
bgColor: string; bgColor: string;
textColor: string; textColor: string;
borderColor: string;
icon: React.ReactNode;
duration: number; duration: number;
visible: boolean; visible: boolean;
onDismiss: () => void; onDismiss: () => void;
@ -15,6 +19,8 @@ const CustomSnackbar: React.FC<CustomSnackbarProps> = ({
message, message,
bgColor, bgColor,
textColor, textColor,
borderColor,
icon,
duration, duration,
visible, visible,
onDismiss, onDismiss,
@ -35,16 +41,29 @@ const CustomSnackbar: React.FC<CustomSnackbarProps> = ({
onDismiss={onDismiss} onDismiss={onDismiss}
style={[ style={[
styles.snackbar, styles.snackbar,
{ backgroundColor: bgColor, zIndex: 999, elevation: 999 }, {
backgroundColor: bgColor,
borderColor,
zIndex: 999,
elevation: 999,
},
]} ]}
duration={Snackbar.DURATION_SHORT} duration={Snackbar.DURATION_SHORT}
> >
<Text style={{ color: textColor, ...styles.message }}>{message}</Text> <View style={styles.content}>
{icon}
<Text style={[styles.message, { color: textColor }]}>{message}</Text>
</View>
</Snackbar> </Snackbar>
); );
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
content: {
flexDirection: "row",
alignItems: "center",
gap: 8,
},
message: { message: {
fontStyle: "normal", fontStyle: "normal",
fontWeight: "bold", fontWeight: "bold",
@ -59,6 +78,8 @@ const styles = StyleSheet.create({
borderRadius: 5, borderRadius: 5,
marginBottom: 60, marginBottom: 60,
color: "#242C3B", color: "#242C3B",
borderWidth: 1,
borderColor: "#B6ECDD",
}, },
}); });

View File

@ -57,9 +57,9 @@ const BatteryWarrantyCard: React.FC<Props> = ({
})()} })()}
</Text> </Text>
</View> </View>
<Pressable style={styles.iconButton}> <View style={styles.iconButton}>
<ChevronRight /> <ChevronRight />
</Pressable> </View>
</View> </View>
<View style={styles.progressContainer}> <View style={styles.progressContainer}>
<View style={styles.progressBar}> <View style={styles.progressBar}>

View File

@ -101,6 +101,7 @@ export default function IssueSelectorModal({
style={[ style={[
styles.clearText, styles.clearText,
!hasSelection && styles.clearTextDisabled, !hasSelection && styles.clearTextDisabled,
selectedValues.length > 0 && { color: "#006C4D" }, // 👈 Add this line
]} ]}
> >
Clear Clear
@ -123,7 +124,7 @@ export default function IssueSelectorModal({
onValueChange={() => toggleValue(option.value)} onValueChange={() => toggleValue(option.value)}
color={ color={
selectedValues.includes(option.value) selectedValues.includes(option.value)
? "#252A34" ? "#009E71"
: undefined : undefined
} }
/> />

View File

@ -71,7 +71,7 @@ export const useTabConfig = () => {
export const MESSAGES = { export const MESSAGES = {
AUTHENTICATION: { AUTHENTICATION: {
INVALID_TOKEN: "Invalid Token", INVALID_TOKEN: "Invalid Token",
VERIFICATION_FAILED: "Verification failed, try again later", VERIFICATION_FAILED: "OTP incorrect",
}, },
}; };
@ -91,10 +91,10 @@ export const ALERT_STYLES = {
}; };
export const SUPPORT = { export const SUPPORT = {
WHATSAPP_NUMBER: "918685846459", WHATSAPP_NUMBER: "919717116943",
WHATSAPP_PLACEHOLDER: "Hi, I need help regarding my vehicle.", WHATSAPP_PLACEHOLDER: "Hi, I need help regarding my vehicle.",
PHONE: "+911234567890", PHONE: "+919717116943",
EMAIL: "support@vecmocon.com", EMAIL: "baas@vecmocon.com",
EMAIL_SUBJECT: "Support Request", EMAIL_SUBJECT: "Support Request",
EMAIL_BODY: "Hello,\n\nI need assistance with...", EMAIL_BODY: "Hello,\n\nI need assistance with...",
}; };

View File

@ -1,8 +1,10 @@
import React, { createContext, useContext, useState, ReactNode } from "react"; import React, { createContext, useContext, useState, ReactNode } from "react";
import CustomSnackbar from "../components/common/CustomSnackbar"; import CustomSnackbar from "../components/common/CustomSnackbar";
import CheckIcon from "@/assets/icons/check_circle.svg";
import CrossIcon from "@/assets/icons/cancel.svg";
interface SnackbarContextProps { interface SnackbarContextProps {
showSnackbar: (message: string, type: "success" | "error") => void; showSnackbar: (message: string, type: "success" | "error" | "info") => void;
} }
const SnackbarContext = createContext<SnackbarContextProps | undefined>( const SnackbarContext = createContext<SnackbarContextProps | undefined>(
@ -30,20 +32,44 @@ export const SnackbarProvider: React.FC<SnackbarProviderProps> = ({
textColor: "", textColor: "",
duration: 2, duration: 2,
visible: false, visible: false,
icon: <></>,
borderColor: "",
}); });
const showSnackbar = ( const showSnackbar = (
message: string, message: string,
type: "success" | "error" | "info" type: "success" | "error" | "info"
) => { ) => {
const bgColor = let bgColor = "";
type === "success" ? "#DFF2E9" : type === "error" ? "#FDEDED" : "#E0F7FA"; let textColor = "";
const textColor = let borderColor = "";
type === "success" ? "#242C3B" : type === "error" ? "#D51D10" : "#00796B"; let icon = <></>;
console.log(type);
if (type === "success") {
bgColor = "#DFF2E9";
textColor = "#242C3B";
borderColor = "#B6ECDD";
icon = <CheckIcon width={20} height={20} fill="#242C3B" />;
} else if (type === "error") {
bgColor = "#FDEDED";
textColor = "#D51D10";
borderColor = "#F5C6CB";
icon = <CrossIcon width={20} height={20} fill="#D51D10" />;
} else if (type === "info") {
bgColor = "#ffffff";
textColor = "#055160"; // dark blue text
borderColor = "#B6E0FE"; // optional subtle border
// icon = <InfoIcon width={20} height={20} fill="#055160" />;
}
setSnackbar({ setSnackbar({
message, message,
bgColor, bgColor,
textColor, textColor,
borderColor,
icon,
duration: 2, duration: 2,
visible: true, visible: true,
}); });
@ -63,6 +89,8 @@ export const SnackbarProvider: React.FC<SnackbarProviderProps> = ({
message={snackbar.message} message={snackbar.message}
bgColor={snackbar.bgColor} bgColor={snackbar.bgColor}
textColor={snackbar.textColor} textColor={snackbar.textColor}
borderColor={snackbar.borderColor}
icon={snackbar.icon}
duration={snackbar.duration} duration={snackbar.duration}
visible={snackbar.visible} visible={snackbar.visible}
onDismiss={hideSnackbar} onDismiss={hideSnackbar}

View File

@ -3,7 +3,8 @@
"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, contact us", "for-any-queries": "For any queries, ",
"contact-us": "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",
@ -38,7 +39,8 @@
"day": "day(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" "km": "km",
"days-left-to-pay-emi": "days left to pay the EMI!"
}, },
"profile": { "profile": {
"my-account": "My Account", "my-account": "My Account",
@ -60,7 +62,8 @@
"whatsapp": "Whatsapp", "whatsapp": "Whatsapp",
"call-us": "Call Us", "call-us": "Call Us",
"email": "Email", "email": "Email",
"view-plan": "View Plan" "lang-changed": "भाषा सफलतापूर्वक बदल दी गई",
"fetching-location": "Fetching location..."
}, },
"payment": { "payment": {
"last-emi-details": "Last EMI Details", "last-emi-details": "Last EMI Details",
@ -86,10 +89,13 @@
"monthly-emi": "Monthly EMI", "monthly-emi": "Monthly EMI",
"installments-paid": "Installments Paid", "installments-paid": "Installments Paid",
"emi-paid-till-now": "EMI Paid Till Now", "emi-paid-till-now": "EMI Paid Till Now",
"transaction-detail": "Transaction Detail", "transaction-details": "Transaction Details",
"transaction-successful": "Transaction successful", "transaction-successful": "Transaction successful",
"payment-mode": "Payment mode", "payment-mode": "Payment mode",
"paid-to": "Paid to", "paid-to": "Paid To",
"paid-by": "Paid By",
"order-id": "Order ID",
"transaction-id": "Transaction ID",
"upi-transaction-id": "UPI Transaction ID", "upi-transaction-id": "UPI Transaction ID",
"all-emi-paid": "All EMI Paid", "all-emi-paid": "All EMI Paid",
"contact-dealer": "For further queries, contact your dealer!", "contact-dealer": "For further queries, contact your dealer!",
@ -106,7 +112,8 @@
"amount-to-be-paid": "Amount to be paid", "amount-to-be-paid": "Amount to be paid",
"pay-using-upi": "Pay using UPI App", "pay-using-upi": "Pay using UPI App",
"confirm-payment": "Confirm once your payment is completed", "confirm-payment": "Confirm once your payment is completed",
"payment-done": "Payment Done" "payment-done": "Payment Done",
"view-plan": "View Plan"
}, },
"service": { "service": {
"schedule-maintenance": "Schedule Maintenance", "schedule-maintenance": "Schedule Maintenance",
@ -119,12 +126,11 @@
"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",
"clear": "Clear", "clear": "Clear",
"issues-selected": "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", "select-valid-time": "Select valid time",
"words": "words" "words": "words"
}, },
"battery": { "battery": {

View File

@ -3,7 +3,8 @@
"welcome": "ड्राइवर साथी में आपका स्वागत है", "welcome": "ड्राइवर साथी में आपका स्वागत है",
"enter-mobile-number": "मोबाइल नंबर दर्ज करें", "enter-mobile-number": "मोबाइल नंबर दर्ज करें",
"enter-registered-mobile-number": "अपना पंजीकृत मोबाइल नंबर दर्ज करें", "enter-registered-mobile-number": "अपना पंजीकृत मोबाइल नंबर दर्ज करें",
"for-any-queries-contact-us": "किसी भी प्रकार की सहायता के लिए, हमसे संपर्क करें", "for-any-queries": "किसी भी प्रकार की सहायता के लिए, ",
"contact-us": " हमसे संपर्क करें",
"number-not-registered": "नंबर पंजीकृत नहीं है।", "number-not-registered": "नंबर पंजीकृत नहीं है।",
"enter-otp": "कृपया अपने मोबाइल नंबर पर भेजा गया OTP दर्ज करें।", "enter-otp": "कृपया अपने मोबाइल नंबर पर भेजा गया OTP दर्ज करें।",
"verify-otp": "ओटीपी वेरिफाई करें", "verify-otp": "ओटीपी वेरिफाई करें",
@ -38,7 +39,9 @@
"day": "दिन", "day": "दिन",
"alerts": "अलर्ट", "alerts": "अलर्ट",
"emi-alert": "EMI का भुगतान करने के लिए 14 दिन शेष!", "emi-alert": "EMI का भुगतान करने के लिए 14 दिन शेष!",
"km": "कि.मी." "km": "कि.मी.",
"days-left-to-pay-emi": "दिन बाकी हैं ईएमआई भरने के लिए!",
"fetching-location": "स्थान प्राप्त किया जा रहा है..."
}, },
"profile": { "profile": {
"my-account": "मेरा खाता", "my-account": "मेरा खाता",
@ -59,7 +62,8 @@
"customer-support": "ग्राहक सहायता", "customer-support": "ग्राहक सहायता",
"whatsapp": "व्हाट्सएप", "whatsapp": "व्हाट्सएप",
"call-us": "हमें कॉल करें", "call-us": "हमें कॉल करें",
"email": "ईमेल" "email": "ईमेल",
"lang-changed": "Language changed successfully"
}, },
"payment": { "payment": {
"last-emi-details": "अंतिम EMI विवरण", "last-emi-details": "अंतिम EMI विवरण",
@ -85,10 +89,13 @@
"monthly-emi": "मासिक EMI", "monthly-emi": "मासिक EMI",
"installments-paid": "भुगतान की गई किश्तें", "installments-paid": "भुगतान की गई किश्तें",
"emi-paid-till-now": "अभी तक भुगतान की गई EMI", "emi-paid-till-now": "अभी तक भुगतान की गई EMI",
"transaction-detail": "लेनदेन विवरण", "transaction-details": "लेनदेन विवरण",
"transaction-successful": "लेनदेन सफल", "transaction-successful": "लेनदेन सफल",
"payment-mode": "भुगतान मोड", "payment-mode": "भुगतान मोड",
"paid-to": "भुगतान किया गया", "paid-to": "भुगतान प्राप्तकर्ता",
"paid-by": "भुगतानकर्ता",
"order-id": "आर्डर ID",
"transaction-id": "लेनदेन ID",
"upi-transaction-id": "यूपीआई ट्रांजैक्शन आईडी", "upi-transaction-id": "यूपीआई ट्रांजैक्शन आईडी",
"all-emi-paid": "सभी EMI का भुगतान किया गया", "all-emi-paid": "सभी EMI का भुगतान किया गया",
"contact-dealer": "अधिक जानकारी के लिए, अपने डीलर से संपर्क करें!", "contact-dealer": "अधिक जानकारी के लिए, अपने डीलर से संपर्क करें!",
@ -96,7 +103,7 @@
"emi-completed": "EMI पूर्ण", "emi-completed": "EMI पूर्ण",
"plan-name": "18 महीने की योजना", "plan-name": "18 महीने की योजना",
"view-plan": "प्लान देखें", "view-plan": "प्लान देखें",
"advance-amount": "अतिरिक्त राशि", "advance-amount": "एडवांस राशि",
"failed": "फेल हो गया", "failed": "फेल हो गया",
"select-amount": "राशि का चयन करें", "select-amount": "राशि का चयन करें",
"pay-amount-due": "बकाया राशि भुगतान करें", "pay-amount-due": "बकाया राशि भुगतान करें",
@ -119,7 +126,6 @@
"supported-formats": "समर्थित प्रारूपों में JPG, JPEG और PNG शामिल हैं।", "supported-formats": "समर्थित प्रारूपों में JPG, JPEG और PNG शामिल हैं।",
"comments": "टिप्पणियाँ", "comments": "टिप्पणियाँ",
"submit": "सबमिट करें", "submit": "सबमिट करें",
"select-issues-tba": "समस्याओं का चयन करें",
"clear": "साफ़ करें", "clear": "साफ़ करें",
"issues-selected": "समस्याएँ चुनी गई", "issues-selected": "समस्याएँ चुनी गई",
"service-request-success": "सेवा अनुरोध सफलतापूर्वक सबमिट किया गया", "service-request-success": "सेवा अनुरोध सफलतापूर्वक सबमिट किया गया",

View File

@ -106,6 +106,7 @@ export const initSocket = async () => {
//get latest telemetry as fallback option //get latest telemetry as fallback option
token = await fetchToken(); token = await fetchToken();
controllingServer = await fetchControllingServer(token); controllingServer = await fetchControllingServer(token);
store.dispatch(setTelemetryLoading());
await fetchLatestTelemetry(token, controllingServer); await fetchLatestTelemetry(token, controllingServer);
connectSocket(); connectSocket();
} catch (err) { } catch (err) {

View File

@ -70,36 +70,52 @@ export const sendOTP = createAsyncThunk<SendOTPResponse, SendOTPParams>(
return response.data; return response.data;
// if (!response.data.status) throw new Error(response.data.message); // if (!response.data.status) throw new Error(response.data.message);
} catch (error: any) { } catch (error: any) {
const serverMessage = error.response?.data?.message; let message = "Something went wrong. Please try again.";
return rejectWithValue(serverMessage || "Something went wrong");
if (axios.isAxiosError(error)) {
// try to read backend message safely
message = error.response?.data?.message || error.message || message;
} else if (error instanceof Error) {
message = error.message;
}
console.log(message, "message");
return rejectWithValue(message);
} }
} }
); );
export const verifyOTP = createAsyncThunk<VerifyOTPResponse, VerifyOTPParams>( export const verifyOTP = createAsyncThunk<
"auth/verifyOTP", VerifyOTPResponse,
async (params, { rejectWithValue }) => { VerifyOTPParams,
try { { rejectValue: string }
console.log("params", params); >("auth/verifyOTP", async (params, { rejectWithValue }) => {
const response = await axios.post<VerifyOTPResponse>( try {
`${BASE_URL}/api/v1/verify-otp`, console.log("params", params);
params const response = await axios.post<VerifyOTPResponse>(
); `${BASE_URL}/api/v1/verify-otp`,
if (!response.data.success) throw new Error(response.data.message); params
);
if (!response.data.success) return rejectWithValue(response.data.message);
// Store tokens // Store tokens
await AsyncStorage.setItem( await AsyncStorage.setItem(
STORAGE_KEYS.AUTH_TOKEN, STORAGE_KEYS.AUTH_TOKEN,
response.data.data.token response.data.data.token
); );
return response.data; return response.data;
} catch (error: any) { } catch (error: any) {
console.log(error.message); let message = "Something went wrong. Please try again.";
return rejectWithValue(MESSAGES.AUTHENTICATION.VERIFICATION_FAILED);
if (axios.isAxiosError(error)) {
message = error.response?.data?.message || error.message || message;
} else if (error instanceof Error) {
message = error.message;
} }
console.log(message, "message");
return rejectWithValue(message);
} }
); });
export const logout = createAsyncThunk( export const logout = createAsyncThunk(
"auth/logout", "auth/logout",
@ -173,7 +189,7 @@ const authSlice = createSlice({
}) })
.addCase(verifyOTP.rejected, (state, action) => { .addCase(verifyOTP.rejected, (state, action) => {
state.status = AUTH_STATUSES.FAILED; state.status = AUTH_STATUSES.FAILED;
state.verifyOTPError = action.error.message || "Failed to verify OTP"; state.verifyOTPError = action.payload || "Failed to verify OTP";
}) })
.addCase(logout.pending, (state) => { .addCase(logout.pending, (state) => {
state.status = AUTH_STATUSES.LOADING; state.status = AUTH_STATUSES.LOADING;