Feature/migration

This commit is contained in:
AmirHossein Mahmoodi
2025-05-05 09:57:50 +00:00
parent 18af64b3f6
commit 9bcddb6610
381 changed files with 9465 additions and 12983 deletions

View File

@@ -1,97 +0,0 @@
import {FA_DATATABLE_LOCALIZATION} from "&/locales/fa/datatable";
import {useRouter} from "next/router";
import {createContext, useEffect, useState} from "react";
import useUser from "../hooks/useUser";
export const LanguageContext = createContext();
export const LanguageProvider = ({children}) => {
const router = useRouter();
const languageList = [
{
key: "fa",
dir: "rtl",
name: "فارسی",
fontFamily: `IRANSans, sans-serif`,
tableLocalization: FA_DATATABLE_LOCALIZATION,
}
];
const {user, userChangedLanguage, changeLanguageState} = useUser();
const [languageIsReady, setLanguageIsReady] = useState(false);
const [languageApp, setLanguageApp] = useState(process.env.NEXT_PUBLIC_DEFAULT_LANGUAGE);
const [directionApp, setDirectionApp] = useState(
process.env.NEXT_PUBLIC_DEFAULT_DIRECTION
);
useEffect(() => {
const lang = localStorage.getItem("_language");
if (lang) {
setLanguageApp(lang);
}
}, []);
useEffect(() => {
if (!languageApp) return;
const lang = localStorage.getItem("_language");
if (!lang) {
localStorage.setItem("_language", languageApp);
} else {
if (languageApp != lang) {
localStorage.setItem("_language", languageApp);
}
}
}, [languageApp]);
useEffect(() => {
if (!router.isReady) return;
if (user.user_language) {
if (user.user_language != languageApp) {
setLanguageApp(user.user_language);
return;
}
}
if (languageApp != router.locale) {
router.replace(
{pathname: router.pathname, query: router.query},
router.asPath,
{
locale: languageApp,
}
);
return;
}
for (const lang of languageList) {
if (languageApp != lang.key) continue;
setDirectionApp(lang.dir);
document.dir = lang.dir;
}
const timer = setTimeout(() => {
changeLanguageState(false);
setLanguageIsReady(true);
}, 1000);
return () => {
clearTimeout(timer);
};
}, [router.locale, router.isReady, userChangedLanguage, languageApp]);
return (
<LanguageContext.Provider
value={{
languageApp,
setLanguageApp,
directionApp,
languageIsReady,
setLanguageIsReady,
languageList,
}}
>
{children}
</LanguageContext.Provider>
);
};

View File

@@ -1,14 +0,0 @@
import LoadingHardPage from "@/core/components/LoadingHardPage";
import {createContext, useState} from "react";
export const LoadingContext = createContext();
export const LoadingProvider = ({children}) => {
const [loadingPage, setLoadingPage] = useState(false);
return (
<LoadingContext.Provider value={{loadingPage, setLoadingPage}}>
<LoadingHardPage loading={loadingPage}/>
{children}
</LoadingContext.Provider>
);
};

View File

@@ -1,75 +0,0 @@
import {createContext, useEffect, useMemo, useRef, useState} from "react";
import {io} from "socket.io-client";
import useUser from "@/lib/app/hooks/useUser";
import {useTranslations} from "next-intl";
import {toast} from "react-toastify";
import PowerIcon from "@mui/icons-material/Power";
import PowerOffIcon from "@mui/icons-material/PowerOff";
import useDirection from "@/lib/app/hooks/useDirection";
export const SocketContext = createContext()
export const SocketProvider = ({children}) => {
const {user, isAuth} = useUser()
const [connectionError, setConnectionError] = useState(false)
const socketToastId = useRef(null);
const t = useTranslations()
const {directionApp} = useDirection();
const socket = useMemo(() => io(process.env.NEXT_PUBLIC_SERVER_SOCKET_URL, {
autoConnect: false,
// transports: ['websocket'],
auth: {
token: ""
}
}), [])
useEffect(() => {
if (isAuth) {
socket.auth.token = user.telephone_id
return
}
socket.auth.token = ''
}, [isAuth]);
useEffect(() => {
const connectErrorHandler = () => {
setConnectionError(true)
}
const connectHandler = () => {
setConnectionError(false)
}
socket.on("connect_error", connectErrorHandler);
socket.on("connect", connectHandler);
return () => {
socket.off("connect_error", connectErrorHandler);
socket.off("connect", connectHandler);
}
}, []);
useEffect(() => {
if (!connectionError) {
toast.update(socketToastId.current, {
type: toast.TYPE.SUCCESS,
render: t('socket_is_connect_message'),
autoClose: 2000,
closeButton: true,
closeOnClick: true,
icon: <PowerIcon/>
});
return
}
socketToastId.current = toast.warn(t('socket_is_not_connect_message'), {
containerId: 'connection',
draggable: false,
autoClose: false, closeButton: false, closeOnClick: false, icon: <PowerOffIcon/>
})
}, [connectionError]);
return (
<SocketContext.Provider value={{socket}}>
{children}
</SocketContext.Provider>
)
}

View File

@@ -1,37 +0,0 @@
import {createContext, useReducer} from "react";
import {toast} from "react-toastify";
export const ToastContext = createContext()
const reducer = (state, action) => {
switch (action.type) {
case "PUSH":
return {
...state,
[action.toast_type]: [...state[action.toast_type], action.toast_id]
};
case "DISMISS":
action.toast_type.map((item) => {
state[item].map((id) => {
toast.dismiss(id);
})
state[item] = []
});
return state;
}
};
export const ToastProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, {
pending: [],
error: [],
warning: [],
success: []
});
return (
<ToastContext.Provider value={{state, dispatch}}>
{children}
</ToastContext.Provider>
);
};

View File

@@ -1,125 +0,0 @@
import {GET_USER} from "@/core/data/apiRoutes";
import axios from "axios";
import {createContext, useCallback, useEffect, useReducer} from "react";
const initialUser = {
isAuth: false,
userChangedLanguage: false,
token: null,
user: {},
};
const reducer = (state, action) => {
switch (action.type) {
case "CLEAR_USER":
return {...state, user: {}};
case "CHANGE_USER":
return {...state, user: action.user};
case "CHANGE_USER_LANGUAGE":
return {
...state,
user: {...state.user, user_language: action.language},
};
case "CHANGE_AUTH_STATE":
return {...state, isAuth: action.isAuth};
case "CHANGE_LANGUAGE_STATE":
return {...state, userChangedLanguage: action.userChangedLanguage};
case "CLEAR_TOKEN":
localStorage.removeItem("_token");
return {...state, token: null};
case "SET_TOKEN":
localStorage.setItem("_token", action.token);
return {...state, token: action.token};
default:
return state;
}
};
export const UserContext = createContext();
export const UserProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, initialUser);
const clearUser = useCallback(() => {
dispatch({type: "CLEAR_USER"});
}, []);
const changeUser = useCallback((user) => {
dispatch({type: "CHANGE_USER", user});
}, []);
const changeUserLanguage = useCallback(/* use in multi language app */);
const changeAuthState = useCallback((isAuth) => {
dispatch({type: "CHANGE_AUTH_STATE", isAuth});
}, []);
const changeLanguageState = useCallback((userChangedLanguage) => {
dispatch({type: "CHANGE_LANGUAGE_STATE", userChangedLanguage});
}, []);
const clearToken = useCallback(() => {
dispatch({type: "CLEAR_TOKEN"});
}, []);
const setToken = useCallback((token) => {
dispatch({type: "SET_TOKEN", token});
}, []);
const getUser = useCallback(
(callback = () => {
}) => {
axios
.get(GET_USER, {
headers: {authorization: `Bearer ${state.token}`},
})
.then(({data}) => {
if (typeof callback === "function") callback(data.data);
})
.catch(error => {
if (error.response.status === 401) clearToken()
})
},
[state.token]
);
useEffect(() => {
const localToken = localStorage.getItem("_token");
if (localToken) dispatch({type: "SET_TOKEN", token: localToken});
}, []);
useEffect(() => {
if (!state.token) {
clearUser();
changeAuthState(false);
changeLanguageState(false);
return;
}
getUser((data) => {
changeUser(data);
changeAuthState(true);
changeLanguageState(true);
});
}, [state.token]);
return (
<UserContext.Provider
value={{
isAuth: state.isAuth,
userChangedLanguage: state.userChangedLanguage,
token: state.token,
user: state.user,
getUser,
clearUser,
changeUser,
changeUserLanguage,
changeAuthState,
changeLanguageState,
clearToken,
setToken,
}}
>
{children}
</UserContext.Provider>
);
};

View File

@@ -1,10 +0,0 @@
import {useContext} from "react";
import {LanguageContext} from "../contexts/language";
const useDirection = () => {
const {directionApp} = useContext(LanguageContext);
return {directionApp};
};
export default useDirection;

View File

@@ -1,28 +0,0 @@
import {useContext} from "react";
import {LanguageContext} from "../contexts/language";
import useUser from "./useUser";
const useLanguage = () => {
const {isAuth, changeUserLanguage} = useUser();
const {
languageApp,
setLanguageApp,
languageIsReady,
setLanguageIsReady,
languageList,
} = useContext(LanguageContext);
const changeLanguage = (lang) => {
if (lang == languageApp) return;
setLanguageIsReady(false);
setLanguageApp(lang);
if (isAuth) {
changeUserLanguage(lang);
}
};
return {languageApp, changeLanguage, languageIsReady, languageList};
};
export default useLanguage;

View File

@@ -1,10 +0,0 @@
import {useContext} from "react";
import {LoadingContext} from "../contexts/loading";
const useLoading = () => {
const {setLoadingPage} = useContext(LoadingContext);
return {setLoadingPage};
};
export default useLoading;

View File

