NOt publised, some permissions issue
parent
39a65e00e3
commit
8a60842bec
|
|
@ -92,8 +92,8 @@ android {
|
|||
applicationId 'com.vecmocon.driversaathi'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 5
|
||||
versionName "1.0.0"
|
||||
versionCode 11
|
||||
versionName "2.0.0"
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.vecmocon.driversaathi" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
|
|
|||
4
app.json
4
app.json
|
|
@ -2,7 +2,7 @@
|
|||
"expo": {
|
||||
"name": "Driver Saathi",
|
||||
"slug": "BatteryAsAService",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"scheme": "batteryasaservice",
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
},
|
||||
"edgeToEdgeEnabled": true,
|
||||
"package": "com.vecmocon.driversaathi",
|
||||
"versionCode": 5
|
||||
"versionCode": 11
|
||||
},
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { useSnackbar } from "@/contexts/Snackbar";
|
|||
import NetInfo from "@react-native-community/netinfo";
|
||||
import { getPaymentSummary, getUserDetails } from "@/store/userSlice";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { logout } from "@/store/authSlice";
|
||||
import { getLanguage, setLanguage } from "@/services/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { useSnackbar } from "@/contexts/Snackbar";
|
|||
import CustomerCareIcon from "../../assets/icons/customer-care.svg";
|
||||
import api from "@/services/axiosClient";
|
||||
import PaymentHistoryCard from "@/components/Payments/PaymentHistoryCard";
|
||||
import { BASE_URL } from "@/constants/config";
|
||||
import { BASE_URL, STORAGE_KEYS } from "@/constants/config";
|
||||
import { useDispatch } from "react-redux";
|
||||
import {
|
||||
setAdvanceBalance,
|
||||
|
|
@ -39,6 +39,7 @@ import RefreshIcon from "@/assets/icons/refresh.svg";
|
|||
import CustomerSupport from "@/components/home/CustomerSupportModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Image } from "expo-image";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
export interface MyPlan {
|
||||
no_of_emi: number;
|
||||
|
|
@ -149,6 +150,12 @@ export default function PaymentsTabScreen() {
|
|||
|
||||
const fetchEmiDetails = async () => {
|
||||
try {
|
||||
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
||||
if (!token) {
|
||||
console.log("No auth token found, skipping API call");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setEmiDetails(null);
|
||||
setAdvanceBalance(null);
|
||||
|
|
@ -218,6 +225,8 @@ export default function PaymentsTabScreen() {
|
|||
spinValue.setValue(0);
|
||||
};
|
||||
|
||||
const { isLoggedIn } = useSelector((state: RootState) => state.auth);
|
||||
|
||||
const spin = spinValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ["0deg", "360deg"],
|
||||
|
|
@ -225,10 +234,12 @@ export default function PaymentsTabScreen() {
|
|||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (isLoggedIn) {
|
||||
setShowFullHistory(false);
|
||||
fetchEmiDetails();
|
||||
fetchPaymentHistory(1, false);
|
||||
}, [])
|
||||
}
|
||||
}, [isLoggedIn])
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
|
|
@ -319,6 +330,12 @@ export default function PaymentsTabScreen() {
|
|||
pageNumber: number = 1,
|
||||
isLoadMore: boolean = false
|
||||
) => {
|
||||
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
||||
if (!token) {
|
||||
console.log("No auth token found, skipping API call");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (isLoadMore) {
|
||||
setIsLoadingMore(true);
|
||||
|
|
|
|||
|
|
@ -42,22 +42,28 @@ interface FormValues {
|
|||
|
||||
interface Issue {
|
||||
id: number;
|
||||
name: string;
|
||||
name: {
|
||||
en: String;
|
||||
hi: String;
|
||||
};
|
||||
}
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
serviceType: Yup.string().required("Service Type is required"),
|
||||
export default function ServiceFormScreen(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
serviceType: Yup.string().required(t("service.service-type-is-required")),
|
||||
issues: Yup.array().when("serviceType", {
|
||||
is: (val: string) => val !== "Regular",
|
||||
then: (schema) => schema.min(1, "At least one issue is required"),
|
||||
then: (schema) =>
|
||||
schema.min(1, t("service.atleast-one-issue-is-required")),
|
||||
otherwise: (schema) => schema.notRequired(),
|
||||
}),
|
||||
date: Yup.date().required("Date and Time is required"),
|
||||
photos: Yup.array().min(1, "At least one photo is required"),
|
||||
date: Yup.date().required(t("service.date-and-time-is-required")),
|
||||
photos: Yup.array().min(1, t("service.atleast-one-photo-is-required")),
|
||||
comments: Yup.string(),
|
||||
});
|
||||
});
|
||||
|
||||
export default function ServiceFormScreen(): JSX.Element {
|
||||
const [isFocus, setIsFocus] = useState<boolean>(false);
|
||||
const [isIssueSelectorVisible, setIssueSelectorVisible] = useState(false);
|
||||
|
||||
|
|
@ -196,26 +202,43 @@ export default function ServiceFormScreen(): JSX.Element {
|
|||
});
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleServiceTypeChange = async (
|
||||
item: { label: string; value: string },
|
||||
setFieldValue: (field: string, value: any) => void,
|
||||
setFieldTouched: (field: string, touched: boolean) => void
|
||||
) => {
|
||||
setFieldValue("serviceType", item.value);
|
||||
|
||||
if (item.value === "Regular") {
|
||||
// Regular service can be selected immediately
|
||||
setFieldValue("serviceType", item.value);
|
||||
setFieldValue("issues", []);
|
||||
setFieldTouched("issues", false);
|
||||
setIssues([]);
|
||||
setIsFocus(false);
|
||||
} else if (item.value === "On-demand") {
|
||||
await fetchIssues();
|
||||
try {
|
||||
setFieldValue("serviceType", item.value);
|
||||
setIsLoadingIssues(true);
|
||||
const response = await api.get(`${BASE_URL}/api/v1/service-issue-list`);
|
||||
|
||||
if (response.data.success) {
|
||||
setIssues(response.data.data);
|
||||
setFieldValue("serviceType", item.value);
|
||||
setFieldValue("issues", []);
|
||||
setFieldTouched("issues", false);
|
||||
}
|
||||
|
||||
setIsFocus(false);
|
||||
} else {
|
||||
setFieldValue("serviceType", null);
|
||||
throw new Error(response.data?.message || "Failed to fetch issues");
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Error fetching issues:", error);
|
||||
showSnackbar(`${t("common.something-went-wrong")}`, "error");
|
||||
setIssues([]);
|
||||
setFieldValue("serviceType", null);
|
||||
} finally {
|
||||
setIsLoadingIssues(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getSelectedIssuesText = (selectedIssueIds: number[]) => {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ export default function WelcomeScreen() {
|
|||
|
||||
const phoneValidationSchema = Yup.object().shape({
|
||||
phone: Yup.string()
|
||||
.required("Phone number is required")
|
||||
.matches(/^\d{10}$/, "Phone number must be exactly 10 digits"),
|
||||
.required(t("onboarding.phone-number-is-required"))
|
||||
.matches(/^\d{10}$/, t("onboarding.must-be-10-digits")),
|
||||
});
|
||||
useEffect(() => {
|
||||
if (status === AUTH_STATUSES.SUCCESS && otpId) {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import {
|
|||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
Alert,
|
||||
Share,
|
||||
Linking,
|
||||
BackHandler,
|
||||
ScrollView,
|
||||
|
|
@ -192,7 +190,9 @@ const UpiPaymentScreen = () => {
|
|||
};
|
||||
|
||||
function handlePaymentDone() {
|
||||
router.navigate("/(tabs)/payments");
|
||||
offPaymentConfirmation();
|
||||
disconnect();
|
||||
router.replace("/(tabs)/payments");
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
|
|
|||
|
|
@ -24,20 +24,21 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { displayValue, formatCurrency } from "@/utils/Common";
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
const SelectAmountScreen = () => {
|
||||
const validationSchema = Yup.object().shape({
|
||||
paymentType: Yup.string().required("Please select a payment option"),
|
||||
customAmount: Yup.string().when("paymentType", {
|
||||
is: "custom",
|
||||
then: (schema) =>
|
||||
schema
|
||||
.required("Amount is required")
|
||||
.required(t("payment.amount-is-required"))
|
||||
.test("valid-number", "Please enter a valid amount", (value) => {
|
||||
const numValue = parseFloat(value);
|
||||
return !isNaN(numValue) && numValue > 0;
|
||||
})
|
||||
.test(
|
||||
"min-amount",
|
||||
`Minimum amount is ₹${payments.MIN_AMOUNT}`,
|
||||
`${t("payment.minimum")}: ₹${payments.MIN_AMOUNT}`,
|
||||
(value) => {
|
||||
const numValue = parseFloat(value);
|
||||
return !isNaN(numValue) && numValue >= payments.MIN_AMOUNT;
|
||||
|
|
@ -45,9 +46,8 @@ const validationSchema = Yup.object().shape({
|
|||
),
|
||||
otherwise: (schema) => schema.notRequired(),
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
const SelectAmountScreen = () => {
|
||||
const dueAmount = useSelector(
|
||||
(state: RootState) => state.payments?.due_amount || 0
|
||||
);
|
||||
|
|
@ -365,7 +365,8 @@ const SelectAmountScreen = () => {
|
|||
</Text>
|
||||
) : (
|
||||
<Text style={styles.payButtonText}>
|
||||
Pay {displayValue(getPaymentAmount(), formatCurrency)}
|
||||
{`${t("payment.pay")}`}{" "}
|
||||
{displayValue(getPaymentAmount(), formatCurrency)}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { setUserData } from "@/store/userSlice";
|
|||
import { Overlay } from "@/components/common/Overlay";
|
||||
import Header from "@/components/common/Header";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { clearStackAndRouteTo } from "@/utils/Common";
|
||||
|
||||
export default function ProfileScreen() {
|
||||
const [isLangaugeModalVisible, setLanguageModalVisible] =
|
||||
|
|
@ -43,7 +44,7 @@ export default function ProfileScreen() {
|
|||
|
||||
const handleLogout = () => {
|
||||
dispatch(logout());
|
||||
router.replace("/auth/login");
|
||||
clearStackAndRouteTo("/auth/login");
|
||||
};
|
||||
|
||||
const handlePickImage = async () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
|
|
@ -11,10 +11,15 @@ import {
|
|||
import Checkbox from "expo-checkbox";
|
||||
import { issueConfig } from "@/constants/config";
|
||||
import CloseIcon from "@/assets/icons/close.svg";
|
||||
import { getLanguage } from "@/services/i18n";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
|
||||
interface Issue {
|
||||
id: number;
|
||||
name: string;
|
||||
name: {
|
||||
en: String;
|
||||
hi: String;
|
||||
};
|
||||
}
|
||||
|
||||
interface IssueSelectorModalProps {
|
||||
|
|
@ -37,6 +42,8 @@ export default function IssueSelectorModal({
|
|||
);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const [lang, setLang] = useState<"en" | "hi">("en");
|
||||
|
||||
const toggleValue = (id: number) => {
|
||||
setSelectedValues((prev) =>
|
||||
prev.includes(id) ? prev.filter((v) => v !== id) : [...prev, id]
|
||||
|
|
@ -44,7 +51,7 @@ export default function IssueSelectorModal({
|
|||
};
|
||||
|
||||
const filteredIssues = issues.filter((issue) =>
|
||||
issue.name.toLowerCase().includes(search.toLowerCase().trim())
|
||||
issue.name[lang].toLowerCase().includes(search.toLowerCase().trim())
|
||||
);
|
||||
|
||||
const clearSelection = () => setSelectedValues([]);
|
||||
|
|
@ -73,6 +80,17 @@ export default function IssueSelectorModal({
|
|||
}
|
||||
}, [visible, initialSelectedValues]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const fetchLang = async () => {
|
||||
const selectedLang = await getLanguage();
|
||||
setLang((selectedLang as "en") || "hi");
|
||||
};
|
||||
|
||||
fetchLang();
|
||||
}, []) // dependencies, can include anything that triggers refetch
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal visible={visible} animationType="slide">
|
||||
<View style={styles.container}>
|
||||
|
|
@ -126,7 +144,7 @@ export default function IssueSelectorModal({
|
|||
selectedValues.includes(issue.id) ? "#009E71" : undefined
|
||||
}
|
||||
/>
|
||||
<Text style={styles.itemLabel}>{issue.name}</Text>
|
||||
<Text style={styles.itemLabel}>{issue.name[lang]}</Text>
|
||||
</TouchableOpacity>
|
||||
))
|
||||
) : issues.length === 0 ? (
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ import DangerIcon from "../assets/icons/danger.svg";
|
|||
import type { BmsState } from "./types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const BASE_URL = "https://dev-driver-saathi-api.vecmocon.com";
|
||||
export const PAYMENT_SOCKET_BASE_URL =
|
||||
"wss://dev-driver-saathi-api.vecmocon.com";
|
||||
export const BASE_URL = "https://driver-saathi-api.vecmocon.com";
|
||||
export const PAYMENT_SOCKET_BASE_URL = "wss://driver-saathi-api.vecmocon.com";
|
||||
// export const BASE_URL = "https://46fa2cacfc37.ngrok-free.app";
|
||||
|
||||
// const SERVER_URL =
|
||||
|
|
@ -181,7 +180,7 @@ export const issueConfig = [
|
|||
];
|
||||
|
||||
export const payments = {
|
||||
MIN_AMOUNT: 1,
|
||||
MIN_AMOUNT: 200,
|
||||
MAX_AMOUNT: 500000,
|
||||
LINK_EXPIRED: "Payment link expired",
|
||||
SOCKET_CONNECTION_TIMEOUT_IN_SECS: 5,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "batteryasaservice",
|
||||
"main": "expo-router/entry",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"scripts": {
|
||||
"start": "expo start --dev-client",
|
||||
"android": "expo run:android",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export const setLanguage = async (language: string) => {
|
|||
export const getLanguage = async () => {
|
||||
const lang = await AsyncStorage.getItem(STORAGE_KEYS.LANGUAGE);
|
||||
|
||||
if (!lang) return "en";
|
||||
return lang;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@
|
|||
"enter-otp": "Please enter OTP sent to your mobile number",
|
||||
"verify-otp": "Verify OTP",
|
||||
"otp-incorrect": "OTP incorrect.",
|
||||
"resend-otp": "Resend OTP in",
|
||||
"resend-otp": "Resend OTP ",
|
||||
"verification-limit-exceeded": "Verification limit exceeded, try again later",
|
||||
"send-otp": "Send OTP"
|
||||
"send-otp": "Send OTP",
|
||||
"phone-number-is-required": "Phone number is required",
|
||||
"must-be-10-digits": "Phone number must be exactly 10 digits"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
|
|
@ -116,7 +118,9 @@
|
|||
"view-plan": "View Plan",
|
||||
"cancel-payment": "Cancel Payment",
|
||||
"continue-payment": "Continue Payment",
|
||||
"do-you-want-to-cancel-payment": "Are you sure you want to cancel the payment?"
|
||||
"do-you-want-to-cancel-payment": "Are you sure you want to cancel the payment?",
|
||||
"amount-is-required": "Amount is required",
|
||||
"pay": "Pay"
|
||||
},
|
||||
"service": {
|
||||
"schedule-maintenance": "Schedule Maintenance",
|
||||
|
|
@ -135,7 +139,11 @@
|
|||
"select-valid-time": "Select valid time",
|
||||
"words": "words",
|
||||
"time-must-be-between-10-and-5": "Select a time between 10 AM – 5 PM.",
|
||||
"regular-available-after-6-months": "Regular service available after 6 months of purchase"
|
||||
"regular-available-after-6-months": "Regular service available after 6 months of purchase",
|
||||
"service-type-is-required": "Service Type is required",
|
||||
"atleast-one-issue-is-required": "At least one issue is required",
|
||||
"date-and-time-is-required": "Date and time is required",
|
||||
"atleast-one-photo-is-required": "At least one photo is required"
|
||||
},
|
||||
"battery": {
|
||||
"battery-and-warranty": "My Battery and Warranty",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
"otp-incorrect": "OTP गलत है।",
|
||||
"resend-otp": "OTP पुनः भेजें",
|
||||
"verification-limit-exceeded": "वेरिफिकेशन लिमिट पार हो गई है, बाद में फिर से कोशिश करो",
|
||||
"send-otp": "OTP भेजें"
|
||||
"send-otp": "OTP भेजें",
|
||||
"phone-number-is-required": "फ़ोन नंबर अनिवार्य है",
|
||||
"must-be-10-digits": "फ़ोन नंबर बिल्कुल 10 अंकों का होना चाहिए"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "होम",
|
||||
|
|
@ -116,7 +118,9 @@
|
|||
"payment-done": "भुगतान हो गया",
|
||||
"cancel-payment": "भुगतान रद्द करें",
|
||||
"continue-payment": "भुगतान जारी रखें",
|
||||
"do-you-want-to-cancel-payment": "क्या आप वाकई भुगतान रद्द करना चाहते हैं?"
|
||||
"do-you-want-to-cancel-payment": "क्या आप वाकई भुगतान रद्द करना चाहते हैं?",
|
||||
"amount-is-required": "राशि दर्ज करें",
|
||||
"pay": "भुगतान करें"
|
||||
},
|
||||
"service": {
|
||||
"schedule-maintenance": "शेड्यूल मेंटेनेंस",
|
||||
|
|
@ -135,7 +139,11 @@
|
|||
"words": "शब्द",
|
||||
"select-valid-time": "सही समय चुनें",
|
||||
"time-must-be-between-10-and-5": "समय सुबह 10:00 बजे से शाम 5:00 बजे के बीच चुनें।",
|
||||
"regular-available-after-6-months": "रेगुलर सेवा खरीद के 6 महीने बाद उपलब्ध होगी"
|
||||
"regular-available-after-6-months": "रेगुलर सेवा खरीद के 6 महीने बाद उपलब्ध होगी",
|
||||
"service-type-is-required": "सेवा का प्रकार चुनें",
|
||||
"atleast-one-issue-is-required": "कम से कम एक समस्या चुनें",
|
||||
"date-and-time-is-required": "तारीख और समय चुनें",
|
||||
"atleast-one-photo-is-required": "कम से कम एक फोटो चुनें"
|
||||
},
|
||||
"battery": {
|
||||
"battery-and-warranty": "मेरी बैटरी और वारंटी",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import { router, type Href } from "expo-router";
|
||||
import type { NavigationOptions } from "expo-router/build/global-state/routing";
|
||||
|
||||
export const displayValue = (value: any, formatter?: (val: any) => string) => {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return "--";
|
||||
|
|
@ -8,3 +11,11 @@ export const displayValue = (value: any, formatter?: (val: any) => string) => {
|
|||
export const formatCurrency = (amount: number) => {
|
||||
return `₹${amount.toLocaleString()}`;
|
||||
};
|
||||
|
||||
export const clearStackAndRouteTo = (
|
||||
link: Href,
|
||||
options?: NavigationOptions
|
||||
): void => {
|
||||
router.dismissAll();
|
||||
router.replace(link, options);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue