CFE-22 Merge branch 'feature/CFE-22_call_widget_btn_dialog_socket' into 'develop'
This commit is contained in:
@@ -15,6 +15,8 @@
|
||||
"between": "میان",
|
||||
"online_message": "شما به اینترنت وصل هستید",
|
||||
"offline_message": "اتصال شما به اینترنت قطع شده است",
|
||||
"socket_is_connect_message": "شما به سوکت وصل هستید",
|
||||
"socket_is_not_connect_message": "اتصال شما به سوکت قطع شده است",
|
||||
"header": {
|
||||
"open_profile": "پروفایل",
|
||||
"edit_profile": " پروفایل",
|
||||
|
||||
@@ -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 (
|
||||
<StyledFab color="primary" aria-label="open-dialog"
|
||||
onClick={() => setOpen(true)}>
|
||||
onClick={() => setOpenCallDialog(true)}>
|
||||
<PermPhoneMsgIcon/>
|
||||
</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 {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 (
|
||||
<>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
TransitionComponent={DialogTransition}
|
||||
>
|
||||
|
||||
</Dialog>
|
||||
</>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={openCallDialog}
|
||||
TransitionComponent={DialogTransition}
|
||||
>
|
||||
<CallTabs/>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<CallWidgetButton setOpen={setOpen}/>
|
||||
<CallWidgetDialog open={open} setOpen={setOpen}/>
|
||||
</>
|
||||
<AnswersProvider>
|
||||
<CallWidgetButton/>
|
||||
<CallWidgetDialog/>
|
||||
</AnswersProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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: <WifiOffIcon/>
|
||||
})
|
||||
}, [network.online]);
|
||||
|
||||
@@ -5,7 +5,6 @@ const StyledFab = styled(Fab)`
|
||||
bottom: 16px;
|
||||
/* @noflip */
|
||||
left: 16px;
|
||||
z-index: 1500;
|
||||
`;
|
||||
|
||||
export default StyledFab;
|
||||
|
||||
@@ -22,26 +22,23 @@ const MuiLayout = ({children, isBot}) => {
|
||||
<ThemeProvider theme={theme}>
|
||||
<GlobalStyles
|
||||
styles={{
|
||||
"*:not(.MuiTableContainer-root)::-webkit-scrollbar": {
|
||||
display: "none",
|
||||
},
|
||||
"*::-webkit-scrollbar": {
|
||||
height: "8px",
|
||||
},
|
||||
"*:not(.MuiTableContainer-root)::-webkit-scrollbar": {
|
||||
display: "none",
|
||||
},
|
||||
"*::-webkit-scrollbar-thumb": {
|
||||
background: `${theme.palette.primary.light}80`,
|
||||
borderRadius: "3px",
|
||||
},
|
||||
"*:not(.MuiTableContainer-root)": {
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "transparent transparent",
|
||||
},
|
||||
|
||||
"*": {
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: `${theme.palette.primary.light}80 transparent`,
|
||||
},
|
||||
|
||||
"*:not(.MuiTableContainer-root)": {
|
||||
scrollbarWidth: "none",
|
||||
},
|
||||
"*::-moz-scrollbar-thumb": {
|
||||
backgroundColor: `${theme.palette.primary.light}80`,
|
||||
},
|
||||
|
||||
@@ -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: <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>
|
||||
}
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
@@ -12,22 +12,22 @@ import {SocketProvider} from "@/lib/app/contexts/socket";
|
||||
const App = ({Component, pageProps}) => {
|
||||
|
||||
return (<>
|
||||
<SocketProvider>
|
||||
<UserProvider>
|
||||
<LanguageProvider>
|
||||
<NextIntlProvider locale={pageProps.locale} messages={pageProps.messages || {}}>
|
||||
<MuiLayout isBot={pageProps.isBot}>
|
||||
{pageProps.messages ? <TitlePage text={pageProps.title}/> : ''}
|
||||
<LoadingProvider>
|
||||
<AppLayout isBot={pageProps.isBot}>
|
||||
<UserProvider>
|
||||
<LanguageProvider>
|
||||
<NextIntlProvider locale={pageProps.locale} messages={pageProps.messages || {}}>
|
||||
<MuiLayout isBot={pageProps.isBot}>
|
||||
{pageProps.messages ? <TitlePage text={pageProps.title}/> : ''}
|
||||
<LoadingProvider>
|
||||
<AppLayout isBot={pageProps.isBot}>
|
||||
<SocketProvider>
|
||||
<Component {...pageProps} />
|
||||
</AppLayout>
|
||||
</LoadingProvider>
|
||||
</MuiLayout>
|
||||
</NextIntlProvider>
|
||||
</LanguageProvider>
|
||||
</UserProvider>
|
||||
</SocketProvider>
|
||||
</SocketProvider>
|
||||
</AppLayout>
|
||||
</LoadingProvider>
|
||||
</MuiLayout>
|
||||
</NextIntlProvider>
|
||||
</LanguageProvider>
|
||||
</UserProvider>
|
||||
</>)
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user