@@ -1,34 +0,0 @@
import {useEffect, useState} from "react";
function useNetwork() {
const [state, setState] = useState(() => {
return {
online: navigator.onLine,
};
});
useEffect(() => {
const handleOnline = () => {
setState((prevState) => ({
...prevState,
online: true,
}));
};
const handleOffline = () => {
setState((prevState) => ({
...prevState,
online: false,
}));
};
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, []);
return state;
}
export default useNetwork;

View File

@@ -1,26 +0,0 @@
import useSWR from 'swr'
import {GET_SIDEBAR_NOTIFICATION} from "@/core/data/apiRoutes";
import useRequest from "@/lib/app/hooks/useRequest";
const GLOBAL_HAS_VALUE = process.env.NEXT_PUBLIC_HAS_VALUE;
const hasPermissionsValue = GLOBAL_HAS_VALUE ? JSON.parse(GLOBAL_HAS_VALUE) : [];
const isNotificationEnabled = hasPermissionsValue.includes("NEXT_PUBLIC_HAS_NOTIFICATION");
const useNotification = () => {
const requestServer = useRequest({auth: true, notification: false})
//swr config
const fetcher = (...args) => {
return requestServer(args, 'get').then((response) => {
return response.data.data;
}).catch(() => {
})
};
const {data, mutate} = useSWR( isNotificationEnabled ? GET_SIDEBAR_NOTIFICATION : '', fetcher , {keepPreviousData:true})
//swr config
// render data
return {notification_count: data, update_notification: mutate}
}
export default useNotification

View File

@@ -1,26 +0,0 @@
import useRequest from "@/lib/app/hooks/useRequest";
import useSWR from "swr";
import {GET_PERMISSIONS_LIST} from "@/core/data/apiRoutes";
const usePermissions = () => {
const requestServer = useRequest({auth: true, notification: false})
const fetcher = (...args) => {
return requestServer(args, 'get').then((response) => {
return response.data.data;
}).catch(() => {
})
};
const {data, isLoading} = useSWR(GET_PERMISSIONS_LIST, fetcher, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
keepPreviousData : true
})
const permissions_list = data
//swr config
// render data
return {permissions_list, isLoading}
}
export default usePermissions;

View File

@@ -1,30 +0,0 @@
import {GET_PROVINCE_LIST} from "@/core/data/apiRoutes";
import useRequest from "@/lib/app/hooks/useRequest";
import useSWR from "swr";
const useProvince = () => {
const requestServer = useRequest({auth: true, notification: false})
//swr config
const fetcher = (...args) => {
return requestServer(args, 'get').then(({data}) => {
return data.data;
}).catch(() => {
})
};
const {data, isLoading} = useSWR(GET_PROVINCE_LIST, fetcher, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
keepPreviousData: true
});
return {
provinceList: data,
isLoadingProvinceList: isLoading,
errorProvinceList: !data
}
};
export default useProvince;

View File

@@ -1,71 +0,0 @@
import axios from "axios";
import {successRequest} from "@/core/utils/succesHandler";
import {useTranslations} from "next-intl";
import useUser from "@/lib/app/hooks/useUser";
import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler";
import useNetwork from "@/lib/app/hooks/useNetwork";
import Notifications from "@/core/components/notifications";
import useToast from "@/lib/app/hooks/useToast";
const defaultOptions = {
auth: false, data: {}, requestOptions: {
headers: {}
}, notification: true, pending: true, success: {
notification: {
show: true,
},
}, failed: {
notification: {
show: true,
},
},
}
const useRequest = (initOptions) => {
const network = useNetwork()
const t = useTranslations()
const {token, clearToken} = useUser()
const {pushToastList, dismissToastList} = useToast();
let _options = {...defaultOptions, ...initOptions}
function requestServer(url = '', method = 'get', options) {
_options = {..._options, ...options}
if (_options.auth) _options = {
..._options, requestOptions: {
..._options.requestOptions,
headers: {..._options.requestOptions.headers, authorization: `Bearer ${token}`}
}
}
return new Promise((resolve, reject) => {
if (!network.online) {
reject()
return
}
if (_options.notification && _options.failed.notification.show && _options.pending) {
dismissToastList(["pending", "warning", "error", "success"]);
Notifications(pushToastList, "pending", t);
}
axios({
url: url, method: method, data: _options.data, ..._options.requestOptions
})
.then(response => {
successRequest(pushToastList, dismissToastList, response, t, _options)
resolve(response)
})
.catch(error => {
if (error.response) {
errorResponse(pushToastList, dismissToastList, error.response, clearToken, t, _options.notification && _options.failed.notification.show)
} else if (error.request) {
errorRequest(dismissToastList, t, _options.notification && _options.failed.notification.show)
} else {
errorSetting(dismissToastList, t, _options.notification && _options.failed.notification.show)
}
reject(error)
})
});
}
return requestServer
}
export default useRequest

View File

@@ -1,30 +0,0 @@
import {GET_ROLE_LIST} from "@/core/data/apiRoutes";
import useRequest from "@/lib/app/hooks/useRequest";
import useSWR from "swr";
const useRole = () => {
const requestServer = useRequest({auth: true, notification: false})
//swr config
const fetcher = (...args) => {
return requestServer(args, 'get').then(({data}) => {
return data.data;
}).catch(() => {
})
};
const {data, isLoading} = useSWR(GET_ROLE_LIST, fetcher, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
keepPreviousData: true
});
return {
roleList: data,
isLoadingRoleList: isLoading,
errorRoleList: !data
}
};
export default useRole;

View File

@@ -1,10 +0,0 @@
import {useContext} from "react";
import {SocketContext} from "@/lib/app/contexts/socket";
const useSocket = () => {
const {socket} = useContext(SocketContext)
return socket
}
export default useSocket

