diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index e813849..7366af7 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/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/lib/app/contexts/socket.jsx b/src/lib/app/contexts/socket.jsx index 7da4f43..47154c8 100644 --- a/src/lib/app/contexts/socket.jsx +++ b/src/lib/app/contexts/socket.jsx @@ -1,9 +1,18 @@ -import {createContext, useMemo} from "react"; +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"; 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 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/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