Setup api's and config
parent
bfd8be7414
commit
4457adfbe5
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
token: string;
|
||||
refreshToken: string;
|
||||
data: {
|
||||
success: boolean;
|
||||
message: string;
|
||||
token: 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;
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
Loading…
Reference in New Issue