View File

@@ -1,21 +0,0 @@
import {useContext} from "react";
import {ToastContext} from "@/lib/app/contexts/toast";
const useToast = () => {
const {dispatch} = useContext(ToastContext);
const pushToastList = (toast_type, toast_id) => {
dispatch({type: "PUSH", toast_type, toast_id});
};
const dismissToastList = (toast_type) => {
dispatch({type: "DISMISS", toast_type});
};
return {
pushToastList,
dismissToastList
}
};
export default useToast;

View File

@@ -1,34 +0,0 @@
import {useContext} from "react";
import {UserContext} from "../contexts/user";
const useUser = () => {
const {
isAuth,
userChangedLanguage,
token,
user,
getUser,
clearUser,
changeUser,
changeAuthState,
changeLanguageState,
clearToken,
setToken,
} = useContext(UserContext);
return {
isAuth,
userChangedLanguage,
token,
user,
getUser,
clearUser,
changeUser,
changeAuthState,
changeLanguageState,
clearToken,
setToken,
};
};
export default useUser;

View File

@@ -1,87 +0,0 @@
import {createContext, useEffect, useReducer, useState} from "react";
import {GET_CATEGORY} from "@/core/data/apiRoutes";
import useRequest from "@/lib/app/hooks/useRequest";
const reducer = (state, action) => {
switch (action.type) {
case "CHANGE_ACTIVE_TAB":
return state.map((answer, index) =>
action.newValue === index
? {...answer, active: true}
: {
...answer,
active: false,
}
);
case "CHANGE_LIST":
return [...action.newList];
case "CHANGE_ACTIVE_ID":
return state.map((answer, index) =>
action.tab_id === answer.id
? {
...answer,
active_category_id: action.category_id,
}
: {...answer}
);
default:
return state;
}
};
const submission = (handleCategories, operation) => {
switch (operation.type) {
case "SET_CATEGORIES":
return {...handleCategories, categoryLists: operation.categoriesByAPI};
case "SET_SUBCATEGORIES":
return {...handleCategories, subCategoryLists: operation.subCategoriesByAPI};
default:
return handleCategories;
}
};
export const AnswersContext = createContext();
export const AnswersProvider = ({children}) => {
const requestServer = useRequest({auth: true, notification: false});
const [openCallDialog, setOpenCallDialog] = useState(false);
const [activeTab, setActiveTab] = useState(0);
const [handleCategories, operation] = useReducer(submission, {
categoryLists: [],
subCategoryLists: [],
});
const [state, dispatch] = useReducer(reducer, []);
useEffect(() => {
requestServer(GET_CATEGORY, "get")
.then(({data}) => {
const subCategoriesByAPI = data.data;
const categoriesByAPI = subCategoriesByAPI.reduce((accumulator, currentValue) => {
if (!accumulator.find((item) => item.category_id === currentValue.category_id)) {
accumulator.push(currentValue);
}
return accumulator;
}, []);
operation({type: "SET_CATEGORIES", categoriesByAPI});
operation({type: "SET_SUBCATEGORIES", subCategoriesByAPI});
})
.catch(() => {
});
}, []);
return (
<AnswersContext.Provider
value={{
openCallDialog,
setOpenCallDialog,
state,
dispatch,
activeTab,
setActiveTab,
categoryLists: handleCategories.categoryLists,
subCategoryLists: handleCategories.subCategoryLists,
}}
>
{children}
</AnswersContext.Provider>
);
};

View File

@@ -1,59 +0,0 @@
import { createContext, useCallback, useEffect, useReducer } from "react";
import useRequest from "@/lib/app/hooks/useRequest";
import { GET_CATEGORY } from "@/core/data/apiRoutes";
const reducer = (state, action) => {
switch (action.type) {
case "CHANGE_ACTIVE_ID":
return { ...state, active_id: action.id };
case "SET_CATEGORIES":
return { ...state, categoryLists: action.cat };
case "SET_SUBCATEGORIES":
return { ...state, subCategoryLists: action.subCat };
default:
return state;
}
};
export const CategoriesContext = createContext();
export const CategoriesProvider = ({ children }) => {
const requestServer = useRequest({ auth: true, notification: false });
const [state, dispatch] = useReducer(reducer, {
categoryLists: [],
subCategoryLists: [],
active_id: null,
});
useEffect(() => {
requestServer(GET_CATEGORY, "get")
.then(({ data }) => {
const subCat = data.data;
const cat = subCat.reduce((accumulator, currentValue) => {
if (!accumulator.find((item) => item.category_id === currentValue.category_id)) {
accumulator.push(currentValue);
}
return accumulator;
}, []);
dispatch({ type: "SET_CATEGORIES", cat });
dispatch({ type: "SET_SUBCATEGORIES", subCat });
})
.catch(() => {});
}, []);
const setCategoryID = useCallback((id) => {
dispatch({ type: "CHANGE_ACTIVE_ID", id });
});
return (
<CategoriesContext.Provider
value={{
categoryLists: state.categoryLists,
subCategoryLists: state.subCategoryLists,
active_id: state.active_id,
setCategoryID,
}}
>
{children}
</CategoriesContext.Provider>
);
};

View File

