CFE-22 implementation of socket and dialog and button call widget
This commit is contained in:
@@ -15,6 +15,8 @@
|
|||||||
"between": "میان",
|
"between": "میان",
|
||||||
"online_message": "شما به اینترنت وصل هستید",
|
"online_message": "شما به اینترنت وصل هستید",
|
||||||
"offline_message": "اتصال شما به اینترنت قطع شده است",
|
"offline_message": "اتصال شما به اینترنت قطع شده است",
|
||||||
|
"socket_is_connect_message": "شما به سوکت وصل هستید",
|
||||||
|
"socket_is_not_connect_message": "اتصال شما به سوکت قطع شده است",
|
||||||
"header": {
|
"header": {
|
||||||
"open_profile": "پروفایل",
|
"open_profile": "پروفایل",
|
||||||
"edit_profile": " پروفایل",
|
"edit_profile": " پروفایل",
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import PermPhoneMsgIcon from '@mui/icons-material/PermPhoneMsg';
|
import PermPhoneMsgIcon from '@mui/icons-material/PermPhoneMsg';
|
||||||
import StyledFab from "@/core/components/StyledFab";
|
import StyledFab from "@/core/components/StyledFab";
|
||||||
|
import useCallDialog from "@/lib/callWidget/hooks/useCallDialog";
|
||||||
|
|
||||||
const CallWidgetButton = ({setOpen}) => {
|
const CallWidgetButton = () => {
|
||||||
|
const {setOpenCallDialog} = useCallDialog()
|
||||||
return (
|
return (
|
||||||
<StyledFab color="primary" aria-label="open-dialog"
|
<StyledFab color="primary" aria-label="open-dialog"
|
||||||
onClick={() => setOpen(true)}>
|
onClick={() => setOpenCallDialog(true)}>
|
||||||
<PermPhoneMsgIcon/>
|
<PermPhoneMsgIcon/>
|
||||||
</StyledFab>
|
</StyledFab>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const CallActions = ({tab}) => {
|
||||||
|
return (
|
||||||
|
<>CallActions - {tab.id}</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallActions
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const CallHistory = ({tab}) => {
|
||||||
|
return (
|
||||||
|
<>CallHistory - {tab.id}</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallHistory
|
||||||
@@ -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 (
|
||||||
|
<Grid container columns={{xs: 3}} sx={{display: !tab.active && 'none'}} role="tabpanel"
|
||||||
|
id={`answer-tabpanel-${tab.id}`} aria-labelledby={`answer-tab-${tab.id}`}>
|
||||||
|
<Grid item xs={2}>
|
||||||
|
<CallActions tab={tab}/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={1}>
|
||||||
|
<CallHistory tab={tab}/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallTabPanel
|
||||||
@@ -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 (
|
||||||
|
<Stack sx={{width: '100%'}} direction={'row'} alignItems={'center'} justifyContent={'space-between'}>
|
||||||
|
<Typography>{tab.phone_number} - {tab.id}</Typography>
|
||||||
|
<Zoom in={tab.active}>
|
||||||
|
<IconButton onClick={() => handleCloseClick(tab.id)}>
|
||||||
|
<CloseIcon/>
|
||||||
|
</IconButton>
|
||||||
|
</Zoom>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallTabLabel
|
||||||
@@ -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 (
|
||||||
|
<Box sx={{borderBottom: 1, borderColor: 'divider'}}>
|
||||||
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant={'fullWidth'}
|
||||||
|
>
|
||||||
|
{answersList.map((answer) => (
|
||||||
|
<Tab component={'div'} sx={{py: 0.5, pr: 0,}} label={<CallTabLabel tab={answer}/>} key={answer.id}/>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallTabsList
|
||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<CallTabsList/>
|
||||||
|
{answersList.map((answer) => (
|
||||||
|
<CallTabPanel key={answer.id} tab={answer}/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default CallTabs
|
||||||
@@ -1,23 +1,47 @@
|
|||||||
import {Dialog} from "@mui/material";
|
import {Dialog} from "@mui/material";
|
||||||
import {DialogTransition} from "@/core/components/DialogTransition";
|
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 = () => {
|
useEffect(() => {
|
||||||
setOpen(false);
|
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 (
|
return (
|
||||||
<>
|
<Dialog
|
||||||
<Dialog
|
fullScreen
|
||||||
fullScreen
|
open={openCallDialog}
|
||||||
open={open}
|
TransitionComponent={DialogTransition}
|
||||||
onClose={handleClose}
|
>
|
||||||
TransitionComponent={DialogTransition}
|
<CallTabs/>
|
||||||
>
|
</Dialog>
|
||||||
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
import CallWidgetButton from "@/components/layouts/Dashboard/CallWidget/CallWidgetButton";
|
import CallWidgetButton from "@/components/layouts/Dashboard/CallWidget/CallWidgetButton";
|
||||||
import CallWidgetDialog from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog";
|
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 CallWidget = () => {
|
||||||
const [open, setOpen] = useState(false)
|
const socket = useSocket()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (socket.connected) return
|
||||||
|
|
||||||
|
socket.connect()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.disconnect()
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnswersProvider>
|
||||||
<CallWidgetButton setOpen={setOpen}/>
|
<CallWidgetButton/>
|
||||||
<CallWidgetDialog open={open} setOpen={setOpen}/>
|
<CallWidgetDialog/>
|
||||||
</>
|
</AnswersProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ const StyledFab = styled(Fab)`
|
|||||||
bottom: 16px;
|
bottom: 16px;
|
||||||
/* @noflip */
|
/* @noflip */
|
||||||
left: 16px;
|
left: 16px;
|
||||||
z-index: 1500;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default StyledFab;
|
export default StyledFab;
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import {createContext, useMemo} from "react";
|
import {createContext, useEffect, useMemo, useRef, useState} from "react";
|
||||||
import {io} from "socket.io-client";
|
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 SocketContext = createContext()
|
||||||
|
|
||||||
export const SocketProvider = ({children}) => {
|
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, {
|
const socket = useMemo(() => io(process.env.NEXT_PUBLIC_SERVER_SOCKET_URL, {
|
||||||
autoConnect: false,
|
autoConnect: false,
|
||||||
auth: {
|
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: <PowerIcon/>
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
socketToastId.current = toast.warn(t('socket_is_not_connect_message'), {
|
||||||
|
draggable: false,
|
||||||
|
autoClose: false, closeButton: false, closeOnClick: false, icon: <PowerOffIcon/>
|
||||||
|
})
|
||||||
|
|
||||||
|
}, [connectionError]);
|
||||||
|
|
||||||
return <SocketContext.Provider value={{socket}}>{children}</SocketContext.Provider>
|
return <SocketContext.Provider value={{socket}}>{children}</SocketContext.Provider>
|
||||||
}
|
}
|
||||||
31
src/lib/callWidget/contexts/answers.jsx
Normal file
31
src/lib/callWidget/contexts/answers.jsx
Normal file
@@ -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 <AnswersContext.Provider
|
||||||
|
value={{
|
||||||
|
openCallDialog,
|
||||||
|
setOpenCallDialog,
|
||||||
|
state,
|
||||||
|
dispatch,
|
||||||
|
activeTab,
|
||||||
|
setActiveTab
|
||||||
|
}}>{children}</AnswersContext.Provider>
|
||||||
|
}
|
||||||
38
src/lib/callWidget/hooks/useAnswers.jsx
Normal file
38
src/lib/callWidget/hooks/useAnswers.jsx
Normal file
@@ -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
|
||||||
10
src/lib/callWidget/hooks/useCallDialog.jsx
Normal file
10
src/lib/callWidget/hooks/useCallDialog.jsx
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user