diff --git a/services/web/frontend/js/features/chat/components/chat-fallback-error.jsx b/services/web/frontend/js/features/chat/components/chat-fallback-error.tsx similarity index 81% rename from services/web/frontend/js/features/chat/components/chat-fallback-error.jsx rename to services/web/frontend/js/features/chat/components/chat-fallback-error.tsx index 7cf990c787..b572722c9f 100644 --- a/services/web/frontend/js/features/chat/components/chat-fallback-error.jsx +++ b/services/web/frontend/js/features/chat/components/chat-fallback-error.tsx @@ -1,9 +1,12 @@ -import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import OLNotification from '@/features/ui/components/ol/ol-notification' import OLButton from '@/features/ui/components/ol/ol-button' -function ChatFallbackError({ reconnect }) { +interface ChatFallbackErrorProps { + reconnect?: () => void +} + +function ChatFallbackError({ reconnect }: ChatFallbackErrorProps) { const { t } = useTranslation() return ( @@ -22,8 +25,4 @@ function ChatFallbackError({ reconnect }) { ) } -ChatFallbackError.propTypes = { - reconnect: PropTypes.any, -} - export default ChatFallbackError diff --git a/services/web/frontend/js/features/chat/components/infinite-scroll.jsx b/services/web/frontend/js/features/chat/components/infinite-scroll.tsx similarity index 56% rename from services/web/frontend/js/features/chat/components/infinite-scroll.jsx rename to services/web/frontend/js/features/chat/components/infinite-scroll.tsx index 361fec55d4..9de0ed9380 100644 --- a/services/web/frontend/js/features/chat/components/infinite-scroll.jsx +++ b/services/web/frontend/js/features/chat/components/infinite-scroll.tsx @@ -1,9 +1,17 @@ import { useRef, useEffect, useLayoutEffect } from 'react' -import PropTypes from 'prop-types' import _ from 'lodash' const SCROLL_END_OFFSET = 30 +interface InfiniteScrollProps { + atEnd?: boolean + children: React.ReactElement + className?: string + fetchData(): void + itemCount: number + isLoading?: boolean +} + function InfiniteScroll({ atEnd, children, @@ -11,20 +19,22 @@ function InfiniteScroll({ fetchData, itemCount, isLoading, -}) { - const root = useRef(null) +}: InfiniteScrollProps) { + const root = useRef(null) // we keep the value in a Ref instead of state so it can be safely used in effects const scrollBottomRef = useRef(0) - function setScrollBottom(value) { + function setScrollBottom(value: number) { scrollBottomRef.current = value } function updateScrollPosition() { - root.current.scrollTop = - root.current.scrollHeight - - root.current.clientHeight - - scrollBottomRef.current + if (root.current) { + root.current.scrollTop = + root.current.scrollHeight - + root.current.clientHeight - + scrollBottomRef.current + } } // Repositions the scroll after new items are loaded @@ -39,23 +49,29 @@ function InfiniteScroll({ } }, []) - function onScrollHandler(event) { - setScrollBottom( - root.current.scrollHeight - - root.current.scrollTop - - root.current.clientHeight - ) - if (event.target !== event.currentTarget) { - // Ignore scroll events on nested divs - // (this check won't be necessary in React 17: https://github.com/facebook/react/issues/15723 - return - } - if (shouldFetchData()) { - fetchData() + function onScrollHandler(event: React.UIEvent) { + if (root.current) { + setScrollBottom( + root.current.scrollHeight - + root.current.scrollTop - + root.current.clientHeight + ) + + if (event.target !== event.currentTarget) { + // Ignore scroll events on nested divs + // (this check won't be necessary in React 17: https://github.com/facebook/react/issues/15723 + return + } + if (shouldFetchData()) { + fetchData() + } } } function shouldFetchData() { + if (!root.current) { + return false + } const containerIsLargerThanContent = root.current.children[0].clientHeight < root.current.clientHeight if (atEnd || isLoading || containerIsLargerThanContent) { @@ -76,13 +92,4 @@ function InfiniteScroll({ ) } -InfiniteScroll.propTypes = { - atEnd: PropTypes.bool, - children: PropTypes.element.isRequired, - className: PropTypes.string, - fetchData: PropTypes.func.isRequired, - itemCount: PropTypes.number.isRequired, - isLoading: PropTypes.bool, -} - export default InfiniteScroll diff --git a/services/web/frontend/js/features/chat/components/message-list.jsx b/services/web/frontend/js/features/chat/components/message-list.tsx similarity index 67% rename from services/web/frontend/js/features/chat/components/message-list.jsx rename to services/web/frontend/js/features/chat/components/message-list.tsx index 173e86c66f..410cd255f2 100644 --- a/services/web/frontend/js/features/chat/components/message-list.jsx +++ b/services/web/frontend/js/features/chat/components/message-list.tsx @@ -1,10 +1,11 @@ -import PropTypes from 'prop-types' import moment from 'moment' import Message from './message' +import { UserId } from '../../../../../types/user' +import type { Message as MessageType } from '@/features/chat/context/chat-context' const FIVE_MINUTES = 5 * 60 * 1000 -function formatTimestamp(date) { +function formatTimestamp(date: moment.MomentInput) { if (!date) { return 'N/A' } else { @@ -12,14 +13,28 @@ function formatTimestamp(date) { } } -function MessageList({ messages, resetUnreadMessages, userId }) { - function shouldRenderDate(messageIndex) { +interface MessageListProps { + messages: MessageType[] + resetUnreadMessages(...args: unknown[]): unknown + userId: UserId | null +} + +function MessageList({ + messages, + resetUnreadMessages, + userId, +}: MessageListProps) { + function shouldRenderDate(messageIndex: number) { if (messageIndex === 0) { return true } else { const message = messages[messageIndex] const previousMessage = messages[messageIndex - 1] - return message.timestamp - previousMessage.timestamp > FIVE_MINUTES + return ( + message.timestamp && + previousMessage.timestamp && + message.timestamp - previousMessage.timestamp > FIVE_MINUTES + ) } } @@ -53,15 +68,4 @@ function MessageList({ messages, resetUnreadMessages, userId }) { ) } -MessageList.propTypes = { - messages: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - timestamp: PropTypes.number, - }) - ).isRequired, - resetUnreadMessages: PropTypes.func.isRequired, - userId: PropTypes.string, -} - export default MessageList diff --git a/services/web/frontend/js/features/chat/components/message.jsx b/services/web/frontend/js/features/chat/components/message.tsx similarity index 68% rename from services/web/frontend/js/features/chat/components/message.jsx rename to services/web/frontend/js/features/chat/components/message.tsx index 380b2fd1cc..87fbb8dbd3 100644 --- a/services/web/frontend/js/features/chat/components/message.jsx +++ b/services/web/frontend/js/features/chat/components/message.tsx @@ -1,20 +1,26 @@ -import PropTypes from 'prop-types' import { getHueForUserId } from '../../../shared/utils/colors' import MessageContent from './message-content' +import type { Message as MessageType } from '@/features/chat/context/chat-context' +import { User } from '../../../../../types/user' -function Message({ message, userId }) { - function hue(user) { +interface MessageProps { + message: MessageType + userId: string | null +} + +function Message({ message, userId }: MessageProps) { + function hue(user?: User) { return user ? getHueForUserId(user.id, userId) : 0 } - function getMessageStyle(user) { + function getMessageStyle(user?: User) { return { borderColor: `hsl(${hue(user)}, 85%, 40%)`, backgroundColor: `hsl(${hue(user)}, 85%, 40%`, } } - function getArrowStyle(user) { + function getArrowStyle(user?: User) { return { borderColor: `hsl(${hue(user)}, 85%, 40%)`, } @@ -24,7 +30,7 @@ function Message({ message, userId }) { return (
- {!isMessageFromSelf && ( + {!isMessageFromSelf && message.user.id && (
{message.user.first_name || message.user.email}
@@ -43,16 +49,4 @@ function Message({ message, userId }) { ) } -Message.propTypes = { - message: PropTypes.shape({ - contents: PropTypes.arrayOf(PropTypes.string).isRequired, - user: PropTypes.shape({ - id: PropTypes.string, - email: PropTypes.string, - first_name: PropTypes.string, - }), - }), - userId: PropTypes.string, -} - export default Message diff --git a/services/web/frontend/js/features/chat/context/chat-context.tsx b/services/web/frontend/js/features/chat/context/chat-context.tsx index 715c178d22..d2b078138d 100644 --- a/services/web/frontend/js/features/chat/context/chat-context.tsx +++ b/services/web/frontend/js/features/chat/context/chat-context.tsx @@ -19,13 +19,15 @@ import { useLayoutContext } from '../../../shared/context/layout-context' import { useIdeContext } from '@/shared/context/ide-context' import getMeta from '@/utils/meta' import { debugConsole } from '@/utils/debugging' +import { User } from '../../../../../types/user' const PAGE_SIZE = 50 export type Message = { id: string timestamp: number - contents: string + contents: string[] + user: User } type State = {