@@ -1,46 +0,0 @@
import { useContext } from "react";
import { AnswersContext } from "@/lib/callWidget/contexts/answers";
const useAnswers = () => {
const { state, dispatch, activeTab, setActiveTab } = useContext(AnswersContext);
const changeActiveTabAnswers = (newValue) => {
dispatch({ type: "CHANGE_ACTIVE_TAB", newValue });
setActiveTab(newValue);
};
const newAnswerTab = (data) => {
const newAnswer = {
id: data.id,
phone_number: data.phone_number,
date: new Date(),
active: true,
active_category_id: null,
};
const newList = [...state, newAnswer];
dispatch({ type: "CHANGE_LIST", newList });
changeActiveTabAnswers(newList.length - 1);
};
const closeAnswerTab = (id) => {
const index = state.findIndex((answer) => answer.id === id);
const newIndex = index === 0 ? 0 : index - 1;
const newList = [...state];
newList.splice(index, 1);
if (newList.length !== 0) {
newList[newIndex].active = true;
setActiveTab(newIndex);
}
dispatch({ type: "CHANGE_LIST", newList });
};
return {
answersList: state,
activeTab,
setActiveTab,
changeActiveTabAnswers,
closeAnswerTab,
newAnswerTab,
};
};
export default useAnswers;

View File

@@ -1,10 +0,0 @@
import {useContext} from "react";
import {AnswersContext} from "@/lib/callWidget/contexts/answers";
const useCallDialog = () => {
const {openCallDialog, setOpenCallDialog} = useContext(AnswersContext)
return {openCallDialog, setOpenCallDialog}
}
export default useCallDialog

View File

@@ -1,37 +0,0 @@
import useRequest from "@/lib/app/hooks/useRequest";
import {useEffect, useState} from "react";
import useSWR from "swr";
import {GET_CALLER_HISTORY} from "@/core/data/apiRoutes";
const useCallerHistory = (call_id, phone_number, max_size) => {
const requestServer = useRequest({auth: true, notification: false});
const [validData, setValidData] = useState([]);
//swr config
const fetcher = (...args) => {
return requestServer(args, 'get').then(({data}) => {
return data.data;
}).catch(() => {
})
};
const {data, isLoading} = useSWR(`${GET_CALLER_HISTORY}?phone_number=${phone_number}&size=${max_size}`, fetcher, {
revalidateIfStale: true,
revalidateOnFocus: false,
revalidateOnReconnect: false,
keepPreviousData: true
});
useEffect(() => {
data && setValidData(data.filter((item) => item.id != call_id));
}, [data, call_id]);
return {
firstItemOfHistory: validData.length === 0 ? false : validData[0],
historyList: validData.length < 2 ? false : validData.slice(1),
isLoadingHistoryList: isLoading,
errorHistoryList: !data
}
};
export default useCallerHistory;

View File

@@ -1,17 +0,0 @@
import { useContext } from "react";
import { AnswersContext } from "@/lib/callWidget/contexts/answers";
const useCategories = () => {
const { categoryLists, subCategoryLists, active_category_id, dispatch } = useContext(AnswersContext);
const setActiveCategoryID = (tab_id, category_id) => {
dispatch({ type: "CHANGE_ACTIVE_ID", tab_id, category_id });
};
return {
categoryLists,
subCategoryLists,
active_category_id,
setActiveCategoryID,
};
};
export default useCategories;

109
src/lib/contexts/auth.js Normal file
View File

@@ -0,0 +1,109 @@
"use client";
import { GET_USER_ROUTE } from "@/core/utils/routes";
import axios from "axios";
import {
createContext,
useCallback,
useContext,
useEffect,
useReducer,
useState,
} from "react";
const initAuth = {
initAuthState: false,
isAuth: false,
user: {},
};
const authReducer = (state, action) => {
switch (action.type) {
case "CLEAR_USER":
return { ...state, user: {} };
case "CHANGE_USER":
return { ...state, user: action.user };
case "CHANGE_AUTH_STATE":
return { ...state, isAuth: action.isAuth };
case "CHANGE_INIT_AUTH":
return { ...state, initAuthState: action.initAuthState };
default:
return state;
}
};
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [errorState, setErrorState] = useState({ status: null, message: "" });
const [state, dispatch] = useReducer(authReducer, initAuth);
const clearUser = useCallback(() => {
dispatch({ type: "CLEAR_USER" });
}, []);
const changeUser = useCallback((user) => {
dispatch({ type: "CHANGE_USER", user });
}, []);
const changeAuthState = useCallback((isAuth) => {
dispatch({ type: "CHANGE_AUTH_STATE", isAuth });
}, []);
const changeInitAuth = useCallback((initAuthState) => {
dispatch({ type: "CHANGE_INIT_AUTH", initAuthState });
}, []);
const logout = () => {
clearUser();
changeAuthState(false);
changeInitAuth(true);
};
const getUser = useCallback(async () => {
try {
const { data } = await axios.get(GET_USER_ROUTE, {
withCredentials: true,
});
changeUser(data.data);
changeAuthState(true);
changeInitAuth(true);
setErrorState(false);
} catch (error) {
if (error.response && error.response.status === 401) {
clearUser();
changeAuthState(false);
changeInitAuth(true);
return;
}
let status =
error.response && error.response.status
? error.response.status
: "نامشخص";
setErrorState({
status,
message: " مشکلی در احراز هویت رخ داده است . دقایقی بعد امتحان کنید!!!",
});
}
}, [clearUser, changeUser, changeAuthState, changeInitAuth]);
useEffect(() => {
getUser();
}, []);
return (
<AuthContext.Provider
value={{
user: state.user,
isAuth: state.isAuth,
initAuthState: state.initAuthState,
getUser,
logout,
errorState,
}}
>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);

