From ef712526178c6ea3ab3b4646c77814467c2ea0d4 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 25 Jan 2024 17:20:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E6=B6=88=E6=81=AF=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit todo: 更新已发送的消息的状态. renderId --- src/lib/msgUtils.js | 124 +++- src/stores/ConversationContext.js | 648 +++++++++++------- .../Conversations/Components/InputBox.jsx | 68 +- .../Conversations/Components/Messages.jsx | 5 +- src/views/Conversations/Conversations.css | 11 + tailwind.config.js | 4 + 6 files changed, 558 insertions(+), 302 deletions(-) diff --git a/src/lib/msgUtils.js b/src/lib/msgUtils.js index 72abe63..adce0ed 100644 --- a/src/lib/msgUtils.js +++ b/src/lib/msgUtils.js @@ -1,19 +1,45 @@ + +export const replaceTemplateString = (str, replacements) => { + let result = str; + let keys = str.match(/{{(.*?)}}/g).map(key => key.replace(/{{|}}/g, '')); + + for (let i = 0; i < keys.length; i++) { + let replaceValue = replacements[i]; + let template = new RegExp(`{{${keys[i]}}}`, 'g'); + result = result.replace(template, replaceValue); + } + + return result; +} +/** + * +// +8618777396951 lyj +{ + "to": "+8613317835586", // qqs + "msgtype": "text", + "msgcontent": "{\"body\":\"txtmsgtest\"}" +} + */ export const sentMsgTypeMapped = { text: { type: 'text', - contentToSend: (msg) => ({ type: 'text', from: '+8617607730395', to: '', text: { body: msg.text } }), - contentToRender: (msg) => ({...msg}), + contentToSend: (msg) => ({ renderId: msg.id, to: msg.to, msgtype: 'text', msgcontent: { body: msg.text } }), + contentToRender: (msg) => ({ ...msg }), }, whatsappTemplate: { - type: 'template', - contentToSend: (msg) => ({ - template: { - namespace: msg.whatsappTemplate.namespace, - language: msg.whatsappTemplate.language, - type: msg.whatsappTemplate.type, - components: msg.whatsappTemplate.components, - }, - }), + contentToSend: (msg) => ({ renderId: msg.id, to: msg.to, msgtype: 'template', msgcontent: msg.template }), + contentToRender: (msg) => { + console.log(msg); + const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({...r, [v.type]: v}), {}) : null; + const templateParam = (templateDataMapped?.body?.parameters || []).map(e => e.text); + const fillTemplate = templateParam.length ? replaceTemplateString(msg.template_origin.components.body?.[0]?.text || '', templateParam) : (msg.template_origin.components.body?.[0]?.text || ''); + return { + ...msg, + type: 'text', + title: msg.template_origin.components.header?.[0]?.text || '', + text: fillTemplate, // msg.template_origin.components.body?.[0]?.text || '', + }; + }, }, }; export const whatsappMsgMapped = { @@ -23,23 +49,48 @@ export const whatsappMsgMapped = { return result?.whatsappInboundMessage || null; }, contentToRender: (result) => { - console.log( 'whatsapp.inbound_message.received', result); + console.log('whatsapp.inbound_message.received to render', result); const contentObj = result?.whatsappInboundMessage || result; // debug: return parseRenderMessageItem(contentObj); }, }, + 'whatsapp.message.updated': { + getMsg: (result) => { + console.log('getMsg', result); + let contentObj = result?.whatsappMessage || null; + if ((contentObj?.status === 'failed' )) { + contentObj = { + type: 'error', + text: {body: 'Message failed to send' }, // contentObj.errorMessage + id: contentObj.id, + wamid: contentObj.id, + }; + } + return contentObj; + }, + contentToRender: (msgcontent) => { + const contentObj = msgcontent?.whatsappMessage || msgcontent; // debug: + console.log('whatsapp.message.updated to render', contentObj); + const _r = parseRenderMessageItem(contentObj); + console.log('_r', _r); + return parseRenderMessageItem(contentObj); + }, + contentToUpdate: (msg) => ({ ...msg, id: msg.wamid, stauts: msg.status }), + }, }; export const whatsappMsgTypeMapped = { - text: { type: 'text', data: (msg) => ({ text: msg.text.body }) }, + error: { + type: (_m) => ({ type: 'system' }), + data: (msg) => ({ id: msg.wamid, text: msg.errorCode ? msg.errorMessage : msg.text.body }), + }, + text: { + type: (msg) => ({ type: msg.errorCode ? 'system' : 'text' }), + data: (msg) => ({ id: msg.wamid, text: msg.errorCode ? msg.errorMessage : msg.text.body }), + }, image: { type: 'photo', data: (msg) => ({ - data: { - uri: msg.image.link, - width: 200, - height: 200, - alt: '', - }, + data: { id: msg.wamid, uri: msg.image.link, width: 200, height: 200, alt: '' }, onOpen: () => { console.log('Open image', msg.image.link); }, @@ -48,18 +99,14 @@ export const whatsappMsgTypeMapped = { sticker: { type: 'photo', data: (msg) => ({ - data: { - uri: msg.sticker.link, - width: 150, - height: 120, - alt: '', - }, + data: { id: msg.wamid, uri: msg.sticker.link, width: 150, height: 120, alt: '' }, }), }, video: { type: 'video', data: (msg) => ({ data: { + id: msg.wamid, videoURL: msg.video.link, status: { click: true, @@ -72,13 +119,25 @@ export const whatsappMsgTypeMapped = { audio: { type: 'audio', data: (msg) => ({ + id: msg.wamid, data: { audioURL: msg.audio.link, }, }), }, - 'unsupported': { type: 'system', data: (msg) => ({ text: 'Message type is currently not supported.' }) }, - // 'unsupported': { type: 'text', data: (msg) => ({ text: 'Message type is currently not supported.' }) } + unsupported: { type: 'system', data: (msg) => ({ text: 'Message type is currently not supported.' }) }, + reaction: { + type: 'text', + data: (msg) => ({ id: msg.wamid, text: msg.reaction?.emoji || msg.reaction?.text?.body || 'Reaction', reply: { message: '{content}', title: 'React from' } }), + }, + template: { + type: (msg) => 'text', + data: (msg) => ({ + id: msg.wamid, + // todo: update status: sent + status: msg.status, + }), + }, // file: 'file', // location: 'location', // contact: 'contact', @@ -87,23 +146,24 @@ export const whatsappMsgTypeMapped = { // 'contact-card-with-photo-and-label': 'contact-card-with-photo-and-label', }; export const parseRenderMessageItem = (msg) => { - console.log(msg, '[[[['); + console.log('parseRenderMessageItem', msg); return { + date: msg?.sendTime || '', ...(whatsappMsgTypeMapped?.[msg.type]?.data(msg) || {}), - id: msg.id, + ...(whatsappMsgTypeMapped?.[msg.type]?.type(msg) || { type: 'text' }), // type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text', sender: msg.from, - type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text', + status: msg?.status || 'waiting', // title: msg.customerProfile.name, - date: msg.sendTime, + // replyButton: true, }; }; export const parseRenderMessageList = (messages) => { return messages.map((msg) => { return { ...(whatsappMsgTypeMapped?.[msg.type]?.data(msg) || {}), + ...(whatsappMsgTypeMapped?.[msg.type]?.type(msg) || { type: 'text' }), // type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text', id: msg.id, sender: msg.from, - type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text', // title: msg.customerProfile.name, date: msg.sendTime, }; diff --git a/src/stores/ConversationContext.js b/src/stores/ConversationContext.js index 92b06e9..b881347 100644 --- a/src/stores/ConversationContext.js +++ b/src/stores/ConversationContext.js @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useState, useEffect } from 'react'; +import React, { createContext, useContext, useState, useEffect, useRef } from 'react'; import { RealTimeAPI } from '@/lib/realTimeAPI'; import { whatsappMsgMapped, sentMsgTypeMapped, parseRenderMessageList } from '@/lib/msgUtils'; import { groupBy } from '@/utils/utils'; @@ -13,48 +13,77 @@ export async function fetchJSON(url, data) { ifp = params ? '?' : ifp; } ifp = url.includes('?') ? '' : ifp; - const host = /^https?:\/\//i.test(url) ? '': ''; // HT_HOST; + const host = /^https?:\/\//i.test(url) ? '' : ''; // HT_HOST; const response = await fetch(`${host}${url}${ifp}${params}`); return await response.json(); } // const API_HOST = 'http://202.103.68.144:8888'; const API_HOST = 'http://202.103.68.144:8888'; -const WS_URL = 'ws://202.103.68.144:8888/whatever/'; -// const WS_URL = 'ws://202.103.68.157:8888/whatever/'; +// const WS_URL = 'ws://202.103.68.144:8888/whatever/'; +const WS_URL = 'ws://202.103.68.157:8888/whatever/'; // let realtimeAPI = new RealTimeAPI({ url: URL, protocol: 'aaa' }); let realtimeAPI = new RealTimeAPI({ url: WS_URL, protocol: 'WhatsApp' }); export const useConversations = () => { + const [errors, setErrors] = useState([]); const [messages, setMessages] = useState([]); // active conversation - const [conversations, setConversations] = useState({}); // all conversation + const [activeConversations, setActiveConversations] = useState({}); // all active conversation const [currentID, setCurrentID] = useState(); const [conversationsList, setConversationsList] = useState([]); // open conversations const [currentConversation, setCurrentConversation] = useState({ - id: '', name: '' + id: '', + name: '', }); + const currentConversationRef = useRef(currentConversation); + useEffect(() => { + currentConversationRef.current = currentConversation; + }, [currentConversation]); + + useEffect(() => { + realtimeAPI.onError(addError.bind(null, 'Error')); + realtimeAPI.onMessage(handleMessage); + realtimeAPI.onCompletion(addError.bind(null, 'Not Connected to Server')); + realtimeAPI.keepAlive(); // Ping Server + + // Cleanup function to remove the event listeners when the component is unmounted + return () => { + realtimeAPI.disconnect(); + }; + }, []); useEffect(() => { - getConversations(); + getConversationsList(); // getTemplates(); return () => {}; }, []); - const getConversations = async () => { + useEffect(() => { + if (!currentConversation.id && conversationsList.length > 0) { + switchConversation(conversationsList[0]); + } + return () => {}; + }, [conversationsList]); + + + const getConversationsList = async () => { const data = await fetchJSON('http://127.0.0.1:4523/m2/3888351-0-default/142426823'); + const dataMapped = data.reduce((r, v) => ({ ...r, [v.id]: [] }), {}); setConversationsList(data); - if (data && data.length) { - switchConversation(data[0]); - } + setActiveConversations({...dataMapped, ...activeConversations}); + console.log(data, dataMapped); + // if (data && data.length) { + // switchConversation(data[0]); + // } }; - const templates = AllTemplates.filter(_t => _t.status !== 'REJECTED').map( ele => ({...ele, components: groupBy(ele.components, _c => _c.type.toLowerCase())})); // test: 0 + const templates = AllTemplates.filter((_t) => _t.status !== 'REJECTED').map((ele) => ({ ...ele, components: groupBy(ele.components, (_c) => _c.type.toLowerCase()) })); // test: 0 // const [templates, setTemplates] = useState([]); const [templatesList, setTemplatesList] = useState([]); const getTemplates = async () => { const data = await fetchJSON(`${API_HOST}/listtemplates`); - const canUseTemplates = (data?.result?.items || []).filter(_t => _t.status !== 'REJECTED'); + const canUseTemplates = (data?.result?.items || []).filter((_t) => _t.status !== 'REJECTED'); setTemplatesList(canUseTemplates); }; @@ -66,36 +95,85 @@ export const useConversations = () => { }; const switchConversation = (cc) => { - console.log('switch to ', cc.id, cc); setCurrentID(cc.id); setCurrentConversation(cc); - // debug: 0 - const _all = all.map((ele) => whatsappMsgMapped['whatsapp.inbound_message.received'].contentToRender(ele)); - setMessages([..._all,...conversations[cc.id] || []]); + const _all = []; + // const _all = all.map((ele) => whatsappMsgMapped['whatsapp.inbound_message.received'].contentToRender(ele)); // debug: 0 + // todo: update msg status + // const _all = all2.map((ele) => whatsappMsgMapped['whatsapp.message.updated'].contentToRender(ele)); // debug: 0 + setMessages([..._all, ...(activeConversations[cc.id] || [])]); // Get customer profile when switching conversation - getCustomerProfile(cc.id); + // getCustomerProfile(cc.id); }; + useEffect(() => { + return () => { + getCustomerProfile(currentID); + }; + }, [currentID]); + /** + * ***************************************************************************************************** * websocket -------------------------------------------------------------------------------------------- + * ***************************************************************************************************** */ const addError = (reason) => { - setErrors(prevErrors => [...prevErrors, { reason }]); - } + setErrors((prevErrors) => [...prevErrors, { reason }]); + }; const addMessageToConversations = (customerId, message) => { - setConversations((prevList) => ({ - ...prevList, - [customerId]: [...(prevList[customerId] || []), message], - })); + // setActiveConversations((prevList) => ({ + // ...prevList, + // [customerId]: [...(prevList[customerId] || []), message], + // })); + // console.log('activeConversations', activeConversations); + setActiveConversations((prevList) => { + const updatedList = {...prevList}; + if(updatedList[customerId]) { + updatedList[customerId].push(message); + } else { + updatedList[customerId] = [message]; + } + return updatedList; + }); }; + useEffect(() => { + console.log(messages, 'messages'); + + return () => { + + } + }, [messages]) + + const addMessage = (message) => { - setMessages((prevMessages) => [ ...prevMessages, message]); - addMessageToConversations(currentConversation.id, message); + setMessages((prevMessages) => [...prevMessages, message]); + addMessageToConversations(currentConversationRef.current.id, message); + }; + + const updateMessage = (message) => { + setMessages((prevMessages) => { + return prevMessages.map(ele => { + if (ele.id === message.id) { + return {...ele, status: message.status}; + } + return ele; + }); + // const updatedList = [...prevMessages]; + // const index = prevMessages.findIndex((_m) => _m.id === message.id); + // if (index !== -1) { + // updatedList[index] = message; + // } + // return updatedList; + }); }; const handleMessage = (data) => { + console.log('handleMessage------------------', ); + /** + * ! event handlers in JavaScript capture the environment (including variables) at the time they are defined, not when they are executed. + */ const { errcode, errmsg, result } = data; if (!result) { @@ -104,293 +182,351 @@ export const useConversations = () => { if (typeof result.type === 'string' && result.type === 'error') { addError('Error Connecting to Server'); } - console.log(result, 'handleMessage------------------'); + console.log(result.type, 'result.type'); const msgObj = whatsappMsgMapped[result.type].getMsg(result); - console.log(msgObj, 'msgObj'); - addMessage(whatsappMsgMapped[result.type].contentToRender(msgObj)); + const msgRender = whatsappMsgMapped[result.type].contentToRender(msgObj); + const msgUpdate = whatsappMsgMapped[result.type].contentToUpdate(msgObj); + console.log('msgRender', msgRender, msgUpdate); + if (result.type === 'whatsapp.message.updated') { + updateMessage(msgRender); + return false; + } + addMessage(msgRender); + console.log('handleMessage*******************', ); }; const sendMessage = (msgObj) => { const contentToSend = sentMsgTypeMapped[msgObj.type].contentToSend(msgObj); realtimeAPI.sendMessage(contentToSend); const contentToRender = sentMsgTypeMapped[msgObj.type].contentToRender(msgObj); + console.log(contentToRender, 'contentToRender sendMessage------------------'); addMessage(contentToRender); }; - realtimeAPI.onError(addError.bind(null, 'Error')); - realtimeAPI.onMessage(handleMessage); - realtimeAPI.onCompletion(addError.bind(null, 'Not Connected to Server')); - realtimeAPI.keepAlive(); // Ping Server - return { - errors, messages, conversationsList, currentConversation, sendMessage, - getConversations, switchConversation, + errors, + messages, + conversationsList, + currentConversation, + sendMessage, + getConversationsList, + switchConversation, // templates: templatesList, // setTemplates, getTemplates, templates, // debug: 0 customerOrderProfile, }; -} - -// test: 0 +}; +// test: 0 "type": "whatsapp.inbound_message.received", const all = [ + // { + // 'id': '65b06828619a1d82777eb4c6', + // 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBQzNDNzBFNjFCREJBNDIyQjQ2AA==', + // 'wabaId': '190290134156880', + // 'from': '+8613317835586', + // 'customerProfile': { + // 'name': 'qqs', + // }, + // 'to': '+8617607730395', + // 'sendTime': '2024-01-24T01:30:14.000Z', + // 'type': 'image', + // 'image': { + // 'link': + // 'https://api.ycloud.com/v2/whatsapp/media/download/934379820978291?sig=t%3D1706059814%2Cs%3D91a79a0e4007ad2f6a044a28307affe663f7f81903b3537bd80e758d3c0d0563&payload=eyJpZCI6IjkzNDM3OTgyMDk3ODI5MSIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNjgyODYxOWExZDgyNzc3ZWI0YzYiLCJtaW1lVHlwZSI6ImltYWdlL2pwZWciLCJzaGEyNTYiOiJPVTJjdkN2eHplMUdMMmQ5NUxyTGVaNmpNb2ZscUZYM1RvcXdTTUNWZkxNPSJ9', + // 'id': '934379820978291', + // 'sha256': 'OU2cvCvxze1GL2d95LrLeZ6jMoflqFX3ToqwSMCVfLM=', + // 'mime_type': 'image/jpeg', + // }, + // }, + // { + // 'id': '65b06ce6619a1d8277c97fc0', + // 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBMUJBOUZCODY4NkNBMkM2NUEzAA==', + // 'wabaId': '190290134156880', + // 'from': '+8613317835586', + // 'customerProfile': { + // 'name': 'qqs', + // }, + // 'to': '+8617607730395', + // 'sendTime': '2024-01-24T01:50:29.000Z', + // 'type': 'text', + // 'text': { + // 'body': 'eeee', + // }, + // }, + // { + // 'id': '65b06b2f619a1d8277b5ab06', + // 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBRkU0RUZGRUI1OUQzQUFBMEExAA==', + // 'wabaId': '190290134156880', + // 'from': '+8613317835586', + // 'customerProfile': { + // 'name': 'qqs', + // }, + // 'to': '+8617607730395', + // 'sendTime': '2024-01-24T01:43:09.000Z', + // 'type': 'audio', + // 'audio': { + // 'link': + // 'https://api.ycloud.com/v2/whatsapp/media/download/901696271448320?sig=t%3D1706060589%2Cs%3Dca75dbd57e4867783390c913491263f07c9738d69c141d4ae622c76df9fa033b&payload=eyJpZCI6IjkwMTY5NjI3MTQ0ODMyMCIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNmIyZjYxOWExZDgyNzdiNWFiMDYiLCJtaW1lVHlwZSI6ImF1ZGlvL29nZzsgY29kZWNzPW9wdXMiLCJzaGEyNTYiOiJoZUNSUDdEMjM3bG9ydkZ4eFhSdHZpU1ZsNDR3Rlk4TytaMFhic2k5cy9rPSJ9', + // 'id': '901696271448320', + // 'sha256': 'heCRP7D237lorvFxxXRtviSVl44wFY8O+Z0Xbsi9s/k=', + // 'mime_type': 'audio/ogg; codecs=opus', + // }, + // }, + // { + // 'id': '65b06b12619a1d8277b3c0c4', + // 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBREZEMEM0MURDNjJGREVEQjY3AA==', + // 'wabaId': '190290134156880', + // 'from': '+8613317835586', + // 'customerProfile': { + // 'name': 'qqs', + // }, + // 'to': '+8617607730395', + // 'sendTime': '2024-01-24T01:42:40.000Z', + // 'type': 'video', + // 'video': { + // 'link': + // 'https://api.ycloud.com/v2/whatsapp/media/download/742404324517058?sig=t%3D1706060560%2Cs%3D53eeb1508c2103e310fb14a72563a8e07c5a84c7e6192a25f3608ac9bea32334&payload=eyJpZCI6Ijc0MjQwNDMyNDUxNzA1OCIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNmIxMjYxOWExZDgyNzdiM2MwYzQiLCJtaW1lVHlwZSI6InZpZGVvL21wNCIsInNoYTI1NiI6IlNJcjRlZFlPb1BDTGtETEgrVTY2d3dkMDgra2JndFV5OHRDd2RjQU5FaFU9In0', + // 'caption': 'and', + // 'id': '742404324517058', + // 'sha256': 'SIr4edYOoPCLkDLH+U66wwd08+kbgtUy8tCwdcANEhU=', + // 'mime_type': 'video/mp4', + // }, + // }, + // { + // 'id': '65b06aa7619a1d8277ac806e', + // 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBOTFBOTU5RDE2QjgxQTQ1MEE2AA==', + // 'wabaId': '190290134156880', + // 'from': '+8613317835586', + // 'customerProfile': { + // 'name': 'qqs', + // }, + // 'to': '+8617607730395', + // 'sendTime': '2024-01-24T01:40:53.000Z', + // 'type': 'sticker', + // 'sticker': { + // 'link': + // 'https://api.ycloud.com/v2/whatsapp/media/download/1156118002042289?sig=t%3D1706060453%2Cs%3Dfbd5f881856614e35715b1e3e1097b3bbe56f8a36aaa67bfbef25a37d9143d51&payload=eyJpZCI6IjExNTYxMTgwMDIwNDIyODkiLCJ3YWJhSWQiOiIxOTAyOTAxMzQxNTY4ODAiLCJpbmJvdW5kTWVzc2FnZUlkIjoiNjViMDZhYTc2MTlhMWQ4Mjc3YWM4MDZlIiwibWltZVR5cGUiOiJpbWFnZS93ZWJwIiwic2hhMjU2IjoibUNaLzdhNnNaNlRNYTE0WW9rUkNTZnVsdGpZNmFRRVZFNVoxMVRwanNQOD0ifQ', + // 'id': '1156118002042289', + // 'sha256': 'mCZ/7a6sZ6TMa14YokRCSfultjY6aQEVE5Z11TpjsP8=', + // 'mime_type': 'image/webp', + // 'animated': false, + // }, + // }, + // { + // 'id': '65b06a91619a1d8277aaf05e', + // 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBRjUxNzdCQ0FEOTlFQzc5MzQ1AA==', + // 'wabaId': '190290134156880', + // 'from': '+8613317835586', + // 'customerProfile': { + // 'name': 'qqs', + // }, + // 'to': '+8617607730395', + // 'sendTime': '2024-01-24T01:40:32.000Z', + // 'type': 'unsupported', + // 'errors': [ + // { + // 'code': '131051', + // 'title': 'Message type unknown', + // 'message': 'Message type unknown', + // 'error_data': { + // 'details': 'Message type is currently not supported.', + // }, + // }, + // ], + // }, +]; +const all2 = [ + // { + // 'id': '63f71fb8741c165b434292fb', + // 'wamid': 'wamid.HBgNOD...', + // 'wabaId': 'WABA-ID', + // 'from': 'CUSTOMER-PHONE-NUMBER', + // 'customerProfile': { + // 'name': 'Joe', + // }, + // 'to': 'BUSINESS-PHONE-NUMBER', + // 'sendTime': '2023-02-22T12:00:00.000Z', + // 'type': 'reaction', + // 'reaction': { + // 'message_id': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBMUJBOUZCODY4NkNBMkM2NUEzAA==', + // 'emoji': '👍', + // }, + // }, { - 'id': '65b06828619a1d82777eb4c6', - 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBQzNDNzBFNjFCREJBNDIyQjQ2AA==', - 'wabaId': '190290134156880', - 'from': '+8613317835586', - 'customerProfile': { - 'name': 'qqs', - }, - 'to': '+8617607730395', - 'sendTime': '2024-01-24T01:30:14.000Z', - 'type': 'image', - 'image': { - 'link': - 'https://api.ycloud.com/v2/whatsapp/media/download/934379820978291?sig=t%3D1706059814%2Cs%3D91a79a0e4007ad2f6a044a28307affe663f7f81903b3537bd80e758d3c0d0563&payload=eyJpZCI6IjkzNDM3OTgyMDk3ODI5MSIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNjgyODYxOWExZDgyNzc3ZWI0YzYiLCJtaW1lVHlwZSI6ImltYWdlL2pwZWciLCJzaGEyNTYiOiJPVTJjdkN2eHplMUdMMmQ5NUxyTGVaNmpNb2ZscUZYM1RvcXdTTUNWZkxNPSJ9', - 'id': '934379820978291', - 'sha256': 'OU2cvCvxze1GL2d95LrLeZ6jMoflqFX3ToqwSMCVfLM=', - 'mime_type': 'image/jpeg', - }, - }, - { - 'id': '65b06ce6619a1d8277c97fc0', - 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBMUJBOUZCODY4NkNBMkM2NUEzAA==', - 'wabaId': '190290134156880', - 'from': '+8613317835586', - 'customerProfile': { - 'name': 'qqs', - }, - 'to': '+8617607730395', - 'sendTime': '2024-01-24T01:50:29.000Z', - 'type': 'text', - 'text': { - 'body': 'eeee', - }, - }, - { - 'id': '65b06b2f619a1d8277b5ab06', - 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBRkU0RUZGRUI1OUQzQUFBMEExAA==', - 'wabaId': '190290134156880', - 'from': '+8613317835586', - 'customerProfile': { - 'name': 'qqs', - }, - 'to': '+8617607730395', - 'sendTime': '2024-01-24T01:43:09.000Z', - 'type': 'audio', - 'audio': { - 'link': - 'https://api.ycloud.com/v2/whatsapp/media/download/901696271448320?sig=t%3D1706060589%2Cs%3Dca75dbd57e4867783390c913491263f07c9738d69c141d4ae622c76df9fa033b&payload=eyJpZCI6IjkwMTY5NjI3MTQ0ODMyMCIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNmIyZjYxOWExZDgyNzdiNWFiMDYiLCJtaW1lVHlwZSI6ImF1ZGlvL29nZzsgY29kZWNzPW9wdXMiLCJzaGEyNTYiOiJoZUNSUDdEMjM3bG9ydkZ4eFhSdHZpU1ZsNDR3Rlk4TytaMFhic2k5cy9rPSJ9', - 'id': '901696271448320', - 'sha256': 'heCRP7D237lorvFxxXRtviSVl44wFY8O+Z0Xbsi9s/k=', - 'mime_type': 'audio/ogg; codecs=opus', - }, - }, - { - 'id': '65b06b12619a1d8277b3c0c4', - 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBREZEMEM0MURDNjJGREVEQjY3AA==', - 'wabaId': '190290134156880', - 'from': '+8613317835586', - 'customerProfile': { - 'name': 'qqs', - }, - 'to': '+8617607730395', - 'sendTime': '2024-01-24T01:42:40.000Z', - 'type': 'video', - 'video': { - 'link': - 'https://api.ycloud.com/v2/whatsapp/media/download/742404324517058?sig=t%3D1706060560%2Cs%3D53eeb1508c2103e310fb14a72563a8e07c5a84c7e6192a25f3608ac9bea32334&payload=eyJpZCI6Ijc0MjQwNDMyNDUxNzA1OCIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNmIxMjYxOWExZDgyNzdiM2MwYzQiLCJtaW1lVHlwZSI6InZpZGVvL21wNCIsInNoYTI1NiI6IlNJcjRlZFlPb1BDTGtETEgrVTY2d3dkMDgra2JndFV5OHRDd2RjQU5FaFU9In0', - 'caption': 'and', - 'id': '742404324517058', - 'sha256': 'SIr4edYOoPCLkDLH+U66wwd08+kbgtUy8tCwdcANEhU=', - 'mime_type': 'video/mp4', - }, - }, - { - 'id': '65b06aa7619a1d8277ac806e', - 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBOTFBOTU5RDE2QjgxQTQ1MEE2AA==', + 'id': '65b1de2f3f0bb66a91377930', + 'wamid': 'wamid.HBgNODYxODc3NzM5Njk1MRUCABEYEkM4NTU5MjMyRDFCRkE5NjM2RAA=', + 'status': 'sent', + 'from': '+8617607730395', + 'to': '+8618777396951', 'wabaId': '190290134156880', - 'from': '+8613317835586', - 'customerProfile': { - 'name': 'qqs', + 'type': 'template', + 'template': { + 'name': 'hello', + 'language': { + 'code': 'zh_CN', + }, }, - 'to': '+8617607730395', - 'sendTime': '2024-01-24T01:40:53.000Z', - 'type': 'sticker', - 'sticker': { - 'link': - 'https://api.ycloud.com/v2/whatsapp/media/download/1156118002042289?sig=t%3D1706060453%2Cs%3Dfbd5f881856614e35715b1e3e1097b3bbe56f8a36aaa67bfbef25a37d9143d51&payload=eyJpZCI6IjExNTYxMTgwMDIwNDIyODkiLCJ3YWJhSWQiOiIxOTAyOTAxMzQxNTY4ODAiLCJpbmJvdW5kTWVzc2FnZUlkIjoiNjViMDZhYTc2MTlhMWQ4Mjc3YWM4MDZlIiwibWltZVR5cGUiOiJpbWFnZS93ZWJwIiwic2hhMjU2IjoibUNaLzdhNnNaNlRNYTE0WW9rUkNTZnVsdGpZNmFRRVZFNVoxMVRwanNQOD0ifQ', - 'id': '1156118002042289', - 'sha256': 'mCZ/7a6sZ6TMa14YokRCSfultjY6aQEVE5Z11TpjsP8=', - 'mime_type': 'image/webp', - 'animated': false, + 'conversation': { + 'id': 'ddb659076d0e970f16fb19520c116a1b', + 'originType': 'marketing', + 'expireTime': '2024-01-26T04:07:00.000Z', }, + 'createTime': '2024-01-25T04:06:07.850Z', + 'updateTime': '2024-01-25T04:06:09.151Z', + 'sendTime': '2024-01-25T04:06:08.000Z', + 'totalPrice': 0.0782, + 'currency': 'USD', + 'bizType': 'whatsapp', }, +]; + +const templateExample = [ { - 'id': '65b06a91619a1d8277aaf05e', - 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBRjUxNzdCQ0FEOTlFQzc5MzQ1AA==', - 'wabaId': '190290134156880', - 'from': '+8613317835586', - 'customerProfile': { - 'name': 'qqs', + 'id': 'evt_eEMtA0PkkyACiS5o', + 'type': 'whatsapp.template.reviewed', + 'apiVersion': 'v2', + 'createTime': '2023-02-20T12:00:00.000Z', + 'whatsappTemplate': { + 'wabaId': 'WABA-ID', + 'name': 'template_name', + 'language': 'en', + 'category': 'MARKETING', + 'status': 'APPROVED', + 'reason': 'NONE', + 'statusUpdateEvent': 'APPROVED', }, - 'to': '+8617607730395', - 'sendTime': '2024-01-24T01:40:32.000Z', - 'type': 'unsupported', - 'errors': [ - { - 'code': '131051', - 'title': 'Message type unknown', - 'message': 'Message type unknown', - 'error_data': { - 'details': 'Message type is currently not supported.', - }, - }, - ], }, ]; const AllTemplates = [ { - "wabaId": "190290134156880", - "name": "say_hi", - "language": "en", - "components": [ + 'wabaId': '190290134156880', + 'name': 'say_hi', + 'language': 'en', + 'components': [ { - "type": "BODY", - "text": "Hi {{customer_name}} I'm {{your_name}} from Asia Highlights.\n\nWe provide *families* and *couples* with personalized and stress-free experiences, whether for _milestone trips_, _birthday trips_, _graduation trips_, or _bucketlist trips_.", - "example": { - "body_text": [ - [ - "Mike", - "Jimmy" - ] - ] - } + 'type': 'BODY', + 'text': + "Hi {{customer_name}} I'm {{your_name}} from Asia Highlights.\n\nWe provide *families* and *couples* with personalized and stress-free experiences, whether for _milestone trips_, _birthday trips_, _graduation trips_, or _bucketlist trips_.", + 'example': { + 'body_text': [['Mike', 'Jimmy']], + }, }, { - "type": "BUTTONS", - "buttons": [ + 'type': 'BUTTONS', + 'buttons': [ { - "type": "URL", - "text": "Asia Highlights", - "url": "https://www.asiahighlights.com/" - } - ] - } + 'type': 'URL', + 'text': 'Asia Highlights', + 'url': 'https://www.asiahighlights.com/', + }, + ], + }, ], - "category": "UTILITY", - "status": "REJECTED", - "qualityRating": "UNKNOWN", - "reason": "INCORRECT_CATEGORY", - "createTime": "2024-01-23T02:26:33.012Z", - "updateTime": "2024-01-23T02:26:35.397Z", - "statusUpdateEvent": "REJECTED" + 'category': 'UTILITY', + 'status': 'REJECTED', + 'qualityRating': 'UNKNOWN', + 'reason': 'INCORRECT_CATEGORY', + 'createTime': '2024-01-23T02:26:33.012Z', + 'updateTime': '2024-01-23T02:26:35.397Z', + 'statusUpdateEvent': 'REJECTED', }, { - "wabaId": "190290134156880", - "name": "i_hope_this_message_finds_you_well", - "language": "en", - "components": [ + 'wabaId': '190290134156880', + 'name': 'i_hope_this_message_finds_you_well', + 'language': 'en', + 'components': [ { - "type": "BODY", - "text": "Hi {{customer_name}}, I hope this message finds you well. Did you see my previous message?", - "example": { - "body_text": [ - [ - "Mike" - ] - ] - } - } + 'type': 'BODY', + 'text': 'Hi {{customer_name}}, I hope this message finds you well. Did you see my previous message?', + 'example': { + 'body_text': [['Mike']], + }, + }, ], - "category": "UTILITY", - "status": "REJECTED", - "qualityRating": "UNKNOWN", - "reason": "INCORRECT_CATEGORY", - "createTime": "2024-01-23T02:22:20.232Z", - "updateTime": "2024-01-23T02:22:22.937Z", - "statusUpdateEvent": "REJECTED" + 'category': 'UTILITY', + 'status': 'REJECTED', + 'qualityRating': 'UNKNOWN', + 'reason': 'INCORRECT_CATEGORY', + 'createTime': '2024-01-23T02:22:20.232Z', + 'updateTime': '2024-01-23T02:22:22.937Z', + 'statusUpdateEvent': 'REJECTED', }, { - "wabaId": "190290134156880", - "name": "asia_highlights_has_receive_your_inquiry", - "language": "en", - "components": [ + 'wabaId': '190290134156880', + 'name': 'asia_highlights_has_receive_your_inquiry', + 'language': 'en', + 'components': [ { - "type": "BODY", - "text": "Dear {{customer_name}},\n\nThank you for choosing Asia Highlights. Your inquiry has been submitted to Asia Highlights. One of our travel advisors will respond within 24 hours.", - "example": { - "body_text": [ - [ - "Jimmy Liow" - ] - ] - } + 'type': 'BODY', + 'text': + 'Dear {{customer_name}},\n\nThank you for choosing Asia Highlights. Your inquiry has been submitted to Asia Highlights. One of our travel advisors will respond within 24 hours.', + 'example': { + 'body_text': [['Jimmy Liow']], + }, }, { - "type": "HEADER", - "format": "TEXT", - "text": "Asia highlights has receive your inquiry" + 'type': 'HEADER', + 'format': 'TEXT', + 'text': 'Asia highlights has receive your inquiry', }, { - "type": "FOOTER", - "text": "Kind regards, Asia Highlights Team" + 'type': 'FOOTER', + 'text': 'Kind regards, Asia Highlights Team', }, { - "type": "BUTTONS", - "buttons": [ + 'type': 'BUTTONS', + 'buttons': [ { - "type": "URL", - "text": "Asia Highlights", - "url": "https://www.asiahighlights.com/" - } - ] - } + 'type': 'URL', + 'text': 'Asia Highlights', + 'url': 'https://www.asiahighlights.com/', + }, + ], + }, ], - "category": "UTILITY", - "status": "APPROVED", - "qualityRating": "UNKNOWN", - "reason": "NONE", - "createTime": "2024-01-19T05:59:32.933Z", - "updateTime": "2024-01-19T05:59:55.581Z", - "statusUpdateEvent": "APPROVED" + 'category': 'UTILITY', + 'status': 'APPROVED', + 'qualityRating': 'UNKNOWN', + 'reason': 'NONE', + 'createTime': '2024-01-19T05:59:32.933Z', + 'updateTime': '2024-01-19T05:59:55.581Z', + 'statusUpdateEvent': 'APPROVED', }, { - "wabaId": "190290134156880", - "name": "hello", - "language": "zh_CN", - "components": [ + 'wabaId': '190290134156880', + 'name': 'hello', + 'language': 'zh_CN', + 'components': [ { - "type": "BODY", - "text": "你好,这是一个测试程序" + 'type': 'BODY', + 'text': '你好,这是一个测试程序', }, { - "type": "HEADER", - "format": "TEXT", - "text": "Hello 同学" + 'type': 'HEADER', + 'format': 'TEXT', + 'text': 'Hello 同学', }, { - "type": "FOOTER", - "text": "Global Highlights" + 'type': 'FOOTER', + 'text': 'Global Highlights', }, { - "type": "BUTTONS", - "buttons": [ + 'type': 'BUTTONS', + 'buttons': [ { - "type": "URL", - "text": "about us", - "url": "https://www.globalhighlights.com/" - } - ] - } + 'type': 'URL', + 'text': 'about us', + 'url': 'https://www.globalhighlights.com/', + }, + ], + }, ], - "category": "MARKETING", - "status": "APPROVED", - "qualityRating": "UNKNOWN", - "reason": "NONE", - "createTime": "2023-11-17T03:26:10.961Z", - "updateTime": "2023-11-17T13:36:33.623Z", - "statusUpdateEvent": "APPROVED" - } + 'category': 'MARKETING', + 'status': 'APPROVED', + 'qualityRating': 'UNKNOWN', + 'reason': 'NONE', + 'createTime': '2023-11-17T03:26:10.961Z', + 'updateTime': '2023-11-17T13:36:33.623Z', + 'statusUpdateEvent': 'APPROVED', + }, ]; diff --git a/src/views/Conversations/Components/InputBox.jsx b/src/views/Conversations/Components/InputBox.jsx index cd9d65a..9ff9800 100644 --- a/src/views/Conversations/Components/InputBox.jsx +++ b/src/views/Conversations/Components/InputBox.jsx @@ -4,11 +4,12 @@ import { Input, Button, Tabs, List, Space, Popover, Flex } from 'antd'; // import { Input } from 'react-chat-elements'; import { useConversationContext } from '@/stores/ConversationContext'; import { LikeOutlined, MessageOutlined, StarOutlined, SendOutlined, PlusOutlined, PlusCircleOutlined } from '@ant-design/icons'; +import { isEmpty } from '@/utils/utils'; const InputBox = observer(({ onSend }) => { - const { templates } = useConversationContext(); + const { currentConversation, templates } = useConversationContext(); const [textContent, setTextContent] = useState(''); - console.log( 'ttt'); + console.log('ttt'); const handleSendText = () => { // console.log(textContent); @@ -17,39 +18,82 @@ const InputBox = observer(({ onSend }) => { type: 'text', text: textContent, sender: 'me', - from: '', + // from: '', + to: currentConversation.channel_id, id: Date.now().toString(16), date: new Date(), - readState: false, + status: 'waiting', + // renderAddCmp: () => ( + // + // ), + // statusTitle: 'Ready to send', + // removeButton: true, + // replyButtom: true, + // focus: true, }; onSend(msgObj); setTextContent(''); } }; - const handleSendTemplate = () => { }; + const handleSendTemplate = (fromTemplate) => { + console.log(fromTemplate, 'fromTemplate'); + if (typeof onSend === 'function') { + const msgObj = { + type: 'whatsappTemplate', + to: currentConversation.channel_id, + id: Date.now().toString(16), + date: new Date(), + status: 'waiting', + statusTitle: 'Ready to send', + sender: 'me', + template: { + name: fromTemplate.name, + language: { code: fromTemplate.language }, + ...(fromTemplate.components.body[0]?.example?.body_text?.[0]?.length > 0 + ? { components: [ + { + 'type': 'body', + 'parameters': [ + { + 'type': 'text', + 'text': currentConversation.name, + }, + { + 'type': 'text', + 'text': currentConversation.name, + }, + ], + }, + ], } + : {}), + }, + template_origin: fromTemplate, + }; + onSend(msgObj); + setOpenTemplates(false); + } + }; const [openTemplates, setOpenTemplates] = useState(false); const handleOpenChange = (newOpen) => { setOpenTemplates(newOpen); }; - const onSendTemplates = () => { - setOpenTemplates(false); - // todo: send - } return (
- ( }> + , ]}> diff --git a/src/views/Conversations/Components/Messages.jsx b/src/views/Conversations/Components/Messages.jsx index cde2dad..26e6131 100644 --- a/src/views/Conversations/Components/Messages.jsx +++ b/src/views/Conversations/Components/Messages.jsx @@ -81,7 +81,7 @@ const Messages = observer(() => { const messagesEndRef = useRef(null); useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messagesList.length]); const [previewVisible, setPreviewVisible] = useState(false); @@ -102,11 +102,12 @@ const Messages = observer(() => {
{messagesList.map((message, index) => ( handlePreview(message)} - status={message.sender === 'me' ? 'sent' : ''} + letterItem={{ id: 1, letter: 'AS' }} // read | 'waiting'| 'sent' | 'received' | 'read' /> ))} diff --git a/src/views/Conversations/Conversations.css b/src/views/Conversations/Conversations.css index 1def9dc..9415dca 100644 --- a/src/views/Conversations/Conversations.css +++ b/src/views/Conversations/Conversations.css @@ -19,6 +19,17 @@ padding: .5em; } +/** Chat Window */ +.chatwindow-wrapper .rce-container-mbox.whatsappme-container .rce-mbox{ + background-color: darkseagreen; + box-shadow: 0px 1px 1px 1px darkseagreen; +} +.chatwindow-wrapper .rce-container-mbox .rce-mbox{ + max-width: 400px; +} +.chatwindow-wrapper .rce-container-mbox.whatsappme-container .rce-mbox-right-notch{ + fill: darkseagreen; +} .chatwindow-wrapper .rce-mbox-time-block{ background: linear-gradient(0deg,#00000014,#0000); color: #00000073; diff --git a/tailwind.config.js b/tailwind.config.js index 7a4ab33..2068eba 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -4,6 +4,10 @@ export default { purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], darkMode: false, theme: { + colors: { + 'whatsapp': '#25D366', + 'whatsappme': '#1ba784', + }, extend: { // gridTemplateColumns: { // 'responsive':repeat(autofill,minmax('300px',1fr))