From 7b588f2cae664437f7ab549e542d552547d40b35 Mon Sep 17 00:00:00 2001 From: AudreyKj <38159391+AudreyKj@users.noreply.github.com> Date: Thu, 21 Oct 2021 15:07:19 +0200 Subject: [PATCH] [#2514] fixed ui bugs --- .../Inbox/MessageInput/InputSelector.tsx | 41 +++++++++++----- .../ui/src/pages/Inbox/MessageInput/index.tsx | 48 ++++++++++++++----- .../Messenger/MessengerContainer/index.tsx | 10 ++-- lib/typescript/render/attachments.ts | 1 + .../render/components/Image/index.module.scss | 1 + .../components/ImageWithFallback/index.tsx | 30 ++++++++---- lib/typescript/render/index.ts | 1 + .../components/QuickReplies/index.tsx | 9 +++- .../components/RichCard/Media/index.tsx | 2 +- .../components/GenericTemplate/index.tsx | 4 +- .../components/QuickReplies/index.tsx | 4 +- .../components/RichCard/Media/index.tsx | 2 +- .../google/components/Suggestions/index.tsx | 8 ++-- 13 files changed, 113 insertions(+), 48 deletions(-) diff --git a/frontend/ui/src/pages/Inbox/MessageInput/InputSelector.tsx b/frontend/ui/src/pages/Inbox/MessageInput/InputSelector.tsx index 006c1b5a68..c5b769d6e5 100644 --- a/frontend/ui/src/pages/Inbox/MessageInput/InputSelector.tsx +++ b/frontend/ui/src/pages/Inbox/MessageInput/InputSelector.tsx @@ -3,22 +3,24 @@ import styles from './InputSelector.module.scss'; import {ReactComponent as Close} from 'assets/images/icons/close.svg'; import {SourceMessage} from 'render'; import {Source, Message} from 'model'; +import {FileInfo} from './index'; type InputSelectorProps = { messageType: 'template' | 'suggestedReplies' | 'message'; message: Message; source: Source; contentResizedHeight: number; + fileInfo: FileInfo | null; removeElementFromInput: () => void; }; export const InputSelector = (props: InputSelectorProps) => { - const {source, message, messageType, removeElementFromInput, contentResizedHeight} = props; + const {source, message, messageType, removeElementFromInput, contentResizedHeight, fileInfo} = props; const [closeIconWidth, setCloseIconWidth] = useState(''); const [closeIconHeight, setCloseIconHeight] = useState(''); + const [closeButtonSelector, setCloseButtonSelector] = useState(false); - const removeSelectedButton = useRef(null); - const fileSelectorDiv = useRef<HTMLDivElement>(null); + const fileSelectorDiv = useRef(null); const removeFileButton = useRef(null); const scaleInputSelector = () => { @@ -32,15 +34,30 @@ export const InputSelector = (props: InputSelectorProps) => { setCloseIconHeight(iconSize); setCloseIconWidth(iconSize); + setCloseButtonSelector(true); if (removeFileButton && removeFileButton.current) { removeFileButton.current.style.width = buttonSize; removeFileButton.current.style.height = buttonSize; } + } else { + setCloseButtonSelector(true); } fileSelectorDiv.current.style.transform = `scale(${scaleRatio})`; fileSelectorDiv.current.style.transformOrigin = 'left'; + } else { + if (fileInfo && fileInfo?.size >= 1 && fileInfo?.type !== 'audio' && fileInfo?.type !== 'file') { + setTimeout(() => { + setCloseButtonSelector(true); + }, 1000); + } else if (fileInfo && fileInfo?.size < 1 && fileInfo?.type !== 'audio' && fileInfo?.type !== 'file') { + setTimeout(() => { + setCloseButtonSelector(true); + }, 500); + } else { + setCloseButtonSelector(true); + } } }; @@ -50,14 +67,16 @@ export const InputSelector = (props: InputSelectorProps) => { return ( <div className={styles.container} ref={fileSelectorDiv}> - <button className={styles.removeButton} onClick={removeElementFromInput} ref={removeSelectedButton}> - <Close - style={{ - width: closeIconWidth ?? '', - height: closeIconHeight ?? '', - }} - /> - </button> + {closeButtonSelector && ( + <button className={styles.removeButton} onClick={removeElementFromInput} ref={removeFileButton}> + <Close + style={{ + width: closeIconWidth ?? '', + height: closeIconHeight ?? '', + }} + /> + </button> + )} <SourceMessage message={message} source={source} contentType={messageType} /> </div> ); diff --git a/frontend/ui/src/pages/Inbox/MessageInput/index.tsx b/frontend/ui/src/pages/Inbox/MessageInput/index.tsx index 4344864047..6f7b5bd899 100644 --- a/frontend/ui/src/pages/Inbox/MessageInput/index.tsx +++ b/frontend/ui/src/pages/Inbox/MessageInput/index.tsx @@ -4,7 +4,16 @@ import {sendMessages} from '../../../actions/messages'; import {withRouter} from 'react-router-dom'; import {Button, SimpleLoader} from 'components'; import {cyMessageSendButton, cyMessageTextArea, cySuggestionsButton} from 'handles'; -import {getOutboundMapper} from 'render'; +import { + getOutboundMapper, + getAttachmentType, + isSupportedByInstagramMessenger, + imageExtensions, + fileExtensions, + videoExtensions, + audioExtensions, + instagramImageExtensions, +} from 'render'; import {Message, SuggestedReply, Suggestions, Template, Source} from 'model'; import {isEmpty} from 'lodash-es'; @@ -24,15 +33,6 @@ import {InputOptions} from './InputOptions'; import styles from './index.module.scss'; import {HttpClientInstance} from '../../../httpClient'; import {FacebookMapper} from 'render/outbound/facebook'; -import { - getAttachmentType, - isSupportedByInstagramMessenger, - imageExtensions, - fileExtensions, - videoExtensions, - audioExtensions, - instagramImageExtensions, -} from 'render/attachments'; import {InputSelector} from './InputSelector'; import {usePrevious} from '../../../services/hooks/usePrevious'; @@ -53,6 +53,7 @@ type Props = { hideSuggestedReplies: () => void; draggedAndDroppedFile: File; setDraggedAndDroppedFile: React.Dispatch<React.SetStateAction<File | null>>; + setDragAndDropDisabled: React.Dispatch<React.SetStateAction<boolean>>; } & ConnectedProps<typeof connector>; interface SelectedTemplate { @@ -60,7 +61,12 @@ interface SelectedTemplate { source: Source; } -interface SelectedSuggestedReply { +export interface FileInfo { + size: number; + type: string; +} + +export interface SelectedSuggestedReply { message: SuggestedReply; } @@ -74,6 +80,7 @@ const MessageInput = (props: Props) => { sendMessages, draggedAndDroppedFile, setDraggedAndDroppedFile, + setDragAndDropDisabled, config, } = props; @@ -90,6 +97,7 @@ const MessageInput = (props: Props) => { const [uploadedFileUrl, setUploadedFileUrl] = useState<string | null>(null); const [fileUploadErrorPopUp, setFileUploadErrorPopUp] = useState<string>(''); const [loadingSelector, setLoadingSelector] = useState(false); + const [fileInfo, setFileInfo] = useState<null | {size: number; type: string}>(null); const prevConversationId = usePrevious(conversation.id); const textAreaRef = useRef(null); @@ -147,9 +155,18 @@ const MessageInput = (props: Props) => { useEffect(() => { if (fileToUpload) { setLoadingSelector(true); + setDragAndDropDisabled(true); } }, [fileToUpload]); + useEffect(() => { + if (isElementSelected()) { + setDragAndDropDisabled(true); + } else if (config.components['media-resolver'].enabled && (source === 'facebook' || source === 'instagram')) { + setDragAndDropDisabled(false); + } + }, [selectedTemplate, selectedSuggestedReply, uploadedFileUrl]); + useEffect(() => { if (textAreaRef && textAreaRef.current) { textAreaRef.current.style.height = 'inherit'; @@ -170,6 +187,7 @@ const MessageInput = (props: Props) => { const uploadFile = (file: File) => { const fileSizeInMB = file.size / Math.pow(1024, 2); + const maxFileSizeAllowed = 15; //instagram upload errors if (source === 'instagram') { @@ -186,8 +204,10 @@ const MessageInput = (props: Props) => { } //facebook upload errors - if (fileSizeInMB >= 25) { - return setFileUploadErrorPopUp('Failed to upload the file. The maximum file size allowed is 25MB.'); + if (fileSizeInMB >= maxFileSizeAllowed) { + return setFileUploadErrorPopUp( + `Failed to upload the file. The maximum file size allowed is ${maxFileSizeAllowed}MB.` + ); } if (!getAttachmentType(file.name)) { @@ -198,6 +218,7 @@ const MessageInput = (props: Props) => { return setFileUploadErrorPopUp(message); } + setFileInfo({size: fileSizeInMB, type: getAttachmentType(file.name)}); setFileToUpload(file); }; @@ -419,6 +440,7 @@ const MessageInput = (props: Props) => { messageType={selectedTemplate ? 'template' : selectedSuggestedReply ? 'suggestedReplies' : 'message'} removeElementFromInput={removeElementFromInput} contentResizedHeight={contentResizedHeight} + fileInfo={fileInfo} /> </> )} diff --git a/frontend/ui/src/pages/Inbox/Messenger/MessengerContainer/index.tsx b/frontend/ui/src/pages/Inbox/Messenger/MessengerContainer/index.tsx index 674d854078..90a8a6b68a 100644 --- a/frontend/ui/src/pages/Inbox/Messenger/MessengerContainer/index.tsx +++ b/frontend/ui/src/pages/Inbox/Messenger/MessengerContainer/index.tsx @@ -97,21 +97,22 @@ const MessengerContainer = ({ }; const handleDragEnter = (event: React.DragEvent<HTMLDivElement>) => { + if (dragAndDropDisabled) return; + event.preventDefault(); event.stopPropagation(); - if (dragAndDropDisabled) return; - dragCounter++; setIsFileDragged(true); }; const handleFileDrop = (event: React.DragEvent<HTMLDivElement>) => { + if (dragAndDropDisabled) return; + event.preventDefault(); event.stopPropagation(); - if (dragAndDropDisabled) return; dragCounter++; const file = event.dataTransfer.files[0]; setDraggedAndDroppedFile(file); @@ -119,10 +120,10 @@ const MessengerContainer = ({ }; const handleDragLeave = (event: React.DragEvent<HTMLDivElement>) => { + if (dragAndDropDisabled) return; event.preventDefault(); event.stopPropagation(); - if (dragAndDropDisabled) return; dragCounter--; if (dragCounter === 0) { setIsFileDragged(false); @@ -164,6 +165,7 @@ const MessengerContainer = ({ source={currentConversation.channel.source as Source} draggedAndDroppedFile={draggedAndDroppedFile} setDraggedAndDroppedFile={setDraggedAndDroppedFile} + setDragAndDropDisabled={setDragAndDropDisabled} /> </> )} diff --git a/lib/typescript/render/attachments.ts b/lib/typescript/render/attachments.ts index f20cb864ec..bd69144b2c 100644 --- a/lib/typescript/render/attachments.ts +++ b/lib/typescript/render/attachments.ts @@ -9,6 +9,7 @@ export const fileExtensions = [ 'docx', 'rtf', 'tex', + 'txt', 'wpd', 'psd', 'svg', diff --git a/lib/typescript/render/components/Image/index.module.scss b/lib/typescript/render/components/Image/index.module.scss index 80c801aa23..eacbe7dd55 100644 --- a/lib/typescript/render/components/Image/index.module.scss +++ b/lib/typescript/render/components/Image/index.module.scss @@ -15,5 +15,6 @@ .messageListItemImageBlock { max-height: 200px; + max-width: 300px; border-radius: 8px; } diff --git a/lib/typescript/render/components/ImageWithFallback/index.tsx b/lib/typescript/render/components/ImageWithFallback/index.tsx index a77207bd7a..8f9a1179c2 100644 --- a/lib/typescript/render/components/ImageWithFallback/index.tsx +++ b/lib/typescript/render/components/ImageWithFallback/index.tsx @@ -4,6 +4,7 @@ type ImageRenderProps = { src: string; alt?: string; className?: string; + isTemplate?: boolean; }; /** @@ -14,7 +15,7 @@ type ImageRenderProps = { */ const failedUrls = []; -export const ImageWithFallback = ({src, alt, className}: ImageRenderProps) => { +export const ImageWithFallback = ({src, alt, className, isTemplate}: ImageRenderProps) => { const [imageFailed, setImageFailed] = useState(failedUrls.includes(src)); useEffect(() => { @@ -27,13 +28,24 @@ export const ImageWithFallback = ({src, alt, className}: ImageRenderProps) => { }; return ( - <a href={src} target="_blank" rel="noopener noreferrer"> - <img - className={className} - src={imageFailed ? 'https://s3.amazonaws.com/assets.airy.co/fallbackMediaImage.svg' : src} - alt={imageFailed ? 'The image failed to load' : alt} - onError={() => loadingFailed()} - /> - </a> + <> + {isTemplate ? ( + <img + className={className} + src={imageFailed ? 'https://s3.amazonaws.com/assets.airy.co/fallbackMediaImage.svg' : src} + alt={imageFailed ? 'The image failed to load' : alt} + onError={() => loadingFailed()} + /> + ) : ( + <a href={src} target="_blank" rel="noopener noreferrer"> + <img + className={className} + src={imageFailed ? 'https://s3.amazonaws.com/assets.airy.co/fallbackMediaImage.svg' : src} + alt={imageFailed ? 'The image failed to load' : alt} + onError={() => loadingFailed()} + /> + </a> + )} + </> ); }; diff --git a/lib/typescript/render/index.ts b/lib/typescript/render/index.ts index 6f789fa4ef..8b99a1ccec 100644 --- a/lib/typescript/render/index.ts +++ b/lib/typescript/render/index.ts @@ -2,3 +2,4 @@ export * from './props'; export * from './SourceMessage'; export * from './SourceMessagePreview'; export * from './outbound'; +export * from './attachments'; diff --git a/lib/typescript/render/providers/chatplugin/components/QuickReplies/index.tsx b/lib/typescript/render/providers/chatplugin/components/QuickReplies/index.tsx index 9ea497b396..e9b6919888 100644 --- a/lib/typescript/render/providers/chatplugin/components/QuickReplies/index.tsx +++ b/lib/typescript/render/providers/chatplugin/components/QuickReplies/index.tsx @@ -44,9 +44,14 @@ export const QuickReplies = ({ <div className={styles.container}> {quickReplies.map((reply: QuickReply) => ( - <button key={reply.title} className={styles.replyButton} onClick={() => clickPostback(reply)}> + <button type="button" key={reply.title} className={styles.replyButton} onClick={() => clickPostback(reply)}> {reply.image_url && ( - <ImageWithFallback className={styles.quickReplyImage} alt={reply.title} src={reply.image_url} /> + <ImageWithFallback + className={styles.quickReplyImage} + alt={reply.title} + src={reply.image_url} + isTemplate + /> )} <h1 key={reply.title} className={styles.title}> {reply.title} diff --git a/lib/typescript/render/providers/chatplugin/components/RichCard/Media/index.tsx b/lib/typescript/render/providers/chatplugin/components/RichCard/Media/index.tsx index 2e1c798a54..604c5fcdf5 100644 --- a/lib/typescript/render/providers/chatplugin/components/RichCard/Media/index.tsx +++ b/lib/typescript/render/providers/chatplugin/components/RichCard/Media/index.tsx @@ -28,5 +28,5 @@ const getHeight = (height: MediaHeight): string => { }; export const Media = ({height, contentInfo: {altText, fileUrl}}: MediaRenderProps) => ( - <ImageWithFallback src={fileUrl} alt={altText} className={`${styles.mediaImage} ${getHeight(height)}`} /> + <ImageWithFallback src={fileUrl} alt={altText} className={`${styles.mediaImage} ${getHeight(height)}`} isTemplate /> ); diff --git a/lib/typescript/render/providers/facebook/components/GenericTemplate/index.tsx b/lib/typescript/render/providers/facebook/components/GenericTemplate/index.tsx index 7332fd595d..ea450ab60d 100644 --- a/lib/typescript/render/providers/facebook/components/GenericTemplate/index.tsx +++ b/lib/typescript/render/providers/facebook/components/GenericTemplate/index.tsx @@ -16,7 +16,9 @@ export const GenericTemplate = ({template}: GenericTemplateRendererProps) => { <Carousel> {template.elements.map((element, idx) => ( <div key={`template-${idx}`} className={styles.template}> - {element.image_url?.length && <ImageWithFallback className={styles.templateImage} src={element.image_url} />} + {element.image_url?.length && ( + <ImageWithFallback className={styles.templateImage} src={element.image_url} isTemplate /> + )} <div className={styles.innerTemplate}> <div className={styles.templateTitle}>{element.title}</div> <div className={styles.templateSubtitle}>{element.subtitle}</div> diff --git a/lib/typescript/render/providers/facebook/components/QuickReplies/index.tsx b/lib/typescript/render/providers/facebook/components/QuickReplies/index.tsx index b524ea01d9..c9fb56d246 100644 --- a/lib/typescript/render/providers/facebook/components/QuickReplies/index.tsx +++ b/lib/typescript/render/providers/facebook/components/QuickReplies/index.tsx @@ -27,8 +27,8 @@ export const QuickReplies = ({quickReplies, fromContact, text, attachment}: Quic <div className={styles.container}> {quickReplies.map(({title, image_url: imageUrl}) => ( - <button key={title} className={styles.replyButton}> - {imageUrl && <ImageWithFallback className={styles.quickReplyImage} alt={title} src={imageUrl} />} + <button type="button" key={title} className={styles.replyButton}> + {imageUrl && <ImageWithFallback className={styles.quickReplyImage} alt={title} src={imageUrl} isTemplate />} <h1 key={title} className={styles.title}> {title} </h1> diff --git a/lib/typescript/render/providers/google/components/RichCard/Media/index.tsx b/lib/typescript/render/providers/google/components/RichCard/Media/index.tsx index d076856061..52767f1b64 100644 --- a/lib/typescript/render/providers/google/components/RichCard/Media/index.tsx +++ b/lib/typescript/render/providers/google/components/RichCard/Media/index.tsx @@ -28,5 +28,5 @@ const getHeight = (height: MediaHeight): string => { }; export const Media = ({height, contentInfo: {altText, fileUrl}}: MediaRenderProps) => ( - <ImageWithFallback src={fileUrl} alt={altText} className={`${styles.mediaImage} ${getHeight(height)}`} /> + <ImageWithFallback src={fileUrl} alt={altText} className={`${styles.mediaImage} ${getHeight(height)}`} isTemplate /> ); diff --git a/lib/typescript/render/providers/google/components/Suggestions/index.tsx b/lib/typescript/render/providers/google/components/Suggestions/index.tsx index 99aa32400e..e89cda5b16 100644 --- a/lib/typescript/render/providers/google/components/Suggestions/index.tsx +++ b/lib/typescript/render/providers/google/components/Suggestions/index.tsx @@ -30,7 +30,7 @@ export const Suggestions = ({text, fallback, image, suggestions, fromContact}: S {(suggestions as SuggestionsUnion[]).map(elem => { if ('reply' in elem) { return ( - <button key={elem.reply.text} className={styles.replyButton}> + <button type="button" key={elem.reply.text} className={styles.replyButton}> <h1 key={elem.reply.text} className={styles.title}> {elem.reply.text} </h1> @@ -40,7 +40,7 @@ export const Suggestions = ({text, fallback, image, suggestions, fromContact}: S if ('action' in elem) { return ( - <button key={elem.action.text} className={styles.replyButton}> + <button type="button" key={elem.action.text} className={styles.replyButton}> <img className={styles.actionImage} alt={elem.action.openUrlAction ? 'link icon' : 'phone icon'} @@ -66,7 +66,7 @@ export const Suggestions = ({text, fallback, image, suggestions, fromContact}: S if ('authenticationRequest' in elem) { return ( - <button key={elem.authenticationRequest.oauth.clientId} className={styles.replyButton}> + <button type="button" key={elem.authenticationRequest.oauth.clientId} className={styles.replyButton}> <h1 key={elem.authenticationRequest.oauth.clientId} className={styles.title}> Authenticate with Google </h1> @@ -76,7 +76,7 @@ export const Suggestions = ({text, fallback, image, suggestions, fromContact}: S if ('liveAgentRequest' in elem) { return ( - <button key={Math.floor(Math.random() * 50)} className={styles.replyButton}> + <button type="button" key={Math.floor(Math.random() * 50)} className={styles.replyButton}> <h1 key={Math.floor(Math.random() * 50)} className={styles.title}> Message a live agent on Google's Business Messages </h1>