106
src/lib/contexts/call.js Normal file
View File

@@ -0,0 +1,106 @@
"use client";
import {
createContext,
useReducer,
useState,
useCallback,
useMemo,
useContext,
} from "react";
const reducer = (state, action) => {
switch (action.type) {
case "CHANGE_ACTIVE_TAB":
return state.map((call, index) => ({
...call,
active: index === action.index_call,
}));
case "CHANGE_LIST":
return [...action.new_list];
case "CHANGE_ACTIVE_CATEGORY_ID":
return state.map((call) =>
call.id === action.tab_id
? { ...call, active_category_id: action.category_id }
: call,
);
default:
return state;
}
};
export const CallContext = createContext(null);
export const CallProvider = ({ children }) => {
const [openCallDialog, setOpenCallDialog] = useState(false);
const [activeCall, setActiveCall] = useState(0);
const [calls, dispatch] = useReducer(reducer, []);
const changeActiveTab = useCallback((index_call) => {
dispatch({ type: "CHANGE_ACTIVE_TAB", index_call });
}, []);
const changeList = useCallback((new_list) => {
dispatch({ type: "CHANGE_LIST", new_list });
}, []);
const changeActiveCategoryId = useCallback((tab_id, category_id) => {
dispatch({ type: "CHANGE_ACTIVE_CATEGORY_ID", tab_id, category_id });
}, []);
const changeActiveTabCall = (newValue) => {
changeActiveTab(newValue);
setActiveCall(newValue);
};
const newCall = useCallback(
(data) => {
const newCall = {
id: data.id,
phone_number: data.phone_number,
date: new Date(),
active: true,
active_category_id: null,
};
const newList = [...calls, newCall];
changeList(newList);
changeActiveTabCall(newList.length - 1);
},
[calls],
);
const closeCall = useCallback(
(id) => {
const index = calls.findIndex((answer) => answer.id === id);
const newIndex = index === 0 ? 0 : index - 1;
const newList = [...calls];
newList.splice(index, 1);
if (newList.length !== 0) {
newList[newIndex].active = true;
changeActiveTabCall(newIndex);
}
changeList(newList);
},
[calls],
);
const contextValue = useMemo(
() => ({
openCallDialog,
setOpenCallDialog,
calls,
newCall,
closeCall,
activeCall,
setActiveCall,
changeActiveCategoryId,
changeActiveTabCall,
}),
[openCallDialog, calls, activeCall],
);
return (
<CallContext.Provider value={contextValue}>{children}</CallContext.Provider>
);
};
export const useCall = () => useContext(CallContext);

View File

@@ -0,0 +1,72 @@
import { GET_CATEGORY } from "@/core/utils/routes";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useReducer,
} from "react";
import useRequest from "../hooks/useRequest";
const initialState = {
categoryLists: [],
subCategoryLists: [],
active_category_id: null,
};
const reducer = (state, action) => {
switch (action.type) {
case "SET_CATEGORIES":
return { ...state, categoryLists: action.payload };
case "SET_SUBCATEGORIES":
return { ...state, subCategoryLists: action.payload };
default:
return state;
}
};
export const CategoriesContext = createContext(null);
export const CategoriesProvider = ({ children }) => {
const requestServer = useRequest({ notificationShow: false });
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const fetchCategories = async () => {
try {
const { data } = await requestServer(GET_CATEGORY, "get");
const subCategories = data.data;
const categories = Array.from(
new Map(
subCategories.map((item) => [item.category_id, item]),
).values(),
);
dispatch({ type: "SET_CATEGORIES", payload: categories });
dispatch({ type: "SET_SUBCATEGORIES", payload: subCategories });
} catch (error) {}
};
fetchCategories();
}, []);
const contextValue = useMemo(
() => ({
categoryLists: state.categoryLists,
subCategoryLists: state.subCategoryLists,
active_category_id: state.active_category_id,
}),
[state],
);
return (
<CategoriesContext.Provider value={contextValue}>
{children}
</CategoriesContext.Provider>
);
};
export const useCategory = () => useContext(CategoriesContext);

104
src/lib/contexts/socket.js Normal file
View File

