Khởi tạo ban đầu
This commit is contained in:
35
config/auth.ts
Normal file
35
config/auth.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { DOMAIN, TOKEN } from "@/constants";
|
||||
import { removeStorageItem } from "@/utils/storage";
|
||||
import { Router } from "expo-router";
|
||||
|
||||
let routerInstance: Router | null = null;
|
||||
|
||||
/**
|
||||
* Set router instance để dùng trong non-component context
|
||||
*/
|
||||
export const setRouterInstance = (router: Router) => {
|
||||
routerInstance = router;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle 401 error - redirect to login
|
||||
*/
|
||||
export const handle401 = () => {
|
||||
if (routerInstance) {
|
||||
removeStorageItem(TOKEN);
|
||||
// Cancel all pending requests to prevent further API calls
|
||||
if (typeof window !== "undefined" && (window as any).axiosAbortController) {
|
||||
(window as any).axiosAbortController.abort();
|
||||
}
|
||||
routerInstance.navigate("/auth/login");
|
||||
} else {
|
||||
console.warn("Router instance not set, cannot redirect to login");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear router instance (optional, for cleanup)
|
||||
*/
|
||||
export const clearRouterInstance = () => {
|
||||
routerInstance = null;
|
||||
};
|
||||
83
config/axios.ts
Normal file
83
config/axios.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { DOMAIN, TOKEN } from "@/constants";
|
||||
import { showErrorToast } from "@/services/toast_service";
|
||||
import { getStorageItem } from "@/utils/storage";
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
import { handle401 } from "./auth";
|
||||
|
||||
const codeMessage = {
|
||||
200: "The server successfully returned the requested data。",
|
||||
201: "New or modified data succeeded。",
|
||||
202: "A request has been queued in the background (asynchronous task)。",
|
||||
204: "Data deleted successfully。",
|
||||
400: "There is an error in the request sent, the server did not perform the operation of creating or modifying data。",
|
||||
401: "The user does not have permission (token, username, password is wrong) 。",
|
||||
403: "User is authorized, but access is prohibited。",
|
||||
404: "The request issued was for a non-existent record, the server did not operate。",
|
||||
406: "The requested format is not available。",
|
||||
410: "The requested resource is permanently deleted and will no longer be available。",
|
||||
422: "When creating an object, a validation error occurred。",
|
||||
500: "Server error, please check the server。",
|
||||
502: "Gateway error。",
|
||||
503: "Service unavailable, server temporarily overloaded or maintained。",
|
||||
504: "Gateway timeout。",
|
||||
};
|
||||
|
||||
// Tạo instance axios với cấu hình cơ bản
|
||||
const api: AxiosInstance = axios.create({
|
||||
baseURL: "https://sgw.gms.vn",
|
||||
timeout: 20000, // Timeout 20 giây
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
console.log("🚀 Axios baseURL:", api.defaults.baseURL);
|
||||
|
||||
// Interceptor cho request (thêm token nếu cần)
|
||||
api.interceptors.request.use(
|
||||
async (config) => {
|
||||
// Thêm auth token nếu có
|
||||
const token = await getStorageItem(TOKEN);
|
||||
if (token) {
|
||||
config.headers.Authorization = `${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Interceptor cho response (xử lý lỗi chung)
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
if (!error.response) {
|
||||
const networkErrorMsg =
|
||||
error.message || "Network error - please check connection";
|
||||
showErrorToast("Lỗi kết nối");
|
||||
console.error("Response Network Error: ", networkErrorMsg);
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
const { status, statusText, data } = error.response;
|
||||
|
||||
// Ưu tiên: codeMessage → backend message → statusText
|
||||
const errMsg =
|
||||
codeMessage[status as keyof typeof codeMessage] ||
|
||||
data?.message ||
|
||||
statusText ||
|
||||
"Unknown error";
|
||||
|
||||
showErrorToast(`Lỗi ${status}: ${errMsg}`);
|
||||
if (status === 401) {
|
||||
handle401();
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default api;
|
||||
3
config/index.ts
Normal file
3
config/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./auth";
|
||||
export { default as api } from "./axios";
|
||||
export * from "./toast";
|
||||
2
config/localization.ts
Normal file
2
config/localization.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { useI18n } from "@/hooks/use-i18n";
|
||||
export { default as i18n } from "./localization/i18n";
|
||||
27
config/localization/i18n.ts
Normal file
27
config/localization/i18n.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import en from "@/locales/en.json";
|
||||
import vi from "@/locales/vi.json";
|
||||
import { getLocales } from "expo-localization";
|
||||
import { I18n } from "i18n-js";
|
||||
|
||||
// Set the key-value pairs for the different languages you want to support
|
||||
const translations = {
|
||||
en,
|
||||
vi,
|
||||
};
|
||||
|
||||
const i18n = new I18n(translations);
|
||||
|
||||
// Set the locale once at the beginning of your app
|
||||
// This will be set from storage in the useI18n hook, default to device language or 'en'
|
||||
i18n.locale = getLocales()[0].languageCode ?? "vi";
|
||||
|
||||
// Enable fallback mechanism - if a key is missing in the current language, it will use the key from English
|
||||
i18n.enableFallback = true;
|
||||
|
||||
// Set default locale to English if no locale is available
|
||||
i18n.defaultLocale = "vi";
|
||||
|
||||
// Storage key for locale preference
|
||||
export const LOCALE_STORAGE_KEY = "app_locale_preference";
|
||||
|
||||
export default i18n;
|
||||
214
config/toast.tsx
Normal file
214
config/toast.tsx
Normal file
@@ -0,0 +1,214 @@
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import { View } from "react-native";
|
||||
import {
|
||||
BaseToast,
|
||||
BaseToastProps,
|
||||
SuccessToast,
|
||||
} from "react-native-toast-message";
|
||||
|
||||
export const Colors: any = {
|
||||
light: {
|
||||
text: "#000",
|
||||
back: "#ffffff",
|
||||
},
|
||||
dark: {
|
||||
text: "#ffffff",
|
||||
back: "#2B2D2E",
|
||||
},
|
||||
default: "#3498db",
|
||||
info: "#3498db",
|
||||
success: "#07bc0c",
|
||||
warn: {
|
||||
background: "#ffffff",
|
||||
text: "black",
|
||||
iconColor: "#f1c40f",
|
||||
},
|
||||
error: {
|
||||
background: "#ffffff",
|
||||
text: "black",
|
||||
iconColor: "#e74c3c",
|
||||
},
|
||||
textDefault: "#4c4c4c",
|
||||
textDark: "black",
|
||||
};
|
||||
|
||||
export const toastConfig = {
|
||||
success: (props: BaseToastProps) => (
|
||||
<SuccessToast
|
||||
{...props}
|
||||
style={{
|
||||
borderLeftColor: Colors.success,
|
||||
backgroundColor: Colors.success,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: 10,
|
||||
}}
|
||||
text1Style={{
|
||||
color: "white",
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
}}
|
||||
text2Style={{
|
||||
color: "#f0f0f0",
|
||||
}}
|
||||
renderLeadingIcon={() => (
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 40,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="check-circle" size={30} color="white" />
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
default: (props: BaseToastProps) => (
|
||||
<BaseToast
|
||||
{...props}
|
||||
style={{
|
||||
borderLeftColor: Colors.default,
|
||||
backgroundColor: Colors.default,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: 10,
|
||||
}}
|
||||
text1Style={{
|
||||
color: "white",
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
}}
|
||||
text2Style={{
|
||||
color: "#f0f0f0",
|
||||
}}
|
||||
renderLeadingIcon={() => (
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 50,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="info" size={30} color="white" />
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
info: (props: BaseToastProps) => (
|
||||
<BaseToast
|
||||
{...props}
|
||||
style={{
|
||||
borderLeftColor: Colors.info,
|
||||
backgroundColor: Colors.info,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: 10,
|
||||
}}
|
||||
text1Style={{
|
||||
color: "white",
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
}}
|
||||
text2Style={{
|
||||
color: "#f0f0f0",
|
||||
}}
|
||||
renderLeadingIcon={() => (
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 50,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="info-outline" size={30} color="white" />
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
warn: (props: BaseToastProps) => (
|
||||
<BaseToast
|
||||
{...props}
|
||||
style={{
|
||||
borderLeftColor: Colors.warn.background,
|
||||
backgroundColor: Colors.warn.background,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: 10,
|
||||
}}
|
||||
text1Style={{
|
||||
color: Colors.warn.text,
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
}}
|
||||
text2Style={{
|
||||
color: "#333",
|
||||
}}
|
||||
renderLeadingIcon={() => (
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 50,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<MaterialIcons
|
||||
name="warning"
|
||||
size={30}
|
||||
color={Colors.warn.iconColor}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
error: (props: BaseToastProps) => (
|
||||
<BaseToast
|
||||
{...props}
|
||||
style={{
|
||||
borderLeftColor: Colors.error.background,
|
||||
backgroundColor: Colors.error.background,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: 10,
|
||||
}}
|
||||
text1Style={{
|
||||
color: Colors.error.text,
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
}}
|
||||
text2Style={{
|
||||
color: "#f0f0f0",
|
||||
}}
|
||||
renderLeadingIcon={() => (
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 50,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<MaterialIcons
|
||||
name="error"
|
||||
size={30}
|
||||
color={Colors.error.iconColor}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
};
|
||||
Reference in New Issue
Block a user