diff --git a/app/auth/verify.tsx b/app/auth/verify.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/assets/icons/call.svg b/assets/icons/call.svg
new file mode 100644
index 0000000..ddd4155
--- /dev/null
+++ b/assets/icons/call.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/chevron_rightside.svg b/assets/icons/chevron_rightside.svg
new file mode 100644
index 0000000..04994b9
--- /dev/null
+++ b/assets/icons/chevron_rightside.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/close.svg b/assets/icons/close.svg
new file mode 100644
index 0000000..2359b61
--- /dev/null
+++ b/assets/icons/close.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/customer-care.svg b/assets/icons/customer-care.svg
new file mode 100644
index 0000000..6370dfa
--- /dev/null
+++ b/assets/icons/customer-care.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/danger.svg b/assets/icons/danger.svg
new file mode 100644
index 0000000..4eaa686
--- /dev/null
+++ b/assets/icons/danger.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/error.svg b/assets/icons/error.svg
new file mode 100644
index 0000000..bdbaedb
--- /dev/null
+++ b/assets/icons/error.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/mail.svg b/assets/icons/mail.svg
new file mode 100644
index 0000000..2ab3939
--- /dev/null
+++ b/assets/icons/mail.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/warning.svg b/assets/icons/warning.svg
new file mode 100644
index 0000000..019a3c1
--- /dev/null
+++ b/assets/icons/warning.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/whatsapp.svg b/assets/icons/whatsapp.svg
new file mode 100644
index 0000000..6c24ce6
--- /dev/null
+++ b/assets/icons/whatsapp.svg
@@ -0,0 +1,3 @@
+
diff --git a/components/EditScreenInfo.tsx b/components/EditScreenInfo.tsx
index 430b609..9b4a437 100644
--- a/components/EditScreenInfo.tsx
+++ b/components/EditScreenInfo.tsx
@@ -1,11 +1,11 @@
-import React from 'react';
-import { StyleSheet } from 'react-native';
+import React from "react";
+import { StyleSheet } from "react-native";
-import { ExternalLink } from './ExternalLink';
-import { MonoText } from './StyledText';
-import { Text, View } from './Themed';
+import { ExternalLink } from "./ExternalLink";
+import { MonoText } from "./StyledText";
+import { Text, View } from "./Themed";
-import Colors from '@/constants/Colors';
+import Colors from "@/constants/Colors";
export default function EditScreenInfo({ path }: { path: string }) {
return (
@@ -14,31 +14,37 @@ export default function EditScreenInfo({ path }: { path: string }) {
+ darkColor="rgba(255,255,255,0.8)"
+ >
Open up the code for this screen:
+ lightColor="rgba(0,0,0,0.05)"
+ >
{path}
- Change any of the text, save the file, and your app will automatically update.
+ darkColor="rgba(255,255,255,0.8)"
+ >
+ Change any of the text, save the file, and your app will automatically
+ update.
+ href="https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet"
+ >
- Tap here if your app doesn't automatically update after making changes
+ Tap here if your app doesn't automatically update after making
+ changes
@@ -48,7 +54,7 @@ export default function EditScreenInfo({ path }: { path: string }) {
const styles = StyleSheet.create({
getStartedContainer: {
- alignItems: 'center',
+ alignItems: "center",
marginHorizontal: 50,
},
homeScreenFilename: {
@@ -61,17 +67,17 @@ const styles = StyleSheet.create({
getStartedText: {
fontSize: 17,
lineHeight: 24,
- textAlign: 'center',
+ textAlign: "center",
},
helpContainer: {
marginTop: 15,
marginHorizontal: 20,
- alignItems: 'center',
+ alignItems: "center",
},
helpLink: {
paddingVertical: 15,
},
helpLinkText: {
- textAlign: 'center',
+ textAlign: "center",
},
});
diff --git a/components/home/BatteryWarrantyCars.tsx b/components/home/BatteryWarrantyCars.tsx
new file mode 100644
index 0000000..501f330
--- /dev/null
+++ b/components/home/BatteryWarrantyCars.tsx
@@ -0,0 +1,112 @@
+import React from "react";
+import { View, Text, StyleSheet, Pressable } from "react-native";
+import ChevronRight from "../../assets/icons/chevron_rightside.svg";
+
+type Props = {
+ totalWarrantyYears: number; // in years
+ batteryPurchaseEpoch: number; // in seconds
+};
+
+const BatteryWarrantyCard: React.FC = ({
+ totalWarrantyYears,
+ batteryPurchaseEpoch,
+}) => {
+ const totalWarrantyMs = totalWarrantyYears * 365.25 * 24 * 60 * 60 * 1000;
+ const purchaseDate = new Date(batteryPurchaseEpoch * 1000);
+ const now = new Date();
+ const elapsed = now.getTime() - purchaseDate.getTime();
+ const remaining = Math.max(totalWarrantyMs - elapsed, 0);
+ const progress = Math.min(elapsed / totalWarrantyMs, 1);
+
+ const remainingDate = new Date(remaining);
+ const yearsLeft = Math.floor(remaining / (365.25 * 24 * 60 * 60 * 1000));
+ const monthsLeft = Math.floor(
+ (remaining % (365.25 * 24 * 60 * 60 * 1000)) / (30.44 * 24 * 60 * 60 * 1000)
+ );
+ const daysLeft = Math.floor(
+ (remaining % (30.44 * 24 * 60 * 60 * 1000)) / (24 * 60 * 60 * 1000)
+ );
+
+ return (
+
+
+
+ Battery Warranty Left
+
+ {`${yearsLeft} years, ${monthsLeft} month${
+ monthsLeft !== 1 ? "s" : ""
+ }, ${daysLeft} day${daysLeft !== 1 ? "s" : ""}`}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ card: {
+ backgroundColor: "#FCFCFC",
+ borderRadius: 10,
+ padding: 16,
+ width: "100%",
+ height: 96,
+ justifyContent: "space-between",
+ },
+ topRow: {
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "flex-start",
+ },
+ textColumn: {
+ flexDirection: "column",
+ gap: 4,
+ },
+ title: {
+ fontSize: 12,
+ fontFamily: "Inter-Medium",
+ color: "#565E70",
+ lineHeight: 16,
+ },
+ time: {
+ fontSize: 14,
+ fontFamily: "Inter-SemiBold",
+ color: "#252A34",
+ lineHeight: 20,
+ },
+ iconButton: {
+ width: 40,
+ height: 40,
+ padding: 10,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ progressContainer: {
+ marginTop: 8,
+ width: "100%",
+ height: 8,
+ },
+ progressBar: {
+ width: "100%",
+ height: 8,
+ backgroundColor: "#E5E9F0",
+ borderRadius: 10,
+ overflow: "hidden",
+ },
+ progressFill: {
+ height: "100%",
+ backgroundColor: "#027A48",
+ borderRadius: 10,
+ },
+});
+
+export default BatteryWarrantyCard;
diff --git a/components/home/CustomerSupportModal.tsx b/components/home/CustomerSupportModal.tsx
new file mode 100644
index 0000000..4ecb20a
--- /dev/null
+++ b/components/home/CustomerSupportModal.tsx
@@ -0,0 +1,151 @@
+import React from "react";
+import {
+ View,
+ Text,
+ StyleSheet,
+ Pressable,
+ Linking,
+ Modal,
+} from "react-native";
+import WhatsappIcon from "../../assets/icons/whatsapp.svg";
+import CallIcon from "../../assets/icons/call.svg";
+import EmailIcon from "../../assets/icons/mail.svg";
+import CloseIcon from "../../assets/icons/close.svg";
+
+import { SUPPORT } from "@/constants/config";
+import { StatusBar } from "expo-status-bar";
+
+type Props = {
+ visible: boolean;
+ onClose: () => void;
+};
+
+const CustomerSupportModal: React.FC = ({ visible, onClose }) => {
+ const openWhatsApp = async () => {
+ const url = `https://wa.me/${
+ SUPPORT.WHATSAPP_NUMBER
+ }?text=${encodeURIComponent(SUPPORT.WHATSAPP_PLACEHOLDER)}`;
+
+ Linking.openURL(url).catch((err) =>
+ console.log("Failed to open WhatsApp:", err)
+ );
+ };
+
+ const makePhoneCall = () => {
+ Linking.openURL(`tel:${SUPPORT.PHONE}`);
+ };
+
+ const sendEmail = () => {
+ const url = `mailto:${SUPPORT.EMAIL}?subject=${encodeURIComponent(
+ SUPPORT.EMAIL_SUBJECT
+ )}&body=${encodeURIComponent(SUPPORT.EMAIL_BODY)}`;
+ Linking.openURL(url);
+ };
+
+ return (
+
+
+
+
+ {/* Header */}
+
+ Customer Support
+
+
+
+
+
+ {/* Buttons */}
+
+
+
+
+ Whatsapp
+
+
+
+ Call Us
+
+
+
+
+ Email
+
+
+
+
+
+ );
+};
+
+export default CustomerSupportModal;
+
+const styles = StyleSheet.create({
+ overlay: {
+ flex: 1,
+ justifyContent: "flex-end",
+ backgroundColor: "rgba(0,0,0,0.5)",
+ },
+ modalContainer: {
+ backgroundColor: "#fff",
+ borderTopLeftRadius: 12,
+ borderTopRightRadius: 12,
+ paddingHorizontal: 16,
+ paddingVertical: 16,
+ },
+ header: {
+ height: 56,
+ borderBottomWidth: 1,
+ borderBottomColor: "#E5E9F0",
+ paddingHorizontal: 16,
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "space-between",
+ },
+ headerText: {
+ fontSize: 14,
+ fontWeight: "600",
+ color: "#252A34",
+ },
+ iconButton: {
+ width: 40,
+ height: 40,
+ padding: 10,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ content: {
+ paddingVertical: 16,
+ gap: 16,
+ },
+ row: {
+ flexDirection: "row",
+ gap: 16,
+ justifyContent: "space-between",
+ },
+ secondaryButton: {
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "center",
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ height: 40,
+ borderRadius: 4,
+ backgroundColor: "#F3F5F8",
+ borderWidth: 1,
+ borderColor: "#D8DDE7",
+ width: 156,
+ gap: 8,
+ },
+ fullButton: {
+ width: "100%",
+ },
+ buttonText: {
+ fontSize: 14,
+ fontWeight: "500",
+ color: "#252A34",
+ },
+});
diff --git a/components/home/LocationMap.tsx b/components/home/LocationMap.tsx
new file mode 100644
index 0000000..53839fc
--- /dev/null
+++ b/components/home/LocationMap.tsx
@@ -0,0 +1,39 @@
+import React from "react";
+import { View, StyleSheet } from "react-native";
+import MapView, { Marker } from "react-native-maps";
+
+interface LocationMapProps {
+ latitude: number;
+ longitude: number;
+}
+
+const LocationMap: React.FC = ({ latitude, longitude }) => {
+ const region = {
+ latitude,
+ longitude,
+ latitudeDelta: 0.01,
+ longitudeDelta: 0.01,
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ borderRadius: 10,
+ overflow: "hidden",
+ },
+ map: {
+ width: "100%",
+ height: "100%",
+ },
+});
+
+export default LocationMap;
diff --git a/components/home/MetricCard.tsx b/components/home/MetricCard.tsx
new file mode 100644
index 0000000..efe064d
--- /dev/null
+++ b/components/home/MetricCard.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+import { View, Text, StyleSheet } from "react-native";
+
+interface MetricCardProps {
+ heading: string;
+ value: string | number;
+}
+
+const MetricCard: React.FC = ({ heading, value }) => {
+ return (
+
+
+ {heading}
+ {value}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ width: 156,
+ height: 68,
+ backgroundColor: "#FCFCFC",
+ borderRadius: 8,
+ padding: 12,
+ justifyContent: "center",
+ },
+ textContainer: {
+ flexDirection: "column",
+ justifyContent: "flex-start",
+ gap: 8,
+ },
+ heading: {
+ fontSize: 12,
+ lineHeight: 16,
+ color: "#565F70",
+ fontFamily: "Inter-Medium",
+ },
+ value: {
+ fontSize: 14,
+ lineHeight: 20,
+ color: "#252A34",
+ fontFamily: "Inter-SemiBold",
+ },
+});
+
+export default MetricCard;
diff --git a/components/home/PaymentDueCard.tsx b/components/home/PaymentDueCard.tsx
new file mode 100644
index 0000000..f9baa01
--- /dev/null
+++ b/components/home/PaymentDueCard.tsx
@@ -0,0 +1,95 @@
+import React from "react";
+import {
+ View,
+ Text,
+ StyleSheet,
+ TouchableOpacity,
+ Dimensions,
+} from "react-native";
+import { MaterialIcons } from "@expo/vector-icons";
+
+interface PaymentDueCardProps {
+ label: string;
+ amount: string;
+ onPress: () => void;
+}
+
+const PaymentDueCard: React.FC = ({
+ label,
+ amount,
+ onPress,
+}) => {
+ return (
+
+
+
+
+
+
+ {label}
+ {amount}
+
+
+
+ Pay Now
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ backgroundColor: "#FCFCFC",
+ borderRadius: 10,
+ padding: 16,
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ width: "100%",
+ gap: 16,
+ },
+ leftSection: {
+ flexDirection: "row",
+ alignItems: "center",
+ flex: 1,
+ },
+ iconWrapper: {
+ width: 40,
+ height: 40,
+ borderRadius: 20,
+ backgroundColor: "#FFEAEA",
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ textGroup: {
+ marginLeft: 12,
+ },
+ label: {
+ fontSize: 12,
+ color: "#565F70",
+ fontFamily: "Inter-Medium",
+ marginBottom: 4,
+ },
+ amount: {
+ fontSize: 14,
+ color: "#252A34",
+ fontFamily: "Inter-SemiBold",
+ },
+ button: {
+ backgroundColor: "#00825B",
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ borderRadius: 4,
+ },
+ buttonText: {
+ color: "#FCFCFC",
+ fontSize: 12,
+ fontFamily: "Inter-Medium",
+ },
+});
+
+export default PaymentDueCard;
diff --git a/components/home/Profile.tsx b/components/home/Profile.tsx
new file mode 100644
index 0000000..dded6dd
--- /dev/null
+++ b/components/home/Profile.tsx
@@ -0,0 +1,45 @@
+import React from "react";
+import { Text, TouchableOpacity, StyleSheet } from "react-native";
+
+type ProfileImageProps = {
+ username: string;
+ onClick?: () => void;
+ textSize?: number;
+ boxSize?: number;
+};
+
+const ProfileImage: React.FC = ({
+ username,
+ onClick,
+ textSize,
+ boxSize,
+}) => {
+ const firstLetter = username?.substring(0, 1)?.toUpperCase() || "V";
+
+ return (
+
+
+ {firstLetter}
+
+
+ );
+};
+
+export default ProfileImage;
+
+const styles = StyleSheet.create({
+ container: {
+ borderRadius: 394.316,
+ justifyContent: "center",
+ alignItems: "center",
+ backgroundColor: "#DAF5ED",
+ },
+ letter: {
+ color: "#008761",
+ fontFamily: "Inter",
+ fontWeight: "bold",
+ },
+});
diff --git a/components/home/SemiCircleProgress.tsx b/components/home/SemiCircleProgress.tsx
new file mode 100644
index 0000000..0eea948
--- /dev/null
+++ b/components/home/SemiCircleProgress.tsx
@@ -0,0 +1,147 @@
+import React, { useEffect, useRef, useState } from "react";
+import { View, StyleSheet, Text, Animated } from "react-native";
+import Svg, { Circle, Defs, LinearGradient, Stop } from "react-native-svg";
+
+const AnimatedCircle = Animated.createAnimatedComponent(Circle);
+
+const CircleProgressBar = ({
+ progress,
+ status,
+}: {
+ progress: number;
+ status: number;
+}) => {
+ const radius = 20;
+ const strokeWidth = 5;
+ const viewBoxPadding = 4;
+ const viewBoxSize = (radius + strokeWidth) * 2 + viewBoxPadding;
+ const center = viewBoxSize / 2;
+ const circumference = 2 * Math.PI * radius;
+
+ const animatedValue = useRef(new Animated.Value(0)).current;
+ const [displayProgress, setDisplayProgress] = useState(0);
+
+ useEffect(() => {
+ if (progress === undefined) {
+ animatedValue.setValue(0);
+ setDisplayProgress(0);
+ } else {
+ Animated.timing(animatedValue, {
+ toValue: progress,
+ duration: 500,
+ useNativeDriver: false,
+ }).start();
+ }
+ }, [progress]);
+
+ useEffect(() => {
+ const listenerId = animatedValue.addListener(({ value }) => {
+ setDisplayProgress(Math.round(value));
+ });
+ return () => animatedValue.removeListener(listenerId);
+ }, []);
+
+ const getColor = (progress: number) => {
+ if (progress <= 20) return "#D51D10";
+ if (progress <= 50) return "#FF7B00";
+ return "#009E71";
+ };
+
+ const animatedColor = getColor(displayProgress);
+
+ const dashOffset = animatedValue.interpolate({
+ inputRange: [0, 100],
+ outputRange: [circumference, circumference - 0.7 * circumference],
+ });
+
+ const progressDashArray = `${circumference} ${circumference}`;
+ const backgroundDashArray = `${0.7 * circumference} ${0.3 * circumference}`;
+
+ return (
+
+
+
+
+ {progress === undefined ? "---" : displayProgress}
+
+
+ {progress === undefined ? null : "%"}
+
+
+
+
+ {status === 1 ? "Charging" : "Discharging"}
+
+
+
+ );
+};
+
+// Keep the styles the same as before
+
+const styles = StyleSheet.create({
+ container: {
+ position: "relative",
+ alignItems: "center",
+ },
+ circleContainer: {
+ transform: [{ rotate: "144deg" }],
+ },
+ batteryPercent: {
+ position: "absolute",
+ top: 80,
+ justifyContent: "center",
+ alignItems: "baseline",
+ display: "flex",
+ flexDirection: "row",
+ },
+ batterySoc: {
+ position: "absolute",
+ top: 210,
+ justifyContent: "center",
+ transform: [{ translateX: 0 }],
+ },
+ batteryStatus: {
+ position: "absolute",
+ top: 300,
+ justifyContent: "center",
+ transform: [{ translateX: 0 }],
+ },
+});
+
+export default CircleProgressBar;
diff --git a/components/home/ServiceReminderCard.tsx b/components/home/ServiceReminderCard.tsx
new file mode 100644
index 0000000..aed268c
--- /dev/null
+++ b/components/home/ServiceReminderCard.tsx
@@ -0,0 +1,82 @@
+import React from "react";
+import {
+ View,
+ Text,
+ StyleSheet,
+ TouchableOpacity,
+ ViewStyle,
+} from "react-native";
+import Feather from "../../assets/icons/chevron_rightside.svg";
+import { ALERT_STYLES } from "@/constants/config";
+
+type AlertType = "info" | "warning" | "danger";
+
+interface AlertCardProps {
+ type: AlertType;
+ message: string;
+ subMessage?: string;
+}
+
+const AlertCard: React.FC = ({ type, message, subMessage }) => {
+ const style = ALERT_STYLES[type];
+
+ const containerStyle: ViewStyle[] = [
+ styles.container,
+ { backgroundColor: style.backgroundColor },
+ ];
+ return (
+
+
+
+
+
+ {message}
+ {subMessage ? `\n${subMessage}` : ""}
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ leftContent: {
+ flexDirection: "row",
+ gap: 8,
+ },
+ container: {
+ flexDirection: "row",
+ backgroundColor: "#E5EBFD",
+ borderRadius: 8,
+ padding: 8,
+ width: "100%",
+ alignSelf: "center",
+ alignItems: "flex-start",
+ justifyContent: "space-between",
+ height: 60,
+ marginHorizontal: 16,
+ },
+ content: {
+ flexDirection: "row",
+ gap: 8,
+ },
+ textContainer: {
+ marginLeft: 8,
+ justifyContent: "center",
+ },
+ text: {
+ color: "#252A34",
+ fontSize: 14,
+ lineHeight: 20,
+ fontFamily: "Inter-Regular",
+ },
+ boldText: {
+ fontWeight: "500",
+ },
+});
+
+export default AlertCard;
diff --git a/constants/config.ts b/constants/config.ts
index 3072a9b..8082c3a 100644
--- a/constants/config.ts
+++ b/constants/config.ts
@@ -6,11 +6,21 @@ import ServiceIcon from "../assets/icons/service.svg";
import ServiceIconFilled from "../assets/icons/service-filled.svg";
import BatteryIcon from "../assets/icons/battery.svg";
import BatteryIconFilled from "../assets/icons/battery-filled.svg";
+import { BASE_URL, ENV } from "@env";
+import InfoIcon from "../assets/icons/error.svg";
+import WarningIcon from "../assets/icons/warning.svg";
+import DangerIcon from "../assets/icons/danger.svg";
+
+export default {
+ ENV,
+ BASE_URL,
+};
export const STORAGE_KEYS = {
- LANGUAGE: "USER_LANGUAGE",
- TOKEN: "AUTH_TOKEN",
- THEME: "APP_THEME",
+ LANGUAGE: "userLanguage",
+ AUTH_TOKEN: "authToken",
+ THEME: "appTheme",
+ REFRESH_TOKEN: "refreshToken",
};
export const APP_CONFIG = {
@@ -48,3 +58,43 @@ export const TAB_CONFIG = [
path: "/my-battery",
},
];
+
+export const MESSAGES = {
+ AUTHENTICATION: {
+ INVALID_TOKEN: "Invalid Token",
+ VERIFICATION_FAILED: "Verification failed, try again later",
+ },
+};
+
+export const AUTH_STATUSES = {
+ IDLE: "Idle",
+ LOADING: "Loading",
+ SUCCESS: "Success",
+ FAILED: "Failed",
+} as const;
+
+export const ALERT_STYLES = {
+ info: {
+ backgroundColor: "#E5EBFD",
+ icon: InfoIcon,
+ },
+ warning: {
+ backgroundColor: "#FFF2E2",
+ icon: WarningIcon,
+ },
+ danger: {
+ backgroundColor: "#FFE9E9",
+ icon: DangerIcon,
+ },
+};
+
+export const SUPPORT = {
+ WHATSAPP_NUMBER: "918685846459",
+ WHATSAPP_PLACEHOLDER: "Hi, I need help regarding my vehicle.",
+ PHONE: "+911234567890",
+ EMAIL: "support@vecmocon.com",
+ EMAIL_SUBJECT: "Support Request",
+ EMAIL_BODY: "Hello,\n\nI need assistance with...",
+};
+
+export type StatusType = (typeof AUTH_STATUSES)[keyof typeof AUTH_STATUSES];