@@ -0,0 +1,104 @@
"use client";
import { Power, PowerOff } from "@mui/icons-material";
import {
createContext,
useContext,
useEffect,
useMemo,
useRef,
useState,
useCallback,
} from "react";
import { toast } from "react-toastify";
import { io } from "socket.io-client";
import { useAuth } from "./auth";
const SocketContext = createContext({
socket: null,
status: "",
});
export const useSocket = () => useContext(SocketContext);
export const SocketProvider = ({ children }) => {
const { user } = useAuth();
const socketToastId = useRef(null);
const [status, setStatus] = useState("");
const socket = useMemo(() => {
return io(`${process.env.NEXT_PUBLIC_SERVER_SOCKET_URL}`, {
path: "/notification/socket.io",
autoConnect: false,
auth: { token: null },
});
}, []);
useEffect(() => {
if (user?.telephone_id) {
socket.auth.token = user.telephone_id;
}
}, [user, socket]);
const handleConnect = useCallback(() => setStatus("connected"), []);
const handleDisconnect = useCallback(() => setStatus("disconnected"), []);
const handleConnectError = useCallback(() => setStatus("error"), []);
useEffect(() => {
socket.on("connect", handleConnect);
socket.on("disconnect", handleDisconnect);
socket.on("connect_error", handleConnectError);
return () => {
socket.off("connect", handleConnect);
socket.off("disconnect", handleDisconnect);
socket.off("connect_error", handleConnectError);
};
}, [socket, handleConnect, handleDisconnect, handleConnectError]);
useEffect(() => {
if (status === "connected") {
toast.update(socketToastId.current, {
containerId: "socket_connection",
type: "success",
render: "ارتباط با سرور برقرار شد",
autoClose: 2000,
closeButton: true,
closeOnClick: true,
icon: ({ theme, type }) => <Power />,
});
} else if (status === "error" || status === "disconnected") {
if (socketToastId.current) {
toast.update(socketToastId.current, {
containerId: "socket_connection",
type: "warning",
render: "ارتباط با سرور قطع شد. در حال تلاش برای بازیابی…",
autoClose: false,
closeButton: false,
draggable: false,
icon: ({ theme, type }) => <PowerOff />,
});
} else {
socketToastId.current = toast.warn(
"ارتباط با سرور قطع شد. در حال تلاش برای بازیابی…",
{
containerId: "socket_connection",
autoClose: false,
closeButton: false,
draggable: false,
icon: ({ theme, type }) => <PowerOff />,
},
);
}
}
}, [status]);
const contextValue = useMemo(() => ({ socket, status }), [socket, status]);
return (
<SocketContext.Provider value={contextValue}>
{children}
</SocketContext.Provider>
);
};
export default SocketContext;

View File

@@ -0,0 +1,189 @@
"use client";
import { createContext, useCallback, useEffect, useState } from "react";
import LZString from "lz-string";
export const TableSettingContext = createContext();
const decompressData = (compressedData) => {
try {
const decompressed = LZString.decompressFromUTF16(compressedData);
if (decompressed === null) {
localStorage.removeItem("_setting-app");
return null;
} else {
return JSON.parse(decompressed);
}
} catch (error) {
localStorage.removeItem("_setting-app");
return null;
}
};
export const TableSettingProvider = ({ children }) => {
const [settingStore, setSettingStore] = useState({});
useEffect(() => {
const compressedData = localStorage.getItem("_setting-app");
const data = compressedData ? decompressData(compressedData) : null;
if (data) {
setSettingStore(data);
}
}, []);
const hideAction = useCallback(
(user_id, page_name, table_name, settingValue) => {
clearAction(user_id, page_name, table_name, "hides");
updateSettingStorage(
user_id,
page_name,
table_name,
"hides",
settingValue,
);
},
[],
);
const sortAction = useCallback(
(user_id, page_name, table_name, settingValue) => {
updateSettingStorage(
user_id,
page_name,
table_name,
"sorts",
settingValue,
);
},
[],
);
const filterAction = (user_id, page_name, table_name, settingValue) => {
updateSettingStorage(
user_id,
page_name,
table_name,
"filters",
settingValue,
);
};
const resetAction = (user_id, page_name, table_name) => {
const compressedData = localStorage.getItem("_setting-app");
const prevLocalStorage = compressedData
? decompressData(compressedData)
: {};
const userSettings = prevLocalStorage[user_id] || {};
const pageSettings = userSettings[page_name] || {};
delete pageSettings[table_name];
const resetData = {
...prevLocalStorage,
[user_id]: {
...userSettings,
[page_name]: {
...pageSettings,
},
},
};
localStorage.setItem(
"_setting-app",
LZString.compressToUTF16(JSON.stringify(resetData)),
);
setSettingStore(resetData);
};
const updateSettingStorage = (
user_id,
page_name,
table_name,
settingType,
settingValue,
) => {
const compressedData = localStorage.getItem("_setting-app");
const prevLocalStorage = compressedData
? decompressData(compressedData)
: {};
const userSettings = prevLocalStorage[user_id] || {};
const pageSettings = userSettings[page_name] || {};
const tableSettings = pageSettings[table_name] || {};
let newSettings;
if (settingType === "sorts" || settingType === "filters") {
newSettings = [...settingValue];
} else {
newSettings = { ...(tableSettings[settingType] || {}), ...settingValue };
}
const updatedLocalStorage = {
...prevLocalStorage,
[user_id]: {
...userSettings,
[page_name]: {
...pageSettings,
[table_name]: {
...tableSettings,
[settingType]: Array.isArray(settingValue)
? [...newSettings]
: { ...newSettings },
},
},
},
};
localStorage.setItem(
"_setting-app",
LZString.compressToUTF16(JSON.stringify(updatedLocalStorage)),
);
setSettingStore(updatedLocalStorage);
};
const clearAction = (user_id, page_name, table_name, key) => {
const compressedData = localStorage.getItem("_setting-app");
const prevLocalStorage = compressedData
? decompressData(compressedData)
: {};
const userSettings = prevLocalStorage[user_id] || {};
const pageSettings = userSettings[page_name] || {};
const tableSettings = pageSettings[table_name] || {};
const updatedSettings = {
...prevLocalStorage,
[user_id]: {
...userSettings,
[page_name]: {
...pageSettings,
[table_name]: {
...tableSettings,
[key]: Array.isArray(tableSettings[key]) ? [] : {},
},
},
},
};
localStorage.setItem(
"_setting-app",
LZString.compressToUTF16(JSON.stringify(updatedSettings)),
);
setSettingStore(updatedSettings);
};
return (
<TableSettingContext.Provider
value={{
settingStore,
hideAction,
sortAction,
filterAction,
resetAction,
}}
>
{children}
</TableSettingContext.Provider>
);
};

