发送图片链接方法; 状态管理: zustand; 删除context provider

dev/mobile
Lei OT 2 years ago
parent 120741fade
commit 8b6679ba49

@ -19,7 +19,8 @@
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.1",
"rxjs": "^7.8.1",
"uuid": "^9.0.1"
"uuid": "^9.0.1",
"zustand": "^4.5.0"
},
"devDependencies": {
"@types/react": "^18.2.15",

@ -44,6 +44,30 @@ export const sentMsgTypeMapped = {
: {}),
}),
},
photo: {
type: 'image',
contentToSend: (msg) => ({
action: 'message',
actionId: msg.id,
renderId: msg.id,
to: msg.to,
msgtype: 'image',
msgcontent: {
image: { link: msg.data.uri },
...(msg.context ? { context: msg.context, message_origin: msg.message_origin } : {}),
},
}),
contentToRender: (msg) => ({
...msg,
actionId: msg.id,
conversationid: msg.id.split('.')[0],
...(msg.context
? {
reply: { message: msg.message_origin.text, title: msg.message_origin.senderName || 'Reference' },
}
: {}),
}),
},
whatsappTemplate: {
contentToSend: (msg) => ({ action: 'message', actionId: msg.id, renderId: msg.id, to: msg.to, msgtype: 'template', msgcontent: msg.template }),
contentToRender: (msg) => {
@ -261,15 +285,3 @@ export const parseRenderMessageList = (messages, conversationid = null) => {
};
});
};
/**
* WhatsApp Templates params
* @deprecated
*/
export const whatsappTemplatesParamMapped = {
/** @deprecated */
'asia_highlights_has_receive_your_inquiry': [['customer_name']],
'hello_from_asia_highlights': [['agent_name']], // todo:
'hello_from_china_highlights': [['agent_name']], // todo:
'use_new_whatsapp': [['agent_name']], // todo:
};

