Skip to content

Commit

Permalink
botonic-react: some little changes using typescript (#2950)
Browse files Browse the repository at this point in the history
## Description

Review commit to commit for more context
In this PR are several small changes that were missing from previous
PRs.

Significant changes 
- the useWebchat already exports the closeWebchat function and
repliesRef
- in the useScrollController checks repliesRef.current

With these changes all tests pass
  • Loading branch information
Iru89 authored Jan 10, 2025
1 parent dce5bcb commit 239c6f4
Show file tree
Hide file tree
Showing 15 changed files with 3,269 additions and 6,058 deletions.
9,185 changes: 3,163 additions & 6,022 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"clean": "rimraf packages/**/lib"
},
"dependencies": {
"axios": "^1.7.2"
"axios": "^1.7.2",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@babel/cli": "^7.23.9",
Expand All @@ -17,9 +19,11 @@
"@babel/runtime": "^7.23.9",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.19",
"@types/react-dom": "^18.0.0",
"@types/react": "^18.0.0",
"@types/rimraf": "^3.0.0",
"@typescript-eslint/parser": "^6.21.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"babel-plugin-add-module-exports": "^1.0.4",
"cloc": "^2.7.0",
"eslint": "^8.56.0",
Expand Down
1 change: 1 addition & 0 deletions packages/botonic-plugin-flow-builder/src/action/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type FlowBuilderActionProps = {

export class FlowBuilderAction extends React.Component<FlowBuilderActionProps> {
static contextType = RequestContext
declare context: React.ContextType<typeof RequestContext>

static async executeConversationStart(
request: ActionRequest
Expand Down
1 change: 1 addition & 0 deletions packages/botonic-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ All notable changes to Botonic will be documented in this file.
- [`WebchatApp` with typescript](https://github.com/hubtype/botonic/pull/2945)
- [`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)

### Fixed

Expand Down
1 change: 1 addition & 0 deletions packages/botonic-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/styled-components": "^5.1.34",
"@types/ua-parser-js": "^0.7.39",
"@types/uuid": "^10.0.0",
"babel-plugin-add-module-exports": "^1.0.4",
"copyfiles": "^2.4.1",
Expand Down
2 changes: 0 additions & 2 deletions packages/botonic-react/src/components/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export const Button = (props: ButtonProps) => {
updateMessage,
} = useContext(WebchatContext)
const [hover, setHover] = useState(false)
const { theme } = webchatState
const { autoDisable, disabledStyle } = ButtonsDisabler.resolveDisabling(
webchatState.theme,
props
Expand Down Expand Up @@ -170,7 +169,6 @@ export const Button = (props: ButtonProps) => {
return (
<StyledButton
className={getClassName()}
theme={theme}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
onClick={e => handleClick(e)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,31 @@ import { SENDERS } from '../index-types'
import { createErrorBoundary } from '../util/error-boundary'
import { warnDeprecatedProps } from '../util/logs'
import { mapObjectNonBooleanValues } from '../util/react'
import { CustomMessageType } from './index-types'
import { Message } from './message'
import { Reply } from './reply'

/**
*
* @param name as it appears at ThemeProps' message.customTypes key
* @param CustomMessageComponent
* @param defaultProps Props for the wrapper Message
* @param ErrorBoundary to recover in case it fails
* name: as it appears at ThemeProps' message.customTypes key
* CustomMessageComponent
* defaultProps: Props for the wrapper Message
* ErrorBoundary: to recover in case it fails
*/

export interface CustomMessageArgs {
name: string
component: React.ComponentType
defaultProps?: Record<string, any>
errorBoundary?: any
}

export const customMessage = ({
name,
component: CustomMessageComponent,
defaultProps = {},
errorBoundary: ErrorBoundary = createErrorBoundary(),
}) => {
errorBoundary = createErrorBoundary(),
}: CustomMessageArgs): CustomMessageType => {
const CustomMessage = props => {
warnDeprecatedProps(defaultProps, 'customMessage:')
if (defaultProps.sentBy === SENDERS.user) {
Expand Down Expand Up @@ -57,6 +66,9 @@ export const customMessage = ({
const WrappedComponent = props => {
const { id, children, ...customMessageProps } = props
const { replies, childrenWithoutReplies } = splitChildren(props)

const ErrorBoundary = errorBoundary

return (
<CustomMessage
id={id}
Expand All @@ -80,7 +92,7 @@ export const customMessage = ({
}
WrappedComponent.customTypeName = name
// eslint-disable-next-line react/display-name
WrappedComponent.deserialize = msg => (
WrappedComponent.deserialize = (msg: any) => (
<WrappedComponent
id={msg.id}
key={msg.key}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import merge from 'lodash.merge'
import UAParser from 'ua-parser-js'
import { v7 as uuidv7 } from 'uuid'

import { ThemeProps } from '../components'
import { WEBCHAT } from '../constants'
import { getProperty } from './objects'

Expand All @@ -11,21 +12,23 @@ import { getProperty } from './objects'
* If property doesn't exist, returns the defaultValue.
*/
export const _getThemeProperty =
theme =>
(property, defaultValue = undefined) => {
for (const [k, v] of Object.entries(WEBCHAT.CUSTOM_PROPERTIES)) {
if (v == property) {
const nestedProperty = getProperty(theme, v)
(theme: ThemeProps) => (property: string, defaultValue?: any) => {
for (const [key, value] of Object.entries(WEBCHAT.CUSTOM_PROPERTIES)) {
if (value === property) {
const nestedProperty = getProperty(theme, value)
if (nestedProperty !== undefined) return nestedProperty
const plainProperty = getProperty(theme, k)
const plainProperty = getProperty(theme, key)
if (plainProperty !== undefined) return plainProperty
return defaultValue
}
}
return undefined
}

export const createUser = () => {
export const createUser = (): {
id: string
name: string
} => {
const parser = new UAParser()
const ua = parser.getResult()
let name = `${ua.os.name} ${ua.browser.name}`
Expand All @@ -35,9 +38,11 @@ export const createUser = () => {
name,
}
}
export const initSession = session => {
export const initSession = (
session: any
): { user: { id: string; name: string } } => {
if (!session) session = {}
const hasUserId = session.user && session.user.id !== undefined
const hasUserId = session?.user?.id !== undefined
if (!session.user || Object.keys(session.user).length === 0 || !hasUserId)
session.user = !hasUserId ? merge(session.user, createUser()) : createUser()
return session
Expand All @@ -48,6 +53,7 @@ export const shouldKeepSessionOnReload = ({
devSettings,
}) => !initialDevSettings || (devSettings && devSettings.keepSessionOnReload)

//TODO: Review param serverConfig if is of type ServerConfig this never have errorMessage
export const getServerErrorMessage = serverConfig => {
if (!serverConfig || !serverConfig.errorMessage) return 'Connection issues'
if (typeof serverConfig.errorMessage === 'function') {
Expand Down
2 changes: 1 addition & 1 deletion packages/botonic-react/src/webchat/hooks/use-previous.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react'

export function usePrevious(value) {
export function usePrevious(value: any): any {
const ref = useRef()
useEffect(() => {
ref.current = value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const useScrollbarController = (currentDevice, host) => {

const hasScrollbar = () => {
if (chatAreaRef.current && scrollableMessagesListRef.current) {
if (!repliesRef) {
if (!repliesRef?.current) {
return (
scrollableMessagesListRef.current?.scrollHeight >
chatAreaRef.current?.clientHeight
Expand Down
55 changes: 48 additions & 7 deletions packages/botonic-react/src/webchat/hooks/use-webchat.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Input, Session } from '@botonic/core'
import { Session } from '@botonic/core'
import { useReducer, useRef } from 'react'

import { ThemeProps, Webview } from '../../components/index-types'
Expand All @@ -19,7 +19,7 @@ export const webchatInitialState: WebchatState = {
webview: null,
webviewParams: null,
session: { user: undefined },
lastRoutePath: null,
lastRoutePath: undefined,
handoff: false,
theme: {
headerTitle: WEBCHAT.DEFAULTS.TITLE,
Expand Down Expand Up @@ -47,7 +47,43 @@ export const webchatInitialState: WebchatState = {
isInputFocused: false,
}

export function useWebchat() {
export interface UseWebchat {
addMessage: (message: WebchatMessage) => void
addMessageComponent: (message: { props: WebchatMessage }) => void
clearMessages: () => void
doRenderCustomComponent: (toggle: boolean) => void
resetUnreadMessages: () => void
setCurrentAttachment: (attachment?: File) => void
setError: (error?: ErrorMessage) => void
setIsInputFocused: (isInputFocused: boolean) => void
setLastMessageVisible: (isLastMessageVisible: boolean) => void
setOnline: (online: boolean) => void
toggleCoverComponent: (toggle: boolean) => void
toggleEmojiPicker: (toggle: boolean) => void
togglePersistentMenu: (toggle: boolean) => void
toggleWebchat: (toggle: boolean) => void
updateDevSettings: (settings: DevSettings) => void
updateHandoff: (handoff: boolean) => void
updateLastMessageDate: (date: string) => void
updateLastRoutePath: (path: string) => void
updateLatestInput: (input: ClientInput) => void
updateMessage: (message: WebchatMessage) => void
updateReplies: (replies: any) => void
updateSession: (session: Partial<Session>) => void
updateTheme: (theme: ThemeProps, themeUpdates?: ThemeProps) => void
updateTyping: (typing: boolean) => void
updateWebview: (webview: Webview, params: Record<string, string>) => void
removeWebview: () => void
webchatState: WebchatState
webchatRef: React.MutableRefObject<HTMLDivElement | null> // TODO: Change name, already exists WebchatRef for useImperativeHandle
headerRef: React.MutableRefObject<HTMLDivElement | null>
chatAreaRef: React.MutableRefObject<HTMLDivElement | null>
scrollableMessagesListRef: React.MutableRefObject<HTMLDivElement | null>
repliesRef: React.MutableRefObject<HTMLDivElement | null>
inputPanelRef: React.MutableRefObject<HTMLDivElement | null>
}

export function useWebchat(): UseWebchat {
const [webchatState, webchatDispatch] = useReducer(
webchatReducer,
webchatInitialState
Expand Down Expand Up @@ -87,7 +123,12 @@ export function useWebchat() {
payload: { webview, webviewParams: params },
})

const updateSession = (session: Session) => {
const removeWebview = () =>
webchatDispatch({
type: WebchatAction.REMOVE_WEBVIEW,
})

const updateSession = (session: Partial<Session>) => {
webchatDispatch({
type: WebchatAction.UPDATE_SESSION,
payload: session,
Expand Down Expand Up @@ -152,7 +193,7 @@ export function useWebchat() {
payload: toggle,
})

const setError = (error: ErrorMessage) =>
const setError = (error?: ErrorMessage) =>
webchatDispatch({
type: WebchatAction.SET_ERROR,
payload: error,
Expand All @@ -177,7 +218,7 @@ export function useWebchat() {
})
}

const setCurrentAttachment = (attachment: File) => {
const setCurrentAttachment = (attachment?: File) => {
webchatDispatch({
type: WebchatAction.SET_CURRENT_ATTACHMENT,
payload: attachment,
Expand Down Expand Up @@ -230,7 +271,7 @@ export function useWebchat() {
updateTheme,
updateTyping,
updateWebview,
webchatDispatch,
removeWebview,
webchatState,
webchatRef,
headerRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ConditionalAnimation } from '../components/conditional-animation'

interface AttachmentProps {
accept: string
enableAttachments: boolean
enableAttachments?: boolean
onChange: (event: any) => void
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Icon } from '../components/common'
import { ConditionalAnimation } from '../components/conditional-animation'

interface EmojiPickerProps {
enableEmojiPicker: boolean
enableEmojiPicker?: boolean
onClick: () => void
}

Expand Down
12 changes: 8 additions & 4 deletions packages/botonic-react/src/webchat/input-panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import { Textarea } from './textarea'

interface InputPanelProps {
persistentMenu: any
enableEmojiPicker: boolean
enableAttachments: boolean
enableEmojiPicker?: boolean
enableAttachments?: boolean
handleAttachment: (event: any) => void
textareaRef: React.MutableRefObject<HTMLTextAreaElement>
textareaRef: React.MutableRefObject<HTMLTextAreaElement | undefined>
host: HTMLElement
onUserInput?: (event: any) => Promise<void>
}
Expand All @@ -43,6 +43,8 @@ export const InputPanel = ({
} = useContext(WebchatContext)

const handleSelectedEmoji = event => {
if (!textareaRef.current) return

textareaRef.current.value += event.emoji
textareaRef.current.focus()
}
Expand All @@ -56,7 +58,9 @@ export const InputPanel = ({
}

const sendTextAreaText = async () => {
await sendText(textareaRef.current.value)
if (!textareaRef.current) return

await sendText(textareaRef.current?.value)
textareaRef.current.value = ''
}

Expand Down
4 changes: 3 additions & 1 deletion packages/botonic-react/src/webchat/input-panel/textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { TextAreaContainer } from './styles'
interface TextareaProps {
host: HTMLElement
persistentMenu: PersistentMenuTheme
textareaRef: React.MutableRefObject<HTMLTextAreaElement>
textareaRef: React.MutableRefObject<HTMLTextAreaElement | undefined>
sendChatEvent: (event: string) => Promise<void>
sendTextAreaText: () => Promise<void>
}
Expand Down Expand Up @@ -45,6 +45,8 @@ export const Textarea = ({
}

const onKeyUp = () => {
if (!textareaRef.current) return

if (textareaRef.current.value === '') {
stopTyping()
return
Expand Down

0 comments on commit 239c6f4

Please sign in to comment.