Setup api's and config

feature/app-setup
vinay kumar 2025-07-05 22:58:43 +05:30
parent bfd8be7414
commit 4457adfbe5
9 changed files with 234 additions and 76 deletions

View File

@ -7,8 +7,9 @@ import { useRouter } from "expo-router";
export default function AuthLayout() {
const router = useRouter();
return (
<SafeAreaView style={styles.container} edges={["top"]}>
<>
<StatusBar style="dark" />
<Stack
screenOptions={{
@ -34,7 +35,7 @@ export default function AuthLayout() {
},
}}
/>
</SafeAreaView>
</>
);
}
// useEffect(() => {

View File

@ -63,22 +63,24 @@ export default function VerifyOTP() {
<View style={styles.body}>
<Text>
<Text style={styles.order}>{t("onboarding.enter-otp")}</Text>
<Text style={styles.phone}> +91 ********{phone?.slice(-2)}.</Text>
<Text style={styles.phone}> {phone}.</Text>
</Text>
<View style={styles.otpContainer}>
<OtpInput
numberOfDigits={4}
focusColor="#006C4D"
onTextChange={() => {
dispatch(clearVerifyOTPError());
}}
onFilled={(code) => {
dispatch(verifyOTP({ phone, otpId, otp: code }));
}}
focusColor="green"
autoFocus={true}
hideStick={true}
blurOnFilled={true}
disabled={false}
type="numeric"
secureTextEntry={false}
focusStickBlinkingDuration={500}
onTextChange={(text) => console.log(text)}
onFilled={(text) => {
dispatch(verifyOTP({ otpId: `${otpId}`, otp: text, phone }));
}}
textInputProps={{
accessibilityLabel: "One-Time Password",
}}
@ -87,6 +89,14 @@ export default function VerifyOTP() {
accessibilityLabel: "OTP digit",
allowFontScaling: false,
}}
theme={{
containerStyle: styles.digitContainer,
pinCodeContainerStyle: styles.pinCodeContainer,
pinCodeTextStyle: styles.pinCodeText,
focusStickStyle: styles.focusStick,
placeholderTextStyle: styles.placeholderText,
disabledPinCodeContainerStyle: styles.disabledPinCodeContainer,
}}
/>
{(verifyOTPError || sendOTPError) && (
@ -202,4 +212,48 @@ const styles = StyleSheet.create({
paddingTop: 8,
paddingBottom: 8,
},
digitContainer: {
width: "100%",
flexDirection: "row",
justifyContent: "center",
gap: 16,
alignItems: "center",
},
pinCodeContainer: {
width: 56,
height: 56,
borderWidth: 1,
borderColor: "#D8DDE7",
borderRadius: 4,
backgroundColor: "#FFFFFF",
justifyContent: "center",
alignItems: "center",
},
disabledPinCodeContainer: {
backgroundColor: "#F2F4F7",
borderColor: "#E0E4EA",
},
pinCodeText: {
fontSize: 20,
fontWeight: "bold",
color: "#252A34",
textAlign: "center",
fontFamily: "Inter",
},
placeholderText: {
fontSize: 20,
color: "#B0B7C5",
textAlign: "center",
fontWeight: "bold",
},
focusStick: {
width: 2,
height: 24,
backgroundColor: "#252A34",
},
});

View File

@ -6,23 +6,19 @@ 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";
import type { BmsState } from "./types";
import { useTranslation } from "react-i18next";
export default {
ENV,
BASE_URL,
};
export const BASE_URL =
"https://eae2-2400-80c0-2001-9c6-50f0-eda-dcd7-4167.ngrok-free.app";
export const STORAGE_KEYS = {
LANGUAGE: "userLanguage",
AUTH_TOKEN: "authToken",
THEME: "appTheme",
REFRESH_TOKEN: "refreshToken",
};
export const APP_CONFIG = {

35
services/axiosClient.ts Normal file
View File

@ -0,0 +1,35 @@
import axios from "axios";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { BASE_URL, STORAGE_KEYS } from "../constants/config";
import { router } from "expo-router";
const api = axios.create({
baseURL: BASE_URL,
timeout: 10000,
});
//Request interceptor to add auth token
api.interceptors.request.use(async (config) => {
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
//Response interceptor to handle errors
api.interceptors.response.use(
(response) => response,
async (error) => {
const status = error.response?.status;
//if token is expired or not present, clear it from storage
if (status === 401 || status === 403) {
await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
router.replace("/auth/login");
}
return Promise.reject(error);
}
);
export default api;

View File

@ -30,9 +30,6 @@ export const setLanguage = async (language: string) => {
export const getLanguage = async () => {
const lang = await AsyncStorage.getItem(STORAGE_KEYS.LANGUAGE);
if (lang) {
i18next.changeLanguage(lang);
}
return lang;
};

View File

@ -10,13 +10,13 @@ import { BmsState } from "@/constants/types";
const SERVER_URL =
"http://dev.vec-tr.ai:8089/?dashboardId=deviceDashboardSocket&assetId=V16000868651064644504";
const TOKEN =
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2NCIsImFjdGlvbiI6ImF1dGgiLCJ0b2tlbi12ZXJzaW9uIjowLCJpYXQiOjE3NTE1Mzg0MDAsImV4cCI6MTc1MTYyNDgwMH0.QIGyV9_jbtv0F8YzbzIgn_669HJz2ftI8KckpPGN0UU";
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2NCIsImFjdGlvbiI6ImF1dGgiLCJ0b2tlbi12ZXJzaW9uIjowLCJpYXQiOjE3NTE2MDgwNjIsImV4cCI6MTc1MTY5NDQ2Mn0.-46Br0jSPwOTvkcDBTI05GJ1GavaAOOli6LEgkvjj3c";
let socket: Socket | null = null;
export const initSocket = () => {
store.dispatch(setTelemetryLoading());
console.log("Initializing socket connection...");
socket = io(SERVER_URL, {
transports: ["websocket"],
extraHeaders: {
@ -54,13 +54,15 @@ const handleSocketData = (data: any) => {
console.log("...");
try {
const SoH =
data.dataSeries.assetData[0].bms[0].bmsSpecific.ivecSpecific.soh;
const SoC = data?.dataSeries?.assetData?.[0]?.bms?.[0]?.batterySoc;
data.dataSeries.assetData[0].bms[0].bmsSpecific.ivecSpecific.soh ?? null;
const SoC = data?.dataSeries?.assetData?.[0]?.bms?.[0]?.batterySoc ?? null;
const currentMode =
data?.dataSeries?.assetData?.[0]?.bms?.[0]?.bmsSpecific?.ivecSpecific
?.ivecStatus?.currentMode;
?.ivecStatus?.currentMode ?? null;
const gps = data?.dataSeries?.locationData?.gps;
const gps = data?.dataSeries?.locationData?.gps ?? null;
const totalDistance = data?.dataSeries?.systemData?.odoMeter ?? null;
const lat = gps?.[1];
const lon = gps?.[2];
@ -84,6 +86,7 @@ const handleSocketData = (data: any) => {
lon,
loading: false,
error: null,
totalDistance,
})
);
} catch (err) {

View File

@ -2,10 +2,10 @@
// type: string;
// payload: T;
// }
import axios from "axios";
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as SecureStore from "expo-secure-store";
import urls, { STORAGE_KEYS, MESSAGES } from "../constants/config";
import { STORAGE_KEYS, MESSAGES, BASE_URL } from "../constants/config";
import { AUTH_STATUSES, StatusType } from "../constants/types";
interface AuthState {
@ -19,18 +19,23 @@ interface AuthState {
}
interface SendOTPResponse {
status: boolean;
success: boolean;
message: string;
data: {
otpId: number;
phone: string;
};
}
interface VerifyOTPResponse {
status: boolean;
success: boolean;
message: string;
data: {
success: boolean;
message: string;
token: string;
refreshToken: string;
token_expires_in: string;
};
}
interface SendOTPParams {
@ -40,7 +45,7 @@ interface SendOTPParams {
interface VerifyOTPParams {
phone: string | null;
otp: string | null;
otpId: number | null;
otpId: string | null;
}
const initialState: AuthState = {
@ -58,22 +63,15 @@ export const sendOTP = createAsyncThunk<SendOTPResponse, SendOTPParams>(
"auth/sendOTP",
async (params, { rejectWithValue }) => {
try {
console.log(urls.BASE_URL, "BASE_URL");
// const response = await axios.post<SendOTPResponse>(
// `${urls.BASE_URL}/send-otp`,
// params
// );
// return response.data;
return {
status: true,
message: "Done",
data: {
otpId: 22,
},
};
const response = await axios.post<SendOTPResponse>(
`${BASE_URL}/api/v1/send-otp`,
params
);
return response.data;
// if (!response.data.status) throw new Error(response.data.message);
} catch (error: any) {
return rejectWithValue(error.response?.data?.message || error.message);
const serverMessage = error.response?.data?.message;
return rejectWithValue(serverMessage || "Something went wrong");
}
}
);
@ -82,27 +80,22 @@ export const verifyOTP = createAsyncThunk<VerifyOTPResponse, VerifyOTPParams>(
"auth/verifyOTP",
async (params, { rejectWithValue }) => {
try {
return {
status: true,
message: "Done",
token: "token",
refreshToken: "refreshToken",
};
// const response = await axios.post<VerifyOTPResponse>(
// `${urls.BASE_URL}/verify-otp`,
// params
// );
// if (!response.data.status) throw new Error(response.data.message);
console.log("params", params);
const response = await axios.post<VerifyOTPResponse>(
`${BASE_URL}/api/v1/verify-otp`,
params
);
if (!response.data.success) throw new Error(response.data.message);
// // Store tokens
// await AsyncStorage.setItem(STORAGE_KEYS.AUTH_TOKEN, response.data.token);
// await SecureStore.setItemAsync(
// STORAGE_KEYS.REFRESH_TOKEN,
// response.data.refreshToken
// );
// Store tokens
await AsyncStorage.setItem(
STORAGE_KEYS.AUTH_TOKEN,
response.data.data.token
);
// return response.data;
return response.data;
} catch (error: any) {
console.log(error.message);
return rejectWithValue(MESSAGES.AUTHENTICATION.VERIFICATION_FAILED);
}
}
@ -112,7 +105,6 @@ export const logout = createAsyncThunk(
"auth/logout",
async (_, { dispatch }) => {
await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
await SecureStore.deleteItemAsync(STORAGE_KEYS.REFRESH_TOKEN);
dispatch(authSlice.actions.logout());
}
);
@ -159,12 +151,12 @@ const authSlice = createSlice({
.addCase(sendOTP.fulfilled, (state, action) => {
state.status = AUTH_STATUSES.SUCCESS;
state.otpId = action.payload.data.otpId;
state.phone = action.meta.arg.phone;
state.phone = action.payload.data.phone;
state.sendOTPError = null;
})
.addCase(sendOTP.rejected, (state, action) => {
state.status = AUTH_STATUSES.FAILED;
state.sendOTPError = action.error.message || "Failed to send OTP";
state.sendOTPError = (action.payload as string) || "Failed to send OTP";
})
.addCase(verifyOTP.pending, (state) => {
state.status = AUTH_STATUSES.LOADING;
@ -174,18 +166,10 @@ const authSlice = createSlice({
state.status = AUTH_STATUSES.SUCCESS;
state.isLoggedIn = true;
state.verifyOTPError = null;
const token = action.payload.token;
const token = action.payload.data.token;
AsyncStorage.setItem(STORAGE_KEYS.AUTH_TOKEN, token).catch((error) => {
console.log("Error storing token", error);
});
const refreshToken = action.payload.refreshToken;
SecureStore.setItemAsync(
STORAGE_KEYS.REFRESH_TOKEN,
refreshToken
).catch((error) => {
console.log("Error storing refresh token", error);
});
})
.addCase(verifyOTP.rejected, (state, action) => {
state.status = AUTH_STATUSES.FAILED;

View File

@ -9,6 +9,7 @@ interface TelemetryState {
lon: number | null;
loading: boolean;
error: string | null;
totalDistance: number | null;
}
const initialState: TelemetryState = {
@ -19,6 +20,7 @@ const initialState: TelemetryState = {
lon: null,
loading: false,
error: null,
totalDistance: null,
};
export const telemetrySlice = createSlice({

86
store/userSlice.ts Normal file
View File

@ -0,0 +1,86 @@
// src/store/userSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import api from "../services/axiosClient";
import { BASE_URL } from "@/constants/config";
interface Vehicle {
vehicle_id: number;
model: string;
chasis_number: string;
}
interface UserData {
user_id: number;
name: string;
mobile: string;
email: string | null;
profile_url: string | null;
vehicles: Vehicle[];
}
interface UserState {
data: UserData | null;
loading: boolean;
error: string | null;
}
const initialState: UserState = {
data: null,
loading: false,
error: null,
};
// 🔁 Thunk to get user details
export const getUserDetails = createAsyncThunk<UserData>(
"user/getUserDetails",
async (_, { rejectWithValue }) => {
try {
const response = await api.get(`${BASE_URL}/api/v1/get-user-details`);
if (response.data.success) {
return response.data.data;
} else {
return rejectWithValue("Failed to fetch user data");
}
} catch (error: any) {
const message =
error.response?.data?.message ||
error.message ||
"Something went wrong";
return rejectWithValue(message);
}
}
);
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
clearUser: (state) => {
state.data = null;
state.error = null;
state.loading = false;
},
},
extraReducers: (builder) => {
builder
.addCase(getUserDetails.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(
getUserDetails.fulfilled,
(state, action: PayloadAction<UserData>) => {
state.loading = false;
state.data = action.payload;
}
)
.addCase(getUserDetails.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
});
},
});
export const { clearUser } = userSlice.actions;
export default userSlice.reducer;