Skip to content

Commit

Permalink
botonic-react: WebchatReplies and Reply with typescript (#2952)
Browse files Browse the repository at this point in the history
## Description

Using typescript in Reply and WebchatReplies components

In the WebchatReplies component I have to put a @ts-ignore because
replies is of type Reply[] but typescript does not detect that the
component is already “rendered with the props” (TODO investigate in the
future how to pass the props and render Reply components at this point).
  • Loading branch information
Iru89 authored Jan 10, 2025
1 parent 239c6f4 commit 8444a6d
Show file tree
Hide file tree
Showing 15 changed files with 360 additions and 116 deletions.
261 changes: 236 additions & 25 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/botonic-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ All notable changes to Botonic will be documented in this file.
- [`Webchat` component with typescript](https://github.com/hubtype/botonic/pull/2947)
- [`Header` component with typescript](https://github.com/hubtype/botonic/pull/2949)
- [fix types and tests](https://github.com/hubtype/botonic/pull/2950)
- [`WebchatReplies` and `Reply` with typescript](https://github.com/hubtype/botonic/pull/2952)

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import styled from 'styled-components'
import { COLORS, WEBCHAT } from '../constants'
import { WebchatContext } from '../contexts'
import { renderComponent } from '../util/react'
import { ReplyProps } from './index-types'

const StyledButton = styled.button`
width: 100%;
Expand All @@ -14,9 +15,10 @@ const StyledButton = styled.button`
outline: 0;
`

export const Reply = props => {
export const Reply = (props: ReplyProps) => {
const { sendText, getThemeProperty } = useContext(WebchatContext)
const handleClick = event => {

const handleClick = (event: any) => {
event.preventDefault()
if (props.children) {
let payload = props.payload
Expand Down Expand Up @@ -59,15 +61,19 @@ export const Reply = props => {
const renderNode = () => {
if (props.path) {
const payload = `__PATH_PAYLOAD__${props.path}`
// @ts-ignore
// eslint-disable-next-line react/no-unknown-property
return <reply payload={payload}>{props.children}</reply>
}
// @ts-ignore
// eslint-disable-next-line react/no-unknown-property
return <reply payload={props.payload}>{props.children}</reply>
}

return renderComponent({ renderBrowser, renderNode })
}

Reply.serialize = replyProps => {
Reply.serialize = (replyProps: ReplyProps) => {
let payload = replyProps.payload
if (replyProps.path) payload = `__PATH_PAYLOAD__${replyProps.path}`
return { reply: { title: replyProps.children, payload } }
Expand Down
21 changes: 11 additions & 10 deletions packages/botonic-react/src/index-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@botonic/core'
import React from 'react'

import { Reply } from './components'
import {
BlockInputOption,
ButtonProps,
Expand Down Expand Up @@ -122,8 +123,8 @@ export interface WebchatProps {
shadowDOM?: any
theme?: ThemeProps
persistentMenu?: PersistentMenuTheme
coverComponent?: any
blockInputs?: any
coverComponent?: CoverComponentOptions
blockInputs?: BlockInputOption[]
enableEmojiPicker?: boolean
enableAttachments?: boolean
enableUserInput?: boolean
Expand All @@ -135,7 +136,7 @@ export interface WebchatProps {
onInit?: (args?: any) => void
onOpen?: (args?: any) => void
onClose?: (args?: any) => void
onUserInput(args: OnUserInputArgs): Promise<void> // TODO: Review this function and params types
onUserInput(args: OnUserInputArgs): Promise<void>
onTrackEvent?: TrackEventFunction
host?: any
server?: ServerConfig
Expand Down Expand Up @@ -163,19 +164,19 @@ export interface WebchatMessage {
ack: 0 | 1
blob: boolean
buttons: ButtonProps[]
children: any
data: any
children: any // messageJSON don't have children prop
data: any // if message.type === 'text' => message.data = {text: string}
delay: number
display: boolean
enabletimestamps: boolean
enabletimestamps?: boolean
id: string
imagestyle: any
imagestyle?: any
isUnread: boolean
json: any
markdown: boolean
markdown: boolean // 0 | 1
replies: ReplyProps[]
sentBy: SENDERS
style: any
style?: any
timestamp: string
type: CoreInputType
typing: number
Expand Down Expand Up @@ -236,7 +237,7 @@ export interface WebchatContextProps {
toggleCoverComponent: (toggle: boolean) => void
updateLatestInput: (input: ClientInput) => void
updateMessage: (message: WebchatMessage) => void
updateReplies: (replies: boolean) => void
updateReplies: (replies: (typeof Reply)[]) => void
updateUser: (user: Partial<CoreSessionUser>) => void
updateWebchatDevSettings: (settings: WebchatSettingsProps) => void
trackEvent?: TrackEventFunction
Expand Down
6 changes: 5 additions & 1 deletion packages/botonic-react/src/webchat-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,11 @@ export class WebchatApp {
sentBy: SENDERS.user,
isUnread: false,
} as unknown as WebchatMessage)
this.hubtypeService.postMessage(user, input)
this.hubtypeService.postMessage(user, {
...input,
// TODO: Review if this is correct add sent_by or this is added in backend
// sent_by: 'message_sent_by_user',
})
return
}

Expand Down
1 change: 1 addition & 0 deletions packages/botonic-react/src/webchat/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export enum WebchatAction {
UPDATE_THEME = 'updateTheme',
UPDATE_TYPING = 'updateTyping',
UPDATE_WEBVIEW = 'updateWebview',
REMOVE_REPLIES = 'removeReplies',
REMOVE_WEBVIEW = 'removeWebview',
SET_IS_INPUT_FOCUSED = 'setIsInputFocused',
}
4 changes: 1 addition & 3 deletions packages/botonic-react/src/webchat/chat-area/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ export const ChatArea = () => {
height={chatAreaHeight}
>
<WebchatMessageList />
{replies && Object.keys(replies).length > 0 && (
<WebchatReplies replies={replies} />
)}
{replies && replies.length > 0 && <WebchatReplies />}
</StyledChatArea>
)
}
10 changes: 8 additions & 2 deletions packages/botonic-react/src/webchat/hooks/use-webchat.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Session } from '@botonic/core'
import { useReducer, useRef } from 'react'

import { Reply } from '../../components'
import { ThemeProps, Webview } from '../../components/index-types'
import { COLORS, WEBCHAT } from '../../constants'
import { ClientInput, WebchatMessage } from '../../index-types'
Expand Down Expand Up @@ -68,11 +69,12 @@ export interface UseWebchat {
updateLastRoutePath: (path: string) => void
updateLatestInput: (input: ClientInput) => void
updateMessage: (message: WebchatMessage) => void
updateReplies: (replies: any) => void
updateReplies: (replies: (typeof Reply)[]) => void
updateSession: (session: Partial<Session>) => void
updateTheme: (theme: ThemeProps, themeUpdates?: ThemeProps) => void
updateTyping: (typing: boolean) => void
updateWebview: (webview: Webview, params: Record<string, string>) => void
removeReplies: () => void
removeWebview: () => void
webchatState: WebchatState
webchatRef: React.MutableRefObject<HTMLDivElement | null> // TODO: Change name, already exists WebchatRef for useImperativeHandle
Expand Down Expand Up @@ -108,9 +110,12 @@ export function useWebchat(): UseWebchat {
const updateMessage = (message: WebchatMessage) =>
webchatDispatch({ type: WebchatAction.UPDATE_MESSAGE, payload: message })

const updateReplies = replies =>
const updateReplies = (replies: (typeof Reply)[]) =>
webchatDispatch({ type: WebchatAction.UPDATE_REPLIES, payload: replies })

const removeReplies = () =>
webchatDispatch({ type: WebchatAction.REMOVE_REPLIES, payload: [] })

const updateLatestInput = (input: ClientInput) =>
webchatDispatch({ type: WebchatAction.UPDATE_LATEST_INPUT, payload: input })

Expand Down Expand Up @@ -271,6 +276,7 @@ export function useWebchat(): UseWebchat {
updateTheme,
updateTyping,
updateWebview,
removeReplies,
removeWebview,
webchatState,
webchatRef,
Expand Down
3 changes: 2 additions & 1 deletion packages/botonic-react/src/webchat/index-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Input as CoreInput, Session as CoreSession } from '@botonic/core'

import { Reply } from '../components'
import { Webview } from '../components/index-types'
import { WebchatArgs } from '../index-types'

Expand Down Expand Up @@ -28,7 +29,7 @@ export interface WebchatState {
height: number
messagesJSON: any[]
messagesComponents: any[]
replies: any[]
replies?: (typeof Reply)[]
latestInput: Partial<CoreInput>
typing: boolean
webview: Webview | null
Expand Down
6 changes: 3 additions & 3 deletions packages/botonic-react/src/webchat/message-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ export const WebchatMessageList = () => {
<IntroMessage />
{webchatState.messagesComponents.map((messageComponent, index) => {
return (
<>
<ContainerMessage role={ROLES.MESSAGE} key={index}>
<React.Fragment key={messageComponent.props.id}>
<ContainerMessage role={ROLES.MESSAGE}>
{showUnreadMessagesBanner(messageComponent.props.id) && (
<UnreadMessagesBanner
unreadMessagesBannerRef={unreadMessagesBannerRef}
Expand All @@ -151,7 +151,7 @@ export const WebchatMessageList = () => {
}}
></div>
)}
</>
</React.Fragment>
)
})}
{webchatState.typing && <TypingIndicator ref={typingRef} />}
Expand Down
2 changes: 2 additions & 0 deletions packages/botonic-react/src/webchat/messages-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export const messagesReducer = (
return updateMessageReducer(state, action)
case WebchatAction.UPDATE_REPLIES:
return { ...state, replies: action.payload }
case WebchatAction.REMOVE_REPLIES:
return { ...state, replies: undefined }
case WebchatAction.CLEAR_MESSAGES:
return {
...state,
Expand Down
64 changes: 0 additions & 64 deletions packages/botonic-react/src/webchat/replies.jsx

This file was deleted.

47 changes: 47 additions & 0 deletions packages/botonic-react/src/webchat/replies/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { useContext } from 'react'

import { WEBCHAT } from '../../constants'
import { WebchatContext } from '../../contexts'
import { BotonicContainerId } from '../constants'
import { RepliesContainer, ReplyContainer, ScrollableReplies } from './styles'

const options = {
left: 'flex-start',
center: 'center',
right: 'flex-end',
}

export const WebchatReplies = () => {
const { webchatState, getThemeProperty, repliesRef } =
useContext(WebchatContext)

let justifyContent = 'center'
const flexWrap = getThemeProperty(
WEBCHAT.CUSTOM_PROPERTIES.wrapReplies,
'wrap'
)

if (flexWrap === 'nowrap') {
justifyContent = 'flex-start'
} else if (getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.alignReplies)) {
justifyContent =
options[getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.alignReplies)]
}

return (
<ScrollableReplies>
<RepliesContainer
id={BotonicContainerId.RepliesContainer}
ref={repliesRef}
className='replies-container'
justify={justifyContent}
wrap={flexWrap}
>
{webchatState.replies?.map((reply, i) => (
// @ts-ignore TODO: In this point reply is the the component to render
<ReplyContainer key={`reply-${i}`}>{reply}</ReplyContainer>
))}
</RepliesContainer>
</ScrollableReplies>
)
}
28 changes: 28 additions & 0 deletions packages/botonic-react/src/webchat/replies/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import styled from 'styled-components'

export const ScrollableReplies = styled.div`
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
`

interface RepliesContainerProps {
justify: string
wrap: string
}

export const RepliesContainer = styled.div<RepliesContainerProps>`
display: flex;
text-align: center;
justify-content: ${props => props.justify};
flex-wrap: ${props => props.wrap};
padding-bottom: 10px;
margin-left: 5px;
margin-right: 5px;
overflow-x: auto;
`

export const ReplyContainer = styled.div`
flex: none;
display: inline-block;
margin: 3px;
`
Loading

0 comments on commit 8444a6d

Please sign in to comment.