Add:
- context/ - hook/ - types/ ### No Issue
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { AuthProvider } from "@/context/AuthContext";
|
||||||
import AppRoot from "@/screens/RootLayout/AppRoot";
|
import AppRoot from "@/screens/RootLayout/AppRoot";
|
||||||
import "react-native-gesture-handler";
|
import "react-native-gesture-handler";
|
||||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||||
@@ -7,7 +8,9 @@ export default function RootLayout() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SafeAreaProvider>
|
<SafeAreaProvider>
|
||||||
<AppRoot />
|
<AppRoot />
|
||||||
|
{/* <AuthProvider>
|
||||||
|
</AuthProvider> */}
|
||||||
</SafeAreaProvider>
|
</SafeAreaProvider>
|
||||||
<Toast />
|
<Toast />
|
||||||
</>
|
</>
|
||||||
|
|||||||
7
bun.lock
7
bun.lock
@@ -5,6 +5,7 @@
|
|||||||
"name": "hipmi-mobile",
|
"name": "hipmi-mobile",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^14.1.0",
|
"@expo/vector-icons": "^14.1.0",
|
||||||
|
"@react-native-async-storage/async-storage": "2.1.2",
|
||||||
"@react-native-community/datetimepicker": "8.4.1",
|
"@react-native-community/datetimepicker": "8.4.1",
|
||||||
"@react-navigation/bottom-tabs": "^7.4.2",
|
"@react-navigation/bottom-tabs": "^7.4.2",
|
||||||
"@react-navigation/drawer": "^7.5.2",
|
"@react-navigation/drawer": "^7.5.2",
|
||||||
@@ -484,6 +485,8 @@
|
|||||||
|
|
||||||
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
|
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
|
||||||
|
|
||||||
|
"@react-native-async-storage/async-storage": ["@react-native-async-storage/async-storage@2.1.2", "", { "dependencies": { "merge-options": "^3.0.4" }, "peerDependencies": { "react-native": "^0.0.0-0 || >=0.65 <1.0" } }, "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow=="],
|
||||||
|
|
||||||
"@react-native-community/datetimepicker": ["@react-native-community/datetimepicker@8.4.1", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": ">=52.0.0", "react": "*", "react-native": "*", "react-native-windows": "*" }, "optionalPeers": ["expo", "react-native-windows"] }, "sha512-DrK+CUS5fZnz8dhzBezirkzQTcNDdaXer3oDLh0z4nc2tbdIdnzwvXCvi8IEOIvleoc9L95xS5tKUl0/Xv71Mg=="],
|
"@react-native-community/datetimepicker": ["@react-native-community/datetimepicker@8.4.1", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": ">=52.0.0", "react": "*", "react-native": "*", "react-native-windows": "*" }, "optionalPeers": ["expo", "react-native-windows"] }, "sha512-DrK+CUS5fZnz8dhzBezirkzQTcNDdaXer3oDLh0z4nc2tbdIdnzwvXCvi8IEOIvleoc9L95xS5tKUl0/Xv71Mg=="],
|
||||||
|
|
||||||
"@react-native/assets-registry": ["@react-native/assets-registry@0.79.5", "", {}, "sha512-N4Kt1cKxO5zgM/BLiyzuuDNquZPiIgfktEQ6TqJ/4nKA8zr4e8KJgU6Tb2eleihDO4E24HmkvGc73naybKRz/w=="],
|
"@react-native/assets-registry": ["@react-native/assets-registry@0.79.5", "", {}, "sha512-N4Kt1cKxO5zgM/BLiyzuuDNquZPiIgfktEQ6TqJ/4nKA8zr4e8KJgU6Tb2eleihDO4E24HmkvGc73naybKRz/w=="],
|
||||||
@@ -1366,6 +1369,8 @@
|
|||||||
|
|
||||||
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
|
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
|
||||||
|
|
||||||
|
"is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="],
|
||||||
|
|
||||||
"is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
|
"is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
|
||||||
|
|
||||||
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
|
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
|
||||||
@@ -1566,6 +1571,8 @@
|
|||||||
|
|
||||||
"memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="],
|
"memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="],
|
||||||
|
|
||||||
|
"merge-options": ["merge-options@3.0.4", "", { "dependencies": { "is-plain-obj": "^2.1.0" } }, "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ=="],
|
||||||
|
|
||||||
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
|
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
|
||||||
|
|
||||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||||
|
|||||||
92
context/AuthContext.tsx
Normal file
92
context/AuthContext.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import { IUser } from "@/types/User";
|
||||||
|
import { createContext, useEffect, useState } from "react";
|
||||||
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
|
import { apiClient, apiLogin } from "@/service/api";
|
||||||
|
|
||||||
|
// --- Types ---
|
||||||
|
type AuthContextType = {
|
||||||
|
user: IUser | null;
|
||||||
|
token: string | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
isAdmin: boolean;
|
||||||
|
isUserActive: boolean;
|
||||||
|
loginWithNomor: (nomor: string) => Promise<void>;
|
||||||
|
// validateOtp: (nomor: string, otp: string) => Promise<void>;
|
||||||
|
// logout: () => Promise<void>;
|
||||||
|
// registerUser: (userData: {
|
||||||
|
// username: string;
|
||||||
|
// nomor: string;
|
||||||
|
// }) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Create Context ---
|
||||||
|
export const AuthContext = createContext<AuthContextType | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [user, setUser] = useState<IUser | null>(null);
|
||||||
|
const [token, setToken] = useState<string | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const isAuthenticated = !!user;
|
||||||
|
const isAdmin = user?.MasterUserRole?.name === "Admin";
|
||||||
|
const isUserActive = user?.active === true;
|
||||||
|
|
||||||
|
// --- Load session from AsyncStorage on app start ---
|
||||||
|
useEffect(() => {
|
||||||
|
const loadSession = async () => {
|
||||||
|
try {
|
||||||
|
const storedToken = await AsyncStorage.getItem("authToken");
|
||||||
|
const storedUser = await AsyncStorage.getItem("userData");
|
||||||
|
|
||||||
|
if (storedToken && storedUser) {
|
||||||
|
setToken(storedToken);
|
||||||
|
setUser(JSON.parse(storedUser));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load session", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadSession();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// --- 1. Kirim nomor → dapat OTP ---
|
||||||
|
const loginWithNomor = async (nomor: string) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await apiLogin({ nomor: nomor });
|
||||||
|
console.log("Response provider login", response);
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error.response?.data?.message || "Gagal kirim OTP");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AuthContext.Provider
|
||||||
|
value={{
|
||||||
|
user,
|
||||||
|
token,
|
||||||
|
isLoading,
|
||||||
|
isAuthenticated,
|
||||||
|
isAdmin,
|
||||||
|
isUserActive,
|
||||||
|
loginWithNomor,
|
||||||
|
// validateOtp,
|
||||||
|
// logout,
|
||||||
|
// registerUser,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
10
hook/use-auth.ts
Normal file
10
hook/use-auth.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { AuthContext } from "@/context/AuthContext";
|
||||||
|
import { useContext } from "react";
|
||||||
|
|
||||||
|
export const useAuth = () => {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useAuth must be used within an AuthProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^14.1.0",
|
"@expo/vector-icons": "^14.1.0",
|
||||||
|
"@react-native-async-storage/async-storage": "2.1.2",
|
||||||
"@react-native-community/datetimepicker": "8.4.1",
|
"@react-native-community/datetimepicker": "8.4.1",
|
||||||
"@react-navigation/bottom-tabs": "^7.4.2",
|
"@react-navigation/bottom-tabs": "^7.4.2",
|
||||||
"@react-navigation/drawer": "^7.5.2",
|
"@react-navigation/drawer": "^7.5.2",
|
||||||
|
|||||||
@@ -129,8 +129,7 @@ export default function LoginView() {
|
|||||||
Login
|
Login
|
||||||
</ButtonCustom>
|
</ButtonCustom>
|
||||||
<Spacing />
|
<Spacing />
|
||||||
|
{/* <ButtonCustom onPress={() => router.navigate("/waiting-room")}>
|
||||||
{/* <ButtonCustom onPress={() => router.navigate("/admin/investment")}>
|
|
||||||
Admin ( Delete Soon )
|
Admin ( Delete Soon )
|
||||||
</ButtonCustom> */}
|
</ButtonCustom> */}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import { OtpInput } from "react-native-otp-entry";
|
|||||||
import Toast from "react-native-toast-message";
|
import Toast from "react-native-toast-message";
|
||||||
|
|
||||||
export default function VerificationView() {
|
export default function VerificationView() {
|
||||||
const { kodeId } = useLocalSearchParams();
|
const { kodeId, nomor } = useLocalSearchParams();
|
||||||
|
console.log("nomor", nomor);
|
||||||
|
|
||||||
const [codeOtp, setCodeOtp] = useState<string>("");
|
const [codeOtp, setCodeOtp] = useState<string>("");
|
||||||
const [inputOtp, setInputOtp] = useState<string>("");
|
const [inputOtp, setInputOtp] = useState<string>("");
|
||||||
const [nomor, setNomor] = useState<string>("");
|
const [userNumber, setUserNumber] = useState<string>("");
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -25,7 +27,7 @@ export default function VerificationView() {
|
|||||||
const response = await apiCheckCodeOtp({ kodeId: kodeId });
|
const response = await apiCheckCodeOtp({ kodeId: kodeId });
|
||||||
console.log("response ", JSON.stringify(response, null, 2));
|
console.log("response ", JSON.stringify(response, null, 2));
|
||||||
setCodeOtp(response.otp);
|
setCodeOtp(response.otp);
|
||||||
setNomor(response.nomor);
|
setUserNumber(response.nomor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleVerification = async () => {
|
const handleVerification = async () => {
|
||||||
@@ -47,7 +49,7 @@ export default function VerificationView() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await apiValidationCode({ nomor: nomor });
|
const response = await apiValidationCode({ nomor: userNumber });
|
||||||
console.log("response ", JSON.stringify(response, null, 2));
|
console.log("response ", JSON.stringify(response, null, 2));
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
@@ -61,7 +63,7 @@ export default function VerificationView() {
|
|||||||
router.replace("/(application)/(user)/waiting-room");
|
router.replace("/(application)/(user)/waiting-room");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
router.replace(`/register?nomor=${nomor}`);
|
router.replace(`/register?nomor=${userNumber}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error verification", error);
|
console.log("Error verification", error);
|
||||||
@@ -79,7 +81,9 @@ export default function VerificationView() {
|
|||||||
<Text style={GStyles.authTitle}>Verifikasi KOde OTP</Text>
|
<Text style={GStyles.authTitle}>Verifikasi KOde OTP</Text>
|
||||||
<Spacing height={30} />
|
<Spacing height={30} />
|
||||||
<Text style={GStyles.textLabel}>Masukan 4 digit kode otp</Text>
|
<Text style={GStyles.textLabel}>Masukan 4 digit kode otp</Text>
|
||||||
<Text style={GStyles.textLabel}>Yang di kirim ke +{nomor}</Text>
|
<Text style={GStyles.textLabel}>
|
||||||
|
Yang di kirim ke +{userNumber}
|
||||||
|
</Text>
|
||||||
<Spacing height={30} />
|
<Spacing height={30} />
|
||||||
<OtpInput
|
<OtpInput
|
||||||
disabled={codeOtp === ""}
|
disabled={codeOtp === ""}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import axios, { AxiosInstance } from "axios";
|
import axios, { AxiosInstance } from "axios";
|
||||||
import Constants from "expo-constants";
|
import Constants from "expo-constants";
|
||||||
|
|
||||||
const API_BASE_URL = Constants.expoConfig?.extra?.API_BASE_URL;
|
const API_BASE_URL = Constants.expoConfig?.extra?.API_BASE_URL;
|
||||||
|
|
||||||
const api: AxiosInstance = axios.create({
|
// const API_BASE_URL = process.env.API_BASE_URL
|
||||||
|
|
||||||
|
export const apiClient: AxiosInstance = axios.create({
|
||||||
baseURL: API_BASE_URL,
|
baseURL: API_BASE_URL,
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -11,26 +12,40 @@ const api: AxiosInstance = axios.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// apiClient.interceptors.request.use(
|
||||||
|
// (config) => {
|
||||||
|
// const token = localStorage.getItem("token");
|
||||||
|
// if (token) {
|
||||||
|
// config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
// }
|
||||||
|
// return config;
|
||||||
|
// },
|
||||||
|
// (error) => {
|
||||||
|
// return Promise.reject(error);
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
export async function apiVersion() {
|
export async function apiVersion() {
|
||||||
console.log("API_BASE_URL", API_BASE_URL);
|
console.log("API_BASE_URL", API_BASE_URL);
|
||||||
const response = await api.get("/version");
|
const response = await apiClient.get("/version");
|
||||||
|
console.log("Response version", response.data);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function apiLogin({ nomor }: { nomor: string }) {
|
export async function apiLogin({ nomor }: { nomor: string }) {
|
||||||
const response = await api.post("/auth/login", {
|
const response = await apiClient.post("/auth/login", {
|
||||||
nomor: nomor,
|
nomor: nomor,
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function apiCheckCodeOtp({ kodeId }: { kodeId: string }) {
|
export async function apiCheckCodeOtp({ kodeId }: { kodeId: string }) {
|
||||||
const response = await api.get(`/auth/check/${kodeId}`);
|
const response = await apiClient.get(`/auth/check/${kodeId}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function apiValidationCode({ nomor }: { nomor: string }) {
|
export async function apiValidationCode({ nomor }: { nomor: string }) {
|
||||||
const response = await api.post(`/auth/validasi`, {
|
const response = await apiClient.post(`/auth/validasi`, {
|
||||||
nomor: nomor,
|
nomor: nomor,
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
@@ -41,7 +56,7 @@ export async function apiRegister({
|
|||||||
}: {
|
}: {
|
||||||
data: { nomor: string; username: string };
|
data: { nomor: string; username: string };
|
||||||
}) {
|
}) {
|
||||||
const response = await api.post(`/auth/register`, {
|
const response = await apiClient.post(`/auth/register`, {
|
||||||
data: data,
|
data: data,
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|||||||
17
types/User.ts
Normal file
17
types/User.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export type TUser = "User" | "Admin" | "Super Admin";
|
||||||
|
|
||||||
|
export interface IMasterUserRole {
|
||||||
|
id: string;
|
||||||
|
name: TUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUser {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
nomor: string;
|
||||||
|
active: boolean;
|
||||||
|
createdAt: string | null;
|
||||||
|
updatedAt: string | null;
|
||||||
|
masterUserRoleId: string;
|
||||||
|
MasterUserRole: IMasterUserRole;
|
||||||
|
}
|
||||||
5
types/env.d.ts
vendored
Normal file
5
types/env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
declare namespace NodeJS {
|
||||||
|
interface ProcessEnv {
|
||||||
|
API_BASE_URL?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user