diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index 3613a1c..e6d60eb 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -15,6 +15,8 @@ "between": "میان", "online_message": "شما به اینترنت وصل هستید", "offline_message": "اتصال شما به اینترنت قطع شده است", + "socket_is_connect_message": "شما به سوکت وصل هستید", + "socket_is_not_connect_message": "اتصال شما به سوکت قطع شده است", "header": { "open_profile": "پروفایل", "edit_profile": " پروفایل", diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx index 416e5c8..76af95c 100644 --- a/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx @@ -1,10 +1,12 @@ import PermPhoneMsgIcon from '@mui/icons-material/PermPhoneMsg'; import StyledFab from "@/core/components/StyledFab"; +import useCallDialog from "@/lib/callWidget/hooks/useCallDialog"; -const CallWidgetButton = ({setOpen}) => { +const CallWidgetButton = () => { + const {setOpenCallDialog} = useCallDialog() return ( setOpen(true)}> + onClick={() => setOpenCallDialog(true)}> ) diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx new file mode 100644 index 0000000..5b9326f --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx @@ -0,0 +1,7 @@ +const CallActions = ({tab}) => { + return ( + <>CallActions - {tab.id} + ) +} + +export default CallActions \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx new file mode 100644 index 0000000..292c640 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx @@ -0,0 +1,7 @@ +const CallHistory = ({tab}) => { + return ( + <>CallHistory - {tab.id} + ) +} + +export default CallHistory \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx new file mode 100644 index 0000000..79dc736 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx @@ -0,0 +1,19 @@ +import {Grid} from "@mui/material"; +import CallActions from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions"; +import CallHistory from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory"; + +const CallTabPanel = ({tab}) => { + return ( + + + + + + + + + ) +} + +export default CallTabPanel \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx new file mode 100644 index 0000000..7a9b703 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx @@ -0,0 +1,23 @@ +import {IconButton, Stack, Typography, Zoom} from "@mui/material"; +import CloseIcon from '@mui/icons-material/Close'; +import useAnswers from "@/lib/callWidget/hooks/useAnswers"; + +const CallTabLabel = ({tab}) => { + const {closeAnswerTab} = useAnswers() + const handleCloseClick = (id) => { + closeAnswerTab(id) + }; + + return ( + + {tab.phone_number} - {tab.id} + + handleCloseClick(tab.id)}> + + + + + ) +} + +export default CallTabLabel \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx new file mode 100644 index 0000000..70fccd9 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx @@ -0,0 +1,27 @@ +import {Box, Tab, Tabs} from "@mui/material"; +import CallTabLabel + from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel"; +import useAnswers from "@/lib/callWidget/hooks/useAnswers"; + +const CallTabsList = () => { + const {answersList, changeActiveTabAnswers, activeTab} = useAnswers() + const handleChange = (event, newValue) => { + changeActiveTabAnswers(newValue) + }; + + return ( + + + {answersList.map((answer) => ( + } key={answer.id}/> + ))} + + + ) +} + +export default CallTabsList \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx new file mode 100644 index 0000000..4e1d2f1 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx @@ -0,0 +1,17 @@ +import CallTabsList from "src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList"; +import CallTabPanel from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel"; +import useAnswers from "@/lib/callWidget/hooks/useAnswers"; + +const CallTabs = () => { + const {answersList} = useAnswers() + + return ( + <> + + {answersList.map((answer) => ( + + ))} + + ) +} +export default CallTabs \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx index 77ed57d..5afb0a1 100644 --- a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx @@ -1,23 +1,47 @@ import {Dialog} from "@mui/material"; import {DialogTransition} from "@/core/components/DialogTransition"; +import {useEffect} from "react"; +import useCallDialog from "@/lib/callWidget/hooks/useCallDialog"; +import CallTabs from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs"; +import useAnswers from "@/lib/callWidget/hooks/useAnswers"; +import useSocket from "@/lib/app/hooks/useSocket"; -const CallWidgetDialog = ({open, setOpen}) => { +const CallWidgetDialog = () => { + const {openCallDialog, setOpenCallDialog} = useCallDialog() + const {answersList, newAnswerTab} = useAnswers() + const socket = useSocket() - const handleClose = () => { - setOpen(false); - }; + useEffect(() => { + const answerCall = (data, act) => { + newAnswerTab({ + id: data.id, + phone_number: data.phone_number + }) + act() + } + socket.on('answer', answerCall) + + return () => { + socket.off('answer', answerCall) + } + }, []); + + useEffect(() => { + if (answersList.length === 0) { + setOpenCallDialog(false) + return + } + setOpenCallDialog(true) + }, [answersList.length]); return ( - <> - - - - + + + ) } diff --git a/src/components/layouts/Dashboard/CallWidget/index.jsx b/src/components/layouts/Dashboard/CallWidget/index.jsx index 4d871b2..d6fe5a0 100644 --- a/src/components/layouts/Dashboard/CallWidget/index.jsx +++ b/src/components/layouts/Dashboard/CallWidget/index.jsx @@ -1,14 +1,27 @@ import CallWidgetButton from "@/components/layouts/Dashboard/CallWidget/CallWidgetButton"; import CallWidgetDialog from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog"; -import {useState} from "react"; +import {AnswersProvider} from "@/lib/callWidget/contexts/answers"; +import useSocket from "@/lib/app/hooks/useSocket"; +import {useEffect} from "react"; const CallWidget = () => { - const [open, setOpen] = useState(false) + const socket = useSocket() + + useEffect(() => { + if (socket.connected) return + + socket.connect() + + return () => { + socket.disconnect() + } + }, []); + return ( - <> - - - + + + + ) } diff --git a/src/core/components/NetworkComponent.jsx b/src/core/components/NetworkComponent.jsx index 8ebbb68..22d4620 100644 --- a/src/core/components/NetworkComponent.jsx +++ b/src/core/components/NetworkComponent.jsx @@ -6,13 +6,13 @@ import WifiOffIcon from '@mui/icons-material/WifiOff'; import {useTranslations} from "next-intl"; const NetworkComponent = () => { - const toastId = useRef(null); + const networkToastId = useRef(null); const network = useNetwork() const t = useTranslations() useEffect(() => { if (network.online) { - toast.update(toastId.current, { + toast.update(networkToastId.current, { type: toast.TYPE.SUCCESS, render: t('online_message'), autoClose: 2000, @@ -22,8 +22,7 @@ const NetworkComponent = () => { }); return } - toast.dismiss() - toastId.current = toast.warn(t('offline_message'), { + networkToastId.current = toast.warn(t('offline_message'), { autoClose: false, closeButton: false, closeOnClick: false, icon: }) }, [network.online]); diff --git a/src/core/components/StyledFab.jsx b/src/core/components/StyledFab.jsx index dd935cc..95c5ff7 100644 --- a/src/core/components/StyledFab.jsx +++ b/src/core/components/StyledFab.jsx @@ -5,7 +5,6 @@ const StyledFab = styled(Fab)` bottom: 16px; /* @noflip */ left: 16px; - z-index: 1500; `; export default StyledFab; diff --git a/src/layouts/MuiLayout.jsx b/src/layouts/MuiLayout.jsx index 6dcd3ed..eba3449 100644 --- a/src/layouts/MuiLayout.jsx +++ b/src/layouts/MuiLayout.jsx @@ -22,26 +22,23 @@ const MuiLayout = ({children, isBot}) => { { + const {user, isAuth} = useUser() + const [connectionError, setConnectionError] = useState(false) + const socketToastId = useRef(null); + const t = useTranslations() const socket = useMemo(() => io(process.env.NEXT_PUBLIC_SERVER_SOCKET_URL, { autoConnect: false, auth: { @@ -11,5 +20,48 @@ export const SocketProvider = ({children}) => { } }), []) + 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: + }); + return + } + socketToastId.current = toast.warn(t('socket_is_not_connect_message'), { + draggable: false, + autoClose: false, closeButton: false, closeOnClick: false, icon: + }) + + }, [connectionError]); + return {children} } \ No newline at end of file diff --git a/src/lib/app/contexts/user.jsx b/src/lib/app/contexts/user.jsx index 0d7adeb..899ac1b 100644 --- a/src/lib/app/contexts/user.jsx +++ b/src/lib/app/contexts/user.jsx @@ -2,7 +2,6 @@ import {GET_USER_ROUTE} from "@/core/data/apiRoutes"; import axios from "axios"; import {createContext, useCallback, useEffect, useReducer} from "react"; import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler"; -import useSocket from "@/lib/app/hooks/useSocket"; const initialUser = { isAuth: false, @@ -40,7 +39,6 @@ const reducer = (state, action) => { export const UserContext = createContext(); export const UserProvider = ({children}) => { - const socket = useSocket() const [state, dispatch] = useReducer(reducer, initialUser); const clearUser = useCallback(() => { @@ -102,16 +100,12 @@ export const UserProvider = ({children}) => { clearUser(); changeAuthState(false); changeLanguageState(false); - - socket.auth.token = "" return; } getUser((data) => { changeUser(data); changeAuthState(true); changeLanguageState(true); - - socket.auth.token = data.telephone_id }); }, [state.token]); diff --git a/src/lib/app/hooks/useNetwork.jsx b/src/lib/app/hooks/useNetwork.jsx index 84e2950..9c436a5 100644 --- a/src/lib/app/hooks/useNetwork.jsx +++ b/src/lib/app/hooks/useNetwork.jsx @@ -1,29 +1,5 @@ import {useEffect, useState} from "react"; -function getNetworkConnection() { - return ( - navigator.connection || - navigator.mozConnection || - navigator.webkitConnection || - null - ); -} - -function getNetworkConnectionInfo() { - const connection = getNetworkConnection(); - if (!connection) { - return {}; - } - return { - rtt: connection.rtt, - type: connection.type, - saveData: connection.saveData, - downLink: connection.downLink, - downLinkMax: connection.downLinkMax, - effectiveType: connection.effectiveType, - }; -} - function useNetwork() { const [state, setState] = useState(() => { return { @@ -51,7 +27,7 @@ function useNetwork() { window.removeEventListener("offline", handleOffline); }; }, []); - + return state; } diff --git a/src/lib/callWidget/contexts/answers.jsx b/src/lib/callWidget/contexts/answers.jsx new file mode 100644 index 0000000..a9f7ae2 --- /dev/null +++ b/src/lib/callWidget/contexts/answers.jsx @@ -0,0 +1,31 @@ +import {createContext, useReducer, useState} from "react"; + +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] + default: + return state; + } +} +export const AnswersContext = createContext() +export const AnswersProvider = ({children}) => { + const [openCallDialog, setOpenCallDialog] = useState(false) + const [activeTab, setActiveTab] = useState(0) + const [state, dispatch] = useReducer(reducer, []); + + return {children} +} diff --git a/src/lib/callWidget/hooks/useAnswers.jsx b/src/lib/callWidget/hooks/useAnswers.jsx new file mode 100644 index 0000000..2155cd7 --- /dev/null +++ b/src/lib/callWidget/hooks/useAnswers.jsx @@ -0,0 +1,38 @@ +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, + active: true + } + 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 \ No newline at end of file diff --git a/src/lib/callWidget/hooks/useCallDialog.jsx b/src/lib/callWidget/hooks/useCallDialog.jsx new file mode 100644 index 0000000..30dd599 --- /dev/null +++ b/src/lib/callWidget/hooks/useCallDialog.jsx @@ -0,0 +1,10 @@ +import {useContext} from "react"; +import {AnswersContext} from "@/lib/callWidget/contexts/answers"; + +const useCallDialog = () => { + const {openCallDialog, setOpenCallDialog} = useContext(AnswersContext) + + return {openCallDialog, setOpenCallDialog} +} + +export default useCallDialog \ No newline at end of file diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index fe249a6..c5fad77 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -12,22 +12,22 @@ import {SocketProvider} from "@/lib/app/contexts/socket"; const App = ({Component, pageProps}) => { return (<> - - - - - - {pageProps.messages ? : ''} - - + + + + + {pageProps.messages ? : ''} + + + - - - - - - - + + + + + + + ) };