@ -43,9 +43,9 @@ ReactDOM.createRoot(document.getElementById('root')).render(
// <React.StrictMode>
<ThemeContext.Provider value={{ colorPrimary: '#1ba784', borderRadius: 4 }}>
<AuthContext.Provider value={{ loginUser: { userId: 354, username: '廖一军', accountList: [{OPI_Code:'LYJ'}, {OPI_Code:'LYJAH'}, {OPI_Code:'LYJGH'}] } }}>
<ConversationProvider>
{/* <ConversationProvider> */}
<RouterProvider router={router} fallbackElement={() => <div>Loading...</div>} />
</ConversationProvider>
{/* </ConversationProvider> */}
</AuthContext.Provider>
</ThemeContext.Provider>
// </React.StrictMode>

@ -0,0 +1,262 @@
import { create } from 'zustand';
import { RealTimeAPI } from '@/lib/realTimeAPI';
import { olog, isEmpty } from '@/utils/utils';
import { receivedMsgTypeMapped } from '@/lib/msgUtils';
import { fetchConversationsList, fetchTemplates } from '@/actions/ConversationActions';
// const WS_URL = 'ws://202.103.68.144:8888/whatever/';
// const WS_URL = 'ws://120.79.9.217:10022/whatever/';
const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_callback'; // prod:
const initialConversationState = {
// websocket: null,
// websocketOpened: null,
// websocketRetrying: null,
// websocketRetrytimes: null,
errors: [], // 错误信息
// templates: [],
// conversationsList: [], // 对话列表
// currentConversation: {}, // 当前对话
// activeConversations: {}, // 激活的对话的消息列表: { [conversationId]: <messageItem>[] }
// referenceMsg: {},
};
olog('initialConversationState');
export const templatesSlice = (set) => ({
templates: [],
setTemplates: (templates) => set({ templates }),
});
export const websocketSlice = (set, get) => ({
websocket: null,
websocketOpened: null,
websocketRetrying: null,
websocketRetrytimes: null,
setWebsocket: (websocket) => set({ websocket }),
setWebsocketOpened: (opened) => set({ websocketOpened: opened }),
setWebsocketRetrying: (retrying) => set({ websocketRetrying: retrying }),
setWebsocketRetrytimes: (retrytimes) => set({ websocketRetrytimes: retrytimes, websocketRetrying: retrytimes > 0 }),
connectWebsocket: (userId) => {
const { setWebsocket, setWebsocketOpened, setWebsocketRetrytimes, addError, handleMessage } = get();
const realtimeAPI = new RealTimeAPI(
{
url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`,
protocol: 'WhatsApp',
},
() => {
setWebsocketOpened(true);
setWebsocketRetrytimes(0);
},
() => setWebsocketOpened(false),
(n) => setWebsocketRetrytimes(n)
);
realtimeAPI.onError(() => addError('Error'));
realtimeAPI.onMessage(handleMessage);
realtimeAPI.onCompletion(() => addError('Connection broken'));
setWebsocket(realtimeAPI);
},
disconnectWebsocket: () => {
const { websocket } = get();
websocket.disconnect();
return set({ websocket: null });
},
handleMessage: (data) => {
console.log('handleMessage------------------');
console.log(data);
const { updateMessageItem, sentOrReceivedNewMessage } = get();
const { errcode, errmsg, result } = data;
if (!result) {
return false;
}
let resultType = result?.action || result.type;
if (errcode !== 0) {
// addError('Error Connecting to Server');
resultType = 'error';
}
console.log(resultType, 'result.type');
const msgObj = receivedMsgTypeMapped[resultType].getMsg(result);
const msgRender = receivedMsgTypeMapped[resultType].contentToRender(msgObj);
const msgUpdate = receivedMsgTypeMapped[resultType].contentToUpdate(msgObj);
console.log('msgRender msgUpdate', msgRender, msgUpdate);
if (['whatsapp.message.updated', 'message', 'error'].includes(resultType)) {
updateMessageItem(msgUpdate);
// return false;
}
if (!isEmpty(msgRender)) {
sentOrReceivedNewMessage(msgRender.conversationid, msgRender);
window.Notification.requestPermission().then(function (permission) {
if (permission === 'granted') {
const notification = new Notification(`${msgRender.senderName}`, {
body: msgRender?.text || `[ ${msgRender.type} ]`,
...(msgRender.type === 'photo' ? { image: msgRender.data.uri } : {}),
});
notification.onclick = function () {
window.parent.parent.focus();
};
}
});
}
console.log('handleMessage*******************');
},
});
export const referenceMsgSlice = (set) => ({
referenceMsg: [],
setReferenceMsg: (referenceMsg) => set({ referenceMsg }),
});
export const conversationSlice = (set, get) => ({
conversationsList: [],
currentConversation: {},
setConversationsList: (conversationsList) => {
const conversationsMapped = conversationsList.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
return set({ conversationsList, activeConversations: conversationsMapped });
},
addToConversationList: (newList) => {
const { activeConversations } = get();
const conversationsIds = Object.keys(activeConversations);
const newConversations = newList.filter((conversation) => !conversationsIds.includes(`${conversation.sn}`));
const newConversationsMapped = newConversations.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
return set((state) => ({
conversationsList: [...newConversations, ...state.conversationsList],
activeConversations: { ...activeConversations, ...newConversationsMapped }
}));
},
delConversationitem: (conversation) => {
const { conversationsList, activeConversations } = get();
const targetId = conversation.sn;
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
conversationsList.splice(targetIndex, 1);
return set((state) => ({
conversationsList: [...conversationsList],
activeConversations: { ...activeConversations, [`${targetId}`]: [] },
currentConversation: {},
}));
},
setCurrentConversation: (conversation) => {
// 清空未读
const { conversationsList } = get();
const targetId = conversation.sn;
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
targetIndex !== -1 ? conversationsList.splice(targetIndex, 1, {
...conversationsList[targetIndex],
unread_msg_count: 0,
}) : null;
return set({ currentConversation: conversation, referenceMsg: {}, conversationsList: [...conversationsList] });
},
});
export const messageSlice = (set, get) => ({
activeConversations: {},
receivedMessageList: (conversationid, msgList) => set((state) => ({
activeConversations: { ...state.activeConversations, [String(conversationid)]: msgList }
})),
updateMessageItem: (message) => { // msgUpdate
console.log('UPDATE_SENT_MESSAGE_ITEM-----------------------------------------------------------------');
// 更新会话中的消息
const { activeConversations, conversationsList } = get();
const targetId = message.conversationid;
const targetMsgs = (activeConversations[String(targetId)] || []).map((ele) => {
// 更新状态
// * 已读的不再更新状态, 有时候投递结果在已读之后返回
if (ele.id === ele.actionId && ele.actionId === message.actionId) {
return { ...ele, id: message.id, status: ele.status === 'read' ? ele.status : message.status, dateString: message.dateString };
} else if (ele.id === message.id) {
return { ...ele, id: message.id, status: ele.status === 'read' ? ele.status : message.status, dateString: message.dateString };
}
return ele;
});
// 显示会话中其他客户端发送的消息
const targetMsgsIds = targetMsgs.map((ele) => ele.id);
if (!targetMsgsIds.includes(message.id)) {
targetMsgs.push(message);
}
// 更新列表的时间
// if (message.type !== 'error') {
// const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
// conversationsList.splice(targetIndex, 1, {
// ...conversationsList[targetIndex],
// last_received_time: message.date,
// });
// }
return set((state) => ({
activeConversations: { ...activeConversations, [String(targetId)]: targetMsgs },
conversationsList: [...conversationsList],
}));
},
sentOrReceivedNewMessage: (targetId, message) => { // msgRender:
const { activeConversations, conversationsList, currentConversation } = get();
const targetMsgs = activeConversations[String(targetId)] || [];
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
const newConversation =
targetId !== -1
? {
...conversationsList[targetIndex],
last_received_time: message.type !== 'system' && message.sender !== 'me' ? message.date : conversationsList[targetIndex].last_received_time,
unread_msg_count:
String(targetId) !== String(currentConversation.sn) && message.sender !== 'me'
? conversationsList[targetIndex].unread_msg_count + 1
: conversationsList[targetIndex].unread_msg_count,
}
: {
...message,
sn: targetId,
last_received_time: message.date,
unread_msg_count: message.sender === 'me' ? 0 : 1,
};
conversationsList.splice(targetIndex, 1);
conversationsList.unshift(newConversation);
return set((state) => ({
activeConversations: { ...activeConversations, [String(targetId)]: [...targetMsgs, message] },
conversationsList: [...conversationsList],
currentConversation: {
...currentConversation,
last_received_time: String(targetId) === String(currentConversation.sn) ? message.date : currentConversation.last_received_time,
},
}));
},
});
export const useConversationStore = create((set, get) => ({
...initialConversationState,
...websocketSlice(set, get),
...conversationSlice(set, get),
...templatesSlice(set, get),
...messageSlice(set, get),
...referenceMsgSlice(set, get),
// state actions
addError: (error) => set((state) => ({ errors: [...state.errors, error] })),
// side effects
fetchInitialData: async (userId) => {
olog('fetch init');
const { setConversationsList, setTemplates } = get();
const conversationsList = await fetchConversationsList({ opisn: userId });
setConversationsList(conversationsList);
const templates = await fetchTemplates();
setTemplates(templates);
},
}));
// window.store = useConversationStore; // debug:
export default useConversationStore;

@ -310,3 +310,10 @@ export const stringToColour = (str) => {
const color = '#' + hexString.substring(0, 6);
return color;
};
export const olog = (text, ...args) => {
console.log(
`%c ${text} `,
'background:#fb923c ; padding: 1px; border-radius: 3px; color: #fff',...args
);
};

@ -1,3 +1,4 @@
import { useEffect } from 'react';
import { Outlet, Link, useHref, NavLink } from 'react-router-dom'
import { Layout, Menu, ConfigProvider, theme, Empty, Row, Col, Avatar, Dropdown, Space, Typography, App as AntApp } from 'antd'
import { DownOutlined } from '@ant-design/icons'
@ -5,6 +6,7 @@ import ErrorBoundary from '@/components/ErrorBoundary'
import zhLocale from 'antd/locale/zh_CN'
import { useThemeContext } from '@/stores/ThemeContext'
import { useAuthContext } from '@/stores/AuthContext'
import useConversationStore from '@/stores/ConversationStore';
import 'dayjs/locale/zh-cn'
import 'react-chat-elements/dist/main.css'
@ -33,6 +35,17 @@ function AuthApp() {
token: { colorBgContainer },
} = theme.useToken()
const { connectWebsocket, disconnectWebsocket, fetchInitialData } = useConversationStore();
useEffect(() => {
if (loginUser && loginUser.userId) {
connectWebsocket(loginUser.userId);
fetchInitialData(loginUser.userId);
}
return () => {
disconnectWebsocket();
}
}, [loginUser, connectWebsocket, disconnectWebsocket, fetchInitialData]);
return (
<ConfigProvider
theme={{

@ -1,12 +1,13 @@
import { useEffect } from 'react';
import { Layout, Spin } from 'antd';
import { useParams, useNavigate } from 'react-router-dom';
// import { useParams, useNavigate } from 'react-router-dom';
import MessagesHeader from './Components/MessagesHeader';
import Messages from './Components/Messages';
import InputComposer from './Components/InputComposer';
import ConversationsList from './Components/ConversationsList';
import CustomerProfile from './Components/CustomerProfile';
import { useConversationState, useConversationDispatch } from '@/stores/ConversationContext';
// import { useAuthContext } from '@/stores/AuthContext';
// import useConversationStore from '@/stores/ConversationStore';
import './Conversations.css';
@ -17,8 +18,9 @@ const { Sider, Content, Header, Footer } = Layout;
*/
const ChatWindow = () => {
console.log('chat window;;;;;;;;;;;;;;;;;;;;;;;;');
const { order_sn } = useParams();
const { currentConversation } = useConversationState();
// const { order_sn } = useParams();
// const { loginUser } = useAuthContext();
// const { currentConversation } = useConversationStore();
useEffect(() => {
console.log('chat window 222;;;;;;;;;;;;;;;;;;;;;;;;');
@ -50,7 +52,7 @@ const ChatWindow = () => {
</Content>
<Sider width={300} theme={'light'} className='h-full overflow-y-auto' style={{ maxHeight: 'calc(100vh - 198px)', height: 'calc(100vh - 198px)' }}>
<CustomerProfile colisn={currentConversation.coli_sn ?? order_sn} />
<CustomerProfile />
</Sider>
</Layout>
</Spin>

@ -1,26 +1,23 @@
import { useRef, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { List, Avatar, Flex, Popconfirm, Button, Dropdown } from 'antd';
import { SendOutlined, MessageOutlined, SmileOutlined, PictureOutlined, CommentOutlined, UploadOutlined, CloudUploadOutlined, FolderAddOutlined, FilePdfOutlined, CloseCircleOutlined, CloseCircleFilled, MoreOutlined } from '@ant-design/icons';
import { Button, Dropdown } from 'antd';
import { MoreOutlined } from '@ant-design/icons';
import { useAuthContext } from '@/stores/AuthContext';
import { useConversationState, useConversationDispatch } from '@/stores/ConversationContext';
import {
fetchConversationsList,
setCurrentConversation,
addConversationList, delConversationitem,
fetchConversationItemClose,
fetchMessages, receivedMessageList,
fetchMessages,
} from '@/actions/ConversationActions';
import { ChatList, } from 'react-chat-elements';
import { isEmpty, pick } from '@/utils/utils';
import { v4 as uuid } from 'uuid';
import { isEmpty } from '@/utils/utils';
import useConversationStore from '@/stores/ConversationStore';
const CDropdown = (props) => {
const dispatch = useConversationDispatch();
const { delConversationitem } = useConversationStore();
const handleConversationItemClose = async () => {
await fetchConversationItemClose({ conversationid: props.sn, opisn: props.opi_sn });
dispatch(delConversationitem(props));
delConversationitem(props);
};
return (
<Dropdown
@ -51,8 +48,7 @@ const Conversations = () => {
const navigate = useNavigate();
const { loginUser } = useAuthContext();
const { userId } = loginUser;
const { conversationsList, activeConversations, currentConversation } = useConversationState();
const dispatch = useConversationDispatch();
const { activeConversations, currentConversation, conversationsList, addToConversationList, setCurrentConversation, receivedMessageList, } = useConversationStore();
const [chatlist, setChatlist] = useState([]);
useEffect(() => {
setChatlist(
@ -78,11 +74,13 @@ const Conversations = () => {
);
return () => {};
}, [conversationsList]);
}, [conversationsList, currentConversation]);
useEffect(() => {
if (order_sn) {
getOrderConversationList(order_sn);
// getOrderConversationList(order_sn);
// debug: reset chat window
setCurrentConversation({ sn: '', customer_name: '', coli_sn: order_sn });
}
return () => {};
@ -91,26 +89,24 @@ const Conversations = () => {
const getOrderConversationList = async (colisn) => {
const data = await fetchConversationsList({ opisn: userId, colisn });
if (!isEmpty(data)) {
dispatch(addConversationList(data));
addToConversationList(data);
switchConversation(data[0]);
// dispatch(setCurrentConversation(data[0]));
} else {
// reset chat window
dispatch(setCurrentConversation({ sn: '', customer_name: '', coli_sn: order_sn }));
setCurrentConversation({ sn: '', customer_name: '', coli_sn: order_sn });
return false;
}
};
const switchConversation = async (item) => {
console.log('invoke switch');
const messagesList = activeConversations[`${item.sn}`] || [];
if (isEmpty(messagesList)) {
const data = await fetchMessages({ opisn: userId, whatsappid: item.whatsapp_phone_number });
dispatch(receivedMessageList(item.sn, data));
receivedMessageList(item.sn, data);
}
if (String(item.sn) === String(currentConversation.sn)) {
return false;
}
dispatch(setCurrentConversation(item));
setCurrentConversation(item);
};
const onSwitchConversation = (item) => {

@ -1,7 +1,7 @@
import { Card, Flex, Avatar, Typography, Radio, Button, Table } from 'antd';
import { useAuthContext } from '@/stores/AuthContext.js';
import { useConversationState } from '@/stores/ConversationContext';
import { useLocation } from 'react-router-dom'
// import { useConversationState } from '@/stores/ConversationContext';
import { useLocation, useParams } from 'react-router-dom'
import { HomeOutlined, LoadingOutlined, SettingFilled, SmileOutlined, SyncOutlined, PhoneOutlined, MailOutlined, WhatsAppOutlined, SmileTwoTone } from '@ant-design/icons';
import CreatePayment from './CreatePayment';
@ -22,13 +22,15 @@ const orderStatus = [
const { Meta } = Card;
const CustomerProfile = (({ colisn }) => {
const CustomerProfile = (() => {
let { state } = useLocation()
console.info(state)
console.log(useParams());
const { order_sn: colisn } = useParams();
console.log('invoke customer profile+++++++++++++++++++++++++++++++++++++++++++++', colisn);
const { customerOrderProfile: orderInfo } = useConversationState();
// const { customerOrderProfile: orderInfo } = useConversationState();
const { loginUser: currentUser } = useAuthContext();
const { quotes, contact, last_contact, ...order } = orderInfo;
const { quotes, contact, last_contact, ...order } = {}; // orderInfo;
return (
<div className=' divide-x-0 divide-y divide-dashed divide-gray-300 '>

@ -1,9 +1,8 @@
import React, { useState } from 'react';
import { Input, Flex, Button, } from 'antd';
// import { Input } from 'react-chat-elements';
import { useConversationState, useConversationDispatch } from '@/stores/ConversationContext';
import { useAuthContext } from '@/stores/AuthContext';
import { sentNewMessage, setReplyTo } from '@/actions/ConversationActions';
import useConversationStore from '@/stores/ConversationStore';
import { SendOutlined, MessageOutlined, SmileOutlined, PictureOutlined, CommentOutlined, UploadOutlined, CloudUploadOutlined, FolderAddOutlined, FilePdfOutlined, CloseCircleOutlined } from '@ant-design/icons';
import { isEmpty } from '@/utils/utils';
import { v4 as uuid } from 'uuid';
@ -14,15 +13,14 @@ import dayjs from 'dayjs';
const InputBox = () => {
const { loginUser } = useAuthContext();
const { userId } = loginUser;
const { websocket, websocketOpened, currentConversation, referenceMsg } = useConversationState();
const dispatch = useConversationDispatch();
const { websocket, websocketOpened, currentConversation, referenceMsg, setReferenceMsg, sentOrReceivedNewMessage } = useConversationStore();
const [textContent, setTextContent] = useState('');
const talkabled = !isEmpty(currentConversation.sn) && websocketOpened;
const gt24h = currentConversation.last_received_time ? dayjs().diff(dayjs(currentConversation.last_received_time), 'hour') > 24 : true;
const textabled = talkabled && !gt24h;
const textabled = (talkabled && !gt24h);
const invokeSendMessage = (msgObj) => {
console.log('sendMessage------------------', msgObj);
@ -31,7 +29,7 @@ const InputBox = () => {
websocket.sendMessage({ ...contentToSend, opi_sn: userId, coli_sn: currentConversation.coli_sn });
const contentToRender = sentMsgTypeMapped[msgObj.type].contentToRender(msgObj);
console.log(contentToRender, 'contentToRender sendMessage------------------');
dispatch(sentNewMessage(contentToRender));
sentOrReceivedNewMessage(contentToRender.conversationid, contentToRender);
};
const handleSendText = () => {
@ -48,16 +46,33 @@ const InputBox = () => {
};
invokeSendMessage(msgObj);
setTextContent('');
dispatch(setReplyTo({}));
setReferenceMsg({});
}
};
const handleSendImage = () => {
if (textContent.trim() !== '') {
const msgObj = {
type: 'photo',
data: { uri: textContent },
sender: 'me',
to: currentConversation.whatsapp_phone_number,
id: `${currentConversation.sn}.${uuid()}`, // Date.now().toString(16),
date: new Date(),
status: 'waiting',
...(referenceMsg.id ? { context: { message_id: referenceMsg.id }, message_origin: referenceMsg } : {}),
};
invokeSendMessage(msgObj);
setTextContent('');
setReferenceMsg({});
}
}
return (
<div>
{referenceMsg.id && (
<Flex justify='space-between' className='reply-to bg-gray-100 p-1 rounded-none text-slate-500'>
<div className=' border-l-3 border-y-0 border-r-0 border-slate-300 border-solid pl-2 pr-1 py-1'>{referenceMsg.text}</div>
<Button type='text' title='取消引用' className=' rounded-none text-slate-500' icon={<CloseCircleOutlined />} size={'middle'} onClick={() => dispatch(setReplyTo({}))} />
<Button type='text' title='取消引用' className=' rounded-none text-slate-500' icon={<CloseCircleOutlined />} size={'middle'} onClick={() => (setReferenceMsg({}))} />
</Flex>
)}
<Input.TextArea

@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from 'react';
import { App, Popover, Flex, Button, List, Input } from 'antd';
import { MessageOutlined, SendOutlined } from '@ant-design/icons';
import { useAuthContext } from '@/stores/AuthContext';
import { useConversationState } from '@/stores/ConversationContext';
import useConversationStore from '@/stores/ConversationStore';
import { cloneDeep, getNestedValue, objectMapper } from '@/utils/utils';
import { v4 as uuid } from 'uuid';
import { replaceTemplateString } from '@/lib/msgUtils';
@ -25,7 +25,7 @@ const InputTemplate = ({ disabled = false, invokeSendMessage }) => {
const searchInputRef = useRef(null);
const { notification } = App.useApp();
const { loginUser } = useAuthContext();
const { currentConversation, templates } = useConversationState();
const { currentConversation, templates } = useConversationStore();
// : customer, agent
const valueMapped = { ...cloneDeep(currentConversation), ...objectMapper(loginUser, { username: [{ key: 'agent_name' }, { key: 'your_name' }] }) };
useEffect(() => {

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { Typography } from 'antd';
import { useConversationState } from '@/stores/ConversationContext';
// import { useConversationState } from '@/stores/ConversationContext';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
@ -9,7 +9,7 @@ dayjs.extend(utc);
dayjs.extend(timezone);
const LocalTimeClock = ((props) => {
const { customerOrderProfile: orderInfo } = useConversationState();
// const { customerOrderProfile: orderInfo } = useConversationState();
const [customerDateTime, setCustomerDateTime] = useState();

@ -1,20 +1,35 @@
import { useEffect, useState, useRef, useMemo } from 'react';
import { Image, Alert } from 'antd';
import { MessageBox } from 'react-chat-elements';
import { useConversationState, useConversationDispatch } from '@/stores/ConversationContext';
import { setReplyTo } from '@/actions/ConversationActions';
import useConversationStore from '@/stores/ConversationStore';
import { useShallow } from 'zustand/react/shallow';
const Messages = () => {
const { activeConversations, currentConversation } = useConversationState();
const dispatch = useConversationDispatch();
const { currentConversation, setReferenceMsg } = useConversationStore();
const activeMessages = useConversationStore(useShallow(state => currentConversation.sn ? state.activeConversations[currentConversation.sn] : []));
const messagesList = useMemo(() => activeConversations[currentConversation.sn] || [], [activeConversations, currentConversation.sn]);
const messagesList = useMemo(() => (activeMessages || []).map(message => ({
...message,
key: message.id,
position: message.sender === 'me' ? 'right' : 'left',
onOpen: () => handlePreview(message),
...(message.sender === 'me' ? {
styles: { backgroundColor: '#ccd4ae' } ,
notchStyle: { fill: '#ccd4ae'},
replyButton: ['text'].includes(message.whatsapp_msg_type) && message.status !== 'failed' ? true : false,
onReplyClick: () => (setReferenceMsg(message)),
className: 'whatsappme-container whitespace-pre-wrap',
} : {
replyButton: ['text'].includes(message.whatsapp_msg_type) ? true : false,
onReplyClick: () => (setReferenceMsg(message)),
}),
})), [activeMessages, currentConversation.sn]);
console.log('messagesList----------------------------------------------------', messagesList);
const messagesEndRef = useRef(null);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messagesList, currentConversation.last_received_time]);
}, [currentConversation.last_received_time]);
const [previewVisible, setPreviewVisible] = useState(false);
const [previewSrc, setPreviewSrc] = useState();
@ -33,31 +48,8 @@ const Messages = () => {
return (
<div>
{messagesList.map((message, index) => (
<MessageBox
// className={message.sender === 'me' ? 'whatsappme-container' : ''}
key={message.id}
position={message.sender === 'me' ? 'right' : 'left'}
<MessageBox key={message.key}
{...message}
onOpen={() => handlePreview(message)}
// letterItem={{ id: 1, letter: 'AS' }}
// 'waiting'| 'sent' | 'received' | 'read'
styles={{ backgroundColor: message.sender === 'me' ? '#ccd5ae' : '' }}
{...(message.sender === 'me'
? {
style: { backgroundColor: '#ccd5ae' },
notchStyle: { fill: '#ccd5ae' },
// forwarded: false,
// todo: reaction: emoji
replyButton: ['text'].includes(message.whatsapp_msg_type) && message.status !== 'failed' ? true : false,
onReplyClick: () => dispatch(setReplyTo(message)),
className: 'whatsappme-container whitespace-pre-wrap',
}
: {
replyButton: ['text'].includes(message.whatsapp_msg_type) ? true : false,
onReplyClick: () => dispatch(setReplyTo(message)),
})}
// notchStyle={{fill: '#ccd5ae'}}
// copiableDate={false}
/>
))}
<Image src={previewSrc} preview={{ visible: previewVisible, src: previewSrc, onClose: onPreviewClose }} />

@ -1,12 +1,12 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { useConversationState } from '@/stores/ConversationContext';
import useConversationStore from '@/stores/ConversationStore';
import { Flex, Typography, Avatar, Alert } from 'antd';
import { LoadingOutlined } from '@ant-design/icons';
import LocalTimeClock from './LocalTimeClock';
const MessagesHeader = () => {
const { websocketOpened, websocketRetrying, websocketRetrytimes, currentConversation } = useConversationState();
const {websocketOpened, websocketRetrying, websocketRetrytimes, currentConversation} = useConversationStore();
return (
<>
{websocketOpened===false && <Alert type='error' message='断开连接' banner />}

@ -1,10 +1,10 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { Popover, Flex, Button, List, Popconfirm } from 'antd';
import { useConversationState } from '@/stores/ConversationContext';
// import { useConversationState } from '@/stores/ConversationContext';
const QuotesHistory = ((props) => {
const { customerOrderProfile: orderInfo } = useConversationState();
const { quotes, ...order } = orderInfo;
// const { customerOrderProfile: orderInfo } = useConversationState();
const { quotes, ...order } = {} // orderInfo;
const [open, setOpen] = useState(false);
const handleOpenChange = (newOpen) => {

@ -33,37 +33,37 @@ const ConversationProvider = ({ children }) => {
console.log('ConversationProvider', state, dispatch);
useEffect(() => {
console.log('invoke provider');
const realtimeAPI = new RealTimeAPI(
{
url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`,
protocol: 'WhatsApp',
},
() => {dispatch(updateWebsocketState(true)); dispatch(updateWebsocketRetrytimes(0));},
() => dispatch(updateWebsocketState(false)),
(n) => dispatch(updateWebsocketRetrytimes(n))
);
// useEffect(() => {
// console.log('invoke provider');
// const realtimeAPI = new RealTimeAPI(
// {
// url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`,
// protocol: 'WhatsApp',
// },
// () => {dispatch(updateWebsocketState(true)); dispatch(updateWebsocketRetrytimes(0));},
// () => dispatch(updateWebsocketState(false)),
// (n) => dispatch(updateWebsocketRetrytimes(n))
// );
realtimeAPI.onError(() => dispatch(addError('Error')));
realtimeAPI.onMessage(handleMessage);
realtimeAPI.onCompletion(() => dispatch(addError('Connection broken')));
// realtimeAPI.onError(() => dispatch(addError('Error')));
// realtimeAPI.onMessage(handleMessage);
// realtimeAPI.onCompletion(() => dispatch(addError('Connection broken')));
dispatch(initWebsocket(realtimeAPI));
return () => {
realtimeAPI.disconnect();
};
}, []);
// dispatch(initWebsocket(realtimeAPI));
// return () => {
// realtimeAPI.disconnect();
// };
// }, []);
useEffect(() => {
fetchConversationsList({ opisn: userId }).then((data) => {
dispatch(receivedConversationList(data));
});
// useEffect(() => {
// fetchConversationsList({ opisn: userId }).then((data) => {
// dispatch(receivedConversationList(data));
// });
fetchTemplates().then((data) => dispatch(receivedTemplates(data)));
// fetchTemplates().then((data) => dispatch(receivedTemplates(data)));
return () => {};
}, []);
// return () => {};
// }, []);
const handleMessage = (data) => {
console.log('handleMessage------------------');
console.log(data);

Loading…
Cancel
Save