From 52f76e5129e81350f859045039f57ff8f0e12c2c Mon Sep 17 00:00:00 2001 From: Ryan Kennedy Date: Wed, 10 Jul 2024 20:30:21 -0700 Subject: [PATCH] FLXAGNTUI-202: Participant name map --- src/components/MessageBubble.tsx | 27 ++++++++++--------- src/sessionDataHandler.ts | 1 + src/store/actions/actionTypes.ts | 1 + src/store/actions/initActions.ts | 12 ++++++++- .../actions/listeners/participantsListener.ts | 15 ++++++++++- src/store/chat.reducer.ts | 15 +++++++++-- src/store/definitions.ts | 1 + src/utils/participantNameMap.ts | 13 +++++++++ 8 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 src/utils/participantNameMap.ts diff --git a/src/components/MessageBubble.tsx b/src/components/MessageBubble.tsx index 5ffcb812..7db12a9c 100644 --- a/src/components/MessageBubble.tsx +++ b/src/components/MessageBubble.tsx @@ -39,12 +39,14 @@ export const MessageBubble = ({ }) => { const [read, setRead] = useState(false); const [isMouseDown, setIsMouseDown] = useState(false); - const { conversationsClient, participants, users, fileAttachmentConfig } = useSelector((state: AppState) => ({ - conversationsClient: state.chat.conversationsClient, - participants: state.chat.participants, - users: state.chat.users, - fileAttachmentConfig: state.config.fileAttachment - })); + const { conversationsClient, participants, fileAttachmentConfig, participantNames } = useSelector( + (state: AppState) => ({ + conversationsClient: state.chat.conversationsClient, + participants: state.chat.participants, + fileAttachmentConfig: state.config.fileAttachment, + participantNames: state.chat.participantNames + }) + ); const messageRef = useRef(null); const belongsToCurrentUser = message.author === conversationsClient?.user.identity; @@ -54,7 +56,7 @@ export const MessageBubble = ({ const getOtherParticipants = participants.filter((p) => p.identity !== conversationsClient?.user.identity); setRead( Boolean(getOtherParticipants.length) && - getOtherParticipants.every((p) => p.lastReadMessageIndex === message.index) + getOtherParticipants.every((p) => p.lastReadMessageIndex === message.index) ); } else { setRead(false); @@ -109,7 +111,8 @@ export const MessageBubble = ({ } }; - const author = users?.find((u) => u.identity === message.author)?.friendlyName || message.author; + // const author = users?.find((u) => u.identity === message.author)?.friendlyName || message.author; + const name = participantNames ? participantNames[message.participantSid] : ""; return ( - - {author} + + {name} - {belongsToCurrentUser - ? "You sent at" - : `${users?.find((u) => u.identity === message.author)?.friendlyName} sent at`} + {belongsToCurrentUser ? "You sent at" : `${name} sent at`} {`${doubleDigit(message.dateCreated.getHours())}:${doubleDigit( diff --git a/src/sessionDataHandler.ts b/src/sessionDataHandler.ts index 4dcd3bc0..4446610f 100644 --- a/src/sessionDataHandler.ts +++ b/src/sessionDataHandler.ts @@ -8,6 +8,7 @@ const CUSTOMER_DEFAULT_NAME = "Customer"; type SessionDataStorage = ProcessedTokenResponse & { loginTimestamp: string | null; + participantNameMap?: Record; }; type InitWebchatAPIPayload = { diff --git a/src/store/actions/actionTypes.ts b/src/store/actions/actionTypes.ts index 795d20d5..7b149dbc 100644 --- a/src/store/actions/actionTypes.ts +++ b/src/store/actions/actionTypes.ts @@ -17,6 +17,7 @@ export const ACTION_DETACH_FILES = "ACTION_DETACH_FILES"; export const ACTION_UPDATE_CONVERSATION_STATE = "ACTION_UPDATE_CONVERSATION_STATE"; export const ACTION_UPDATE_SESSION_DATA = "ACTION_UPDATE_SESSION_DATA"; export const ACTION_UPDATE_PRE_ENGAGEMENT_DATA = "ACTION_UPDATE_PRE_ENGAGEMENT_DATA"; +export const ACTION_UPDATE_PARTICIPANT_NAME = "ACTION_UPDATE_PARTICIPANT_NAME"; // Notification reducer actions export const ACTION_ADD_NOTIFICATION = "ACTION_ADD_NOTIFICATION"; diff --git a/src/store/actions/initActions.ts b/src/store/actions/initActions.ts index 102def37..45868e6f 100644 --- a/src/store/actions/initActions.ts +++ b/src/store/actions/initActions.ts @@ -12,6 +12,7 @@ import { addNotification, changeEngagementPhase } from "./genericActions"; import { MESSAGES_LOAD_COUNT } from "../../constants"; import { parseRegionForConversations } from "../../utils/regionUtil"; import { sessionDataHandler } from "../../sessionDataHandler"; +import { createParticipantNameMap } from "../../utils/participantNameMap"; export function initConfig(config: ConfigState) { return { @@ -33,6 +34,7 @@ export function initSession({ token, conversationSid }: InitSessionPayload) { let participants; let users; let messages; + let participantNameMap; try { conversationsClient = await Client.create(token, { @@ -49,6 +51,13 @@ export function initSession({ token, conversationSid }: InitSessionPayload) { participants = await conversation.getParticipants(); users = await Promise.all(participants.map(async (p) => p.getUser())); messages = (await conversation.getMessages(MESSAGES_LOAD_COUNT)).items; + + /* + * TODO: If we have an existing participantNameMap for this conversationSid, + * in localStorage, use it and update it with the new participants. + */ + participantNameMap = createParticipantNameMap(participants, users); + } catch (e) { logger.error("Something went wrong when initializing session", e); throw e; @@ -65,7 +74,8 @@ export function initSession({ token, conversationSid }: InitSessionPayload) { participants, messages, conversationState: conversation.state?.current, - currentPhase: EngagementPhase.MessagingCanvas + currentPhase: EngagementPhase.MessagingCanvas, + participantNames: participantNameMap } }); diff --git a/src/store/actions/listeners/participantsListener.ts b/src/store/actions/listeners/participantsListener.ts index f8ec4b24..bdfe4c87 100644 --- a/src/store/actions/listeners/participantsListener.ts +++ b/src/store/actions/listeners/participantsListener.ts @@ -1,7 +1,12 @@ import { Conversation, Participant } from "@twilio/conversations"; import { Dispatch } from "redux"; -import { ACTION_ADD_PARTICIPANT, ACTION_REMOVE_PARTICIPANT, ACTION_UPDATE_PARTICIPANT } from "../actionTypes"; +import { + ACTION_ADD_PARTICIPANT, + ACTION_REMOVE_PARTICIPANT, + ACTION_UPDATE_PARTICIPANT, + ACTION_UPDATE_PARTICIPANT_NAME +} from "../actionTypes"; export const initParticipantsListener = (conversation: Conversation, dispatch: Dispatch) => { conversation.addListener("participantJoined", async (participant: Participant) => { @@ -10,6 +15,14 @@ export const initParticipantsListener = (conversation: Conversation, dispatch: D type: ACTION_ADD_PARTICIPANT, payload: { participant, user } }); + + // set the name to empty string if we do not have a user + const name = user ? user.friendlyName : ""; + + dispatch({ + type: ACTION_UPDATE_PARTICIPANT_NAME, + payload: { participantSid: participant.sid, name } + }); }); conversation.addListener("participantLeft", (participant: Participant) => { diff --git a/src/store/chat.reducer.ts b/src/store/chat.reducer.ts index f9cef28e..feb7dbf6 100644 --- a/src/store/chat.reducer.ts +++ b/src/store/chat.reducer.ts @@ -13,7 +13,8 @@ import { ACTION_START_SESSION, ACTION_UPDATE_CONVERSATION_STATE, ACTION_UPDATE_MESSAGE, - ACTION_UPDATE_PARTICIPANT + ACTION_UPDATE_PARTICIPANT, + ACTION_UPDATE_PARTICIPANT_NAME } from "./actions/actionTypes"; const initialState: ChatState = {}; @@ -40,7 +41,8 @@ export const ChatReducer: Reducer = (state: ChatState = initialState, action: An conversationState: action.payload.conversationState, users: action.payload.users, participants: action.payload.participants, - messages: action.payload.messages + messages: action.payload.messages, + participantNames: action.payload.participantNames }; } case ACTION_ADD_MULTIPLE_MESSAGES: { @@ -124,6 +126,15 @@ export const ChatReducer: Reducer = (state: ChatState = initialState, action: An conversationState: action.payload.conversationState }; } + case ACTION_UPDATE_PARTICIPANT_NAME: { + return { + ...state, + participantNames: { + ...state.participantNames, + [action.payload.participantSid]: action.payload.name + } + }; + } default: return state; diff --git a/src/store/definitions.ts b/src/store/definitions.ts index a825909c..2098b6ea 100644 --- a/src/store/definitions.ts +++ b/src/store/definitions.ts @@ -18,6 +18,7 @@ export type ChatState = { messages?: Message[]; attachedFiles?: File[]; conversationState?: string; + participantNames?: { [key: string]: string }; }; export type PreEngagementData = { name: string; email: string; query: string }; diff --git a/src/utils/participantNameMap.ts b/src/utils/participantNameMap.ts new file mode 100644 index 00000000..420bd8b8 --- /dev/null +++ b/src/utils/participantNameMap.ts @@ -0,0 +1,13 @@ +/* eslint-disable import/no-unused-modules */ +import { Participant, User } from "@twilio/conversations"; + +// return a map of participant sid to participant name +export const createParticipantNameMap = (participants: Participant[], users: User[]) => { + return participants.reduce((acc: { [key: string]: string }, p) => { + const user = users.find((u) => u.identity === p.identity); + if (user) { + acc[p.sid] = user.friendlyName; + } + return acc; + }, {}); +};