From 8504bf6ebaf3b1840b3bcbc2d11947768724da29 Mon Sep 17 00:00:00 2001 From: Irene Ryu Date: Wed, 27 Mar 2024 17:56:12 +0900 Subject: [PATCH] feat: add message feedback --- package-lock.json | 38 ++--- package.json | 4 +- src/components/BotMessageFeedback.tsx | 163 +++++++++++++++++++++ src/components/BotMessageWithBodyInput.tsx | 4 +- src/components/Chat.tsx | 2 + src/const.ts | 2 +- src/context/ConstantContext.tsx | 6 +- src/css/index.css | 4 + 8 files changed, 198 insertions(+), 25 deletions(-) create mode 100644 src/components/BotMessageFeedback.tsx diff --git a/package-lock.json b/package-lock.json index 58c59d106..2fa807384 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@sendbird/chat-ai-widget", - "version": "1.3.4", + "version": "1.3.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@sendbird/chat-ai-widget", - "version": "1.3.4", + "version": "1.3.7", "dependencies": { - "@sendbird/chat": "^4.10.1", - "@sendbird/uikit-react": "^3.13.1", + "@sendbird/chat": "^4.11.0", + "@sendbird/uikit-react": "^3.13.4", "@tanstack/react-query": "^5.17.19", "dompurify": "^3.0.4", "react-popper-tooltip": "^4.4.2", @@ -830,11 +830,11 @@ } }, "node_modules/@sendbird/react-uikit-message-template-view": { - "version": "0.0.1-alpha.65", - "resolved": "https://registry.npmjs.org/@sendbird/react-uikit-message-template-view/-/react-uikit-message-template-view-0.0.1-alpha.65.tgz", - "integrity": "sha512-WCGNYPkahAKwP633p0+lDUKqKB8s2aulVqIEc4UDp7DBNfeLor8VY/vsuQKxWDCtbQHiJZdTT2fPQrkZfpLxxg==", + "version": "0.0.1-alpha.68", + "resolved": "https://registry.npmjs.org/@sendbird/react-uikit-message-template-view/-/react-uikit-message-template-view-0.0.1-alpha.68.tgz", + "integrity": "sha512-nS/3i984jayt5KquA8//IwzxKVgmpSSdIou5eRD8Z2R2ULxcR34qrlRVYAef1vGC4JY0Gee0fKdnFaz140XQmg==", "dependencies": { - "@sendbird/uikit-message-template": "^0.0.1-alpha.65" + "@sendbird/uikit-message-template": "^0.0.1-alpha.68" }, "peerDependencies": { "@sendbird/chat": ">=4.3.0 <5", @@ -843,21 +843,21 @@ } }, "node_modules/@sendbird/uikit-message-template": { - "version": "0.0.1-alpha.65", - "resolved": "https://registry.npmjs.org/@sendbird/uikit-message-template/-/uikit-message-template-0.0.1-alpha.65.tgz", - "integrity": "sha512-d2vRpEr2dEdO9M82zwW1vG8Dnnvi7NPfh2YPYFy2gHUNyGjTANb8jIKf+blh/qCmcXFGKbdcjrz4FxZNsXRdhA==", + "version": "0.0.1-alpha.69", + "resolved": "https://registry.npmjs.org/@sendbird/uikit-message-template/-/uikit-message-template-0.0.1-alpha.69.tgz", + "integrity": "sha512-AhO3EtTFa148XDOnfU1AbmjMzNViMOh9XLYn/IYntMJJaQfME+a44ymTkStoIQCOIFfdlTrtvpBGsxYq7np64g==", "peerDependencies": { "react": ">=16.8.6" } }, "node_modules/@sendbird/uikit-react": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@sendbird/uikit-react/-/uikit-react-3.13.1.tgz", - "integrity": "sha512-e8ml2HE8cdCntZUIJW9clfRgTQr/fZkl8FhLxIK6keuIT7UymEg+8vLOjFFVxHNgNbaXuUzEofCg2gircdNwkw==", + "version": "3.13.4", + "resolved": "https://registry.npmjs.org/@sendbird/uikit-react/-/uikit-react-3.13.4.tgz", + "integrity": "sha512-C/gLugGFPuZ4c1ISIExfVL8gYetLOOC10fKd7A5GEjvWOJ+PzTd4gtTXzfm20wECPqXMC1/XJyAKBCMaOaljJw==", "dependencies": { "@sendbird/chat": "^4.11.0", - "@sendbird/react-uikit-message-template-view": "0.0.1-alpha.65", - "@sendbird/uikit-tools": "0.0.1-alpha.65", + "@sendbird/react-uikit-message-template-view": "0.0.1-alpha.68", + "@sendbird/uikit-tools": "0.0.1-alpha.68", "css-vars-ponyfill": "^2.3.2", "date-fns": "^2.16.1", "dompurify": "^3.0.1" @@ -868,9 +868,9 @@ } }, "node_modules/@sendbird/uikit-tools": { - "version": "0.0.1-alpha.65", - "resolved": "https://registry.npmjs.org/@sendbird/uikit-tools/-/uikit-tools-0.0.1-alpha.65.tgz", - "integrity": "sha512-qUIN4pMtEP0F2n18ulmjNKXMhTbO+Q63y+o/77PPp03MpinPcRlMDHNwe9ido4el0fIkHextKta8bE8X7BLD3g==", + "version": "0.0.1-alpha.68", + "resolved": "https://registry.npmjs.org/@sendbird/uikit-tools/-/uikit-tools-0.0.1-alpha.68.tgz", + "integrity": "sha512-ANZgc8rEbFjkKoNxfVmmFJt7FjWEWKOIZ4q7XoAjcqa7/MgMrax7aSuXxbFejveQqGVBZi6XFS/lumjnIwiKTA==", "peerDependencies": { "@sendbird/chat": ">=4.10.5 <5", "react": ">=16.8.6" diff --git a/package.json b/package.json index 86b189582..57f0ecde4 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,8 @@ "preview": "vite preview" }, "dependencies": { - "@sendbird/chat": "^4.10.1", - "@sendbird/uikit-react": "^3.13.1", + "@sendbird/chat": "^4.11.0", + "@sendbird/uikit-react": "^3.13.4", "@tanstack/react-query": "^5.17.19", "dompurify": "^3.0.4", "react-popper-tooltip": "^4.4.2", diff --git a/src/components/BotMessageFeedback.tsx b/src/components/BotMessageFeedback.tsx new file mode 100644 index 000000000..2ffb52045 --- /dev/null +++ b/src/components/BotMessageFeedback.tsx @@ -0,0 +1,163 @@ +import { FeedbackRating, UserMessage, Feedback } from '@sendbird/chat/message'; +import FeedbackIconButton from '@sendbird/uikit-react/ui/FeedbackIconButton'; +import Icon, { IconTypes } from '@sendbird/uikit-react/ui/Icon'; +import MessageFeedbackFailedModal from '@sendbird/uikit-react/ui/MessageFeedbackFailedModal'; +import MessageFeedbackModal from '@sendbird/uikit-react/ui/MessageFeedbackModal'; +import MobileFeedbackMenu from '@sendbird/uikit-react/ui/MobileFeedbackMenu'; +import { useState } from 'react'; + +import { useConstantState } from '../context/ConstantContext'; +import { isMobile } from '../utils'; + +function BotMessageFeedback({ message }: { message: UserMessage }) { + const { stringSet } = useConstantState(); + const [showFeedbackOptionsMenu, setShowFeedbackOptionsMenu] = + useState(false); + const [showFeedbackModal, setShowFeedbackModal] = useState(false); + const [feedbackFailedText, setFeedbackFailedText] = useState(''); + + const openFeedbackFormOrMenu = () => { + if (isMobile) { + setShowFeedbackOptionsMenu(true); + } else { + setShowFeedbackModal(true); + } + }; + + const onCloseFeedbackForm = () => { + setShowFeedbackModal(false); + }; + + return ( + <> + {/** Feedback icons */} +
+ { + if (!message?.myFeedback?.rating) { + try { + await message.submitFeedback({ + rating: FeedbackRating.GOOD, + }); + openFeedbackFormOrMenu(); + } catch (error) { + console.error?.('Channel: Submit feedback failed.', error); + setFeedbackFailedText(stringSet.FEEDBACK_FAILED_SUBMIT); + } + } else { + openFeedbackFormOrMenu(); + } + }} + disabled={ + message?.myFeedback != null && + message.myFeedback.rating !== FeedbackRating.GOOD + } + > + + + { + if (!message?.myFeedback?.rating) { + try { + await message.submitFeedback({ + rating: FeedbackRating.BAD, + }); + openFeedbackFormOrMenu(); + } catch (error) { + console.error?.('Channel: Submit feedback failed.', error); + setFeedbackFailedText(stringSet.FEEDBACK_FAILED_SUBMIT); + } + } else { + openFeedbackFormOrMenu(); + } + }} + disabled={ + message?.myFeedback != null && + message.myFeedback.rating !== FeedbackRating.BAD + } + > + + +
+ { + // Feedback menu + message.myFeedback?.rating && showFeedbackOptionsMenu && ( + { + setShowFeedbackOptionsMenu(false); + }} + onEditFeedback={() => { + setShowFeedbackOptionsMenu(false); + setShowFeedbackModal(true); + }} + onRemoveFeedback={async () => { + try { + if (message.myFeedback?.id == null) { + throw new Error('Invalid feedback id'); + } + await message.deleteFeedback(message.myFeedback.id); + } catch (error) { + console.error?.('Channel: Delete feedback failed.', error); + setFeedbackFailedText(stringSet.FEEDBACK_FAILED_DELETE); + } + setShowFeedbackOptionsMenu(false); + }} + /> + ) + } + { + // Feedback modal + message.myFeedback != null && + message?.myFeedback?.rating && + showFeedbackModal && ( + { + const newFeedback: Feedback = new Feedback({ + id: message.myFeedback.id, + rating: selectedFeedback, + comment, + }); + try { + await message.updateFeedback(newFeedback); + } catch (error) { + console.error('Channel: Update feedback failed.', error); + setFeedbackFailedText(stringSet.FEEDBACK_FAILED_SAVE); + } + onCloseFeedbackForm(); + }} + onClose={onCloseFeedbackForm} + onRemove={async () => { + try { + await message.deleteFeedback(message.myFeedback.id); + } catch (error) { + console.error('Channel: Delete feedback failed.', error); + setFeedbackFailedText(stringSet.FEEDBACK_FAILED_DELETE); + } + onCloseFeedbackForm(); + }} + /> + ) + } + { + // Feedback failed modal + feedbackFailedText && ( + { + setFeedbackFailedText(''); + }} + /> + ) + } + + ); +} + +export default BotMessageFeedback; diff --git a/src/components/BotMessageWithBodyInput.tsx b/src/components/BotMessageWithBodyInput.tsx index 3e8f85249..644de3344 100644 --- a/src/components/BotMessageWithBodyInput.tsx +++ b/src/components/BotMessageWithBodyInput.tsx @@ -8,9 +8,9 @@ import Label, { import { ReactNode } from 'react'; import styled from 'styled-components'; +import BotMessageFeedback from './BotMessageFeedback'; import BotProfileImage from './BotProfileImage'; import { SentTime, BodyContainer } from './MessageComponent'; -import { ReactionContainer } from './ReactionContainer'; import { useConstantState } from '../context/ConstantContext'; import { formatCreatedAtToAMPM } from '../utils'; import { isLastMessageInStreaming } from '../utils/messages'; @@ -111,7 +111,7 @@ export default function BotMessageWithBodyInput(props: Props) { displayProfileImage && !isBotWelcomeMessage && !(isLastBotMessage && isLastMessageInStreaming(message)) && - !isFormMessage && } + !isFormMessage && } {formatCreatedAtToAMPM(message.createdAt)} diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index fe5193210..e64cf3ae4 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -27,6 +27,7 @@ const SBComponent = () => { userNickName, configureSession, enableMention, + enableEmojiFeedback, customUserAgentParam, stringSet, } = useConstantState(); @@ -89,6 +90,7 @@ const SBComponent = () => { enableDocument: false, }, enableVoiceMessage: false, + enableFeedback: enableEmojiFeedback, }, }} > diff --git a/src/const.ts b/src/const.ts index 8296b241b..0bb55c7ba 100644 --- a/src/const.ts +++ b/src/const.ts @@ -97,7 +97,7 @@ export interface Constant { enableMention: boolean; enableMobileView: boolean; firstMessageData: FirstMessageItem[]; - stringSet: Partial | undefined; + stringSet: Partial; } export interface SuggestedReply { diff --git a/src/context/ConstantContext.tsx b/src/context/ConstantContext.tsx index dc9e7217a..8d91a4518 100644 --- a/src/context/ConstantContext.tsx +++ b/src/context/ConstantContext.tsx @@ -1,3 +1,4 @@ +import { LabelStringSet } from '@sendbird/uikit-react/ui/Label'; import { createContext, useContext, useMemo } from 'react'; import { type Constant, DEFAULT_CONSTANT } from '../const'; @@ -67,7 +68,10 @@ export const ConstantStateProvider = (props: ProviderProps) => { }, customUserAgentParam: props.customUserAgentParam, configureSession: props.configureSession, - stringSet: props.stringSet, + stringSet: { + ...LabelStringSet, + ...props.stringSet, + }, enableSourceMessage: props.enableSourceMessage ?? initialState.enableSourceMessage, enableEmojiFeedback: diff --git a/src/css/index.css b/src/css/index.css index ac20d535a..297e85d90 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -52,3 +52,7 @@ .sendbird-message-input .sendbird-message-input-text-field { overflow-y: hidden; } + +.sendbird-modal__content { + width: calc(100% - 20px); +}