View File

@@ -0,0 +1,50 @@
import { useEffect, useState } from "react";
import useRequest from "./useRequest";
import { GET_CALLER_HISTORY } from "@/core/utils/routes";
const useCallerHistory = (callId, phoneNumber, maxSize) => {
const requestServer = useRequest();
const [historyData, setHistoryData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [hasError, setHasError] = useState(false);
useEffect(() => {
const fetchHistory = async () => {
if (!phoneNumber || !maxSize) return;
setIsLoading(true);
setHasError(false);
try {
const url = `${GET_CALLER_HISTORY}?phone_number=${phoneNumber}&size=${maxSize}`;
const { data } = await requestServer(url, "get");
const items = data?.data || [];
const filtered = items.filter((item) => item.id !== callId);
const unique = Array.from(
new Map(filtered.map((item) => [item.id, item])).values(),
);
setHistoryData(unique);
} catch (error) {
console.error("Error fetching caller history:", error);
setHasError(true);
setHistoryData([]);
} finally {
setIsLoading(false);
}
};
fetchHistory();
}, [callId, phoneNumber, maxSize]);
return {
firstItemOfHistory: historyData[0] || false,
historyList: historyData.length > 1 ? historyData.slice(1) : false,
isLoadingHistoryList: isLoading,
errorHistoryList: hasError,
};
};
export default useCallerHistory;

View File

@@ -0,0 +1,21 @@
import { GET_PERMISSIONS_ROUTE } from "@/core/utils/routes";
import useSWR from "swr";
import useRequest from "./useRequest";
export const usePermissions = () => {
const request = useRequest();
const fetcher = async (url) => {
try {
const response = await request(url);
return response.data.data;
} catch (error) {
throw new Error();
}
};
return useSWR(GET_PERMISSIONS_ROUTE, fetcher, {
keepPreviousData: true,
dedupingInterval: 30000,
});
};

View File

@@ -0,0 +1,66 @@
import { errorResponse } from "@/core/utils/errorResponse";
import { successRequest } from "@/core/utils/successRequest";
import axios from "axios";
import { useAuth } from "../contexts/auth";
import { useSidebarBadge } from "./useSidebarBadge";
const defaultOptions = {
data: {},
requestOptions: {
headers: {},
},
notificationShow: true,
notificationSuccess: false,
notificationFailed: true,
hasSidebarUpdate: false,
};
const useRequest = (initOptions) => {
const _options = Object.assign({}, defaultOptions, initOptions);
const { mutate: sidebarUpdate } = useSidebarBadge({
enable: _options.hasSidebarUpdate,
});
const { logout } = useAuth();
/**
* Performs an HTTP request.
* @param {string} url - The URL to send the request to.
* @param {'get' | 'post' | 'put' | 'delete'} [method='get'] - HTTP method.
* @param {object} [options] - Additional request options.
* @returns {Promise<AxiosResponse>} - Axios response.
*/
return async (url = "", method = "get", options) => {
const mergedOptions = Object.assign({}, _options, options);
try {
const response = await axios({
url,
method,
data: method === "get" ? null : mergedOptions.data,
withCredentials: true,
...mergedOptions.requestOptions,
});
if (mergedOptions.hasSidebarUpdate) {
if (method === "post" || method === "put" || method === "delete")
sidebarUpdate();
}
successRequest(
mergedOptions.notificationShow && mergedOptions.notificationSuccess,
"request_data",
);
return response;
} catch (error) {
if (error.response) {
errorResponse(
error.response,
mergedOptions.notificationShow && mergedOptions.notificationFailed,
"request_data",
logout,
);
}
throw error;
}
};
};
export default useRequest;

View File

@@ -0,0 +1,23 @@
import { GET_SIDEBAR_BADGE_ROUTE } from "@/core/utils/routes";
import axios from "axios";
import useSWR from "swr";
export const useSidebarBadge = ({ enable = true }) => {
const fetcher = async (url) => {
try {
const response = await axios({
url,
method: "get",
withCredentials: true,
});
return response.data.data;
} catch (error) {
throw new Error();
}
};
return useSWR(enable ? GET_SIDEBAR_BADGE_ROUTE : "", fetcher, {
keepPreviousData: true,
dedupingInterval: 30000,
});
};