Skip to content

Commit

Permalink
Merge pull request #281 from jucallej/hubspot
Browse files Browse the repository at this point in the history
  • Loading branch information
robmorieson committed Jan 9, 2024
2 parents d8b52f8 + 29ff383 commit e9c9537
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 1 deletion.
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ You can pass the following props to the `LiveChatLoaderProvider` provider:

## 💬 Supported Providers

Currently there are six supported providers:
Currently there are seven supported providers:

<details>
<summary id="help-scout">Help Scout</summary>
Expand Down Expand Up @@ -347,6 +347,37 @@ You can customise the Chatwoot placeholder by passing the following props to the

</details>

<details>
<summary id="hubspot">Hubspot</summary>

To use Hubspot import the `LiveChatLoaderProvider` and set the `provider` prop
as `hubSpot` and the `providerKey` prop as your Hubspot API Key.

Then import the `Hubspot` component.

```jsx
import { LiveChatLoaderProvider, Hubspot } from 'react-live-chat-loader'

export default class App extends React.Component {
render() {
return (
<LiveChatLoaderProvider providerKey="asdjkasl123123" provider="hubspot">
/* ... */
<Hubspot />
</LiveChatLoaderProvider>
)
}
}
```

You can customise the Hubspot placeholder by passing the following props to the
`Hubspot` component:

- `backgroundColor`: The background color of the placeholder
- `loader`: A react component shown while the Hubspot libraries are loading

</details>

## ➕ Adding a Provider

To add a new live chat provider, follow the steps in [Contributing: Adding a Provider](CONTRIBUTING.md#-adding-a-provider).
Expand Down
95 changes: 95 additions & 0 deletions src/components/HubSpot/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, {
ReactElement,
useState
} from 'react'
import useChat from '../../hooks/useChat'
const Icon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="30" viewBox="0 0 39 37"><g fill="none"><path fill="#fff" d="M37.65 4.303c-.314-1.943-1.486-3.113-3.374-3.38C34.23.912 29.632.03 22.416.03c-7.214 0-11.812.882-11.843.889-1.477.21-2.507.967-3.042 2.192a83.103 83.103 0 0 1 9.312-.503c6.994 0 11.647.804 12.33.93 3.079.462 5.22 2.598 5.738 5.728.224 1.02.932 4.606.932 8.887 0 2.292-.206 4.395-.428 6.002 1.22-.516 1.988-1.55 2.23-3.044.008-.037.893-3.814.893-8.415 0-4.6-.885-8.377-.89-8.394"/><path fill="#fff" d="M32.077 9.76c-.314-1.944-1.487-3.114-3.374-3.382-.046-.01-4.644-.89-11.859-.89-7.214 0-11.813.88-11.843.888-1.903.27-3.075 1.44-3.384 3.363-.01.037-.894 3.814-.894 8.415 0 4.6.884 8.377.889 8.393.314 1.944 1.486 3.114 3.374 3.382.037.007 3.02.578 7.933.801l2.928 5.072a1.151 1.151 0 0 0 1.994 0l2.929-5.071c4.913-.224 7.893-.794 7.918-.8 1.902-.27 3.075-1.44 3.384-3.363.01-.037.893-3.814.893-8.414 0-4.601-.884-8.378-.888-8.394"/></g></svg>
)

const HubSpot = ({
backgroundColor,
loader: Loader
}: {
backgroundColor?: string
loader?: ReactElement
}): JSX.Element | null => {
const [state, loadChat] = useChat({ loadWhenIdle: true })
const [isLoading, setIsLoading] = useState(false)
if (state === 'complete') {
return null
}
return (
<div
style={{
zIndex: 1051, // 1 more than the actual widget
paddingBottom: '16px',
position: 'fixed',
bottom: 0,
paddingLeft: '0px',
paddingRight: '16px',
left: 'inherit',
right: '0px'
}}
>
<span
style={{
display: 'flex !important',
paddingLeft: '24px !important',
paddingTop: '20px !important',
float: 'right'
}}
>
<div
style={{
position: 'relative',
display: 'inline-flex',
alignItems: 'baseline',
lineHeight: 1
}}
>
<button
onClick={() => {
setIsLoading(true);
loadChat({ open: true });
}}
aria-label="Open live chat"
aria-haspopup="false"
style={{
fontWeight: 400,
fontSize: '14px',
color: 'rgb(51, 71, 91)',
backgroundColor: backgroundColor || 'rgb(66, 91, 118)',
boxShadow:
'rgba(0, 0, 0, 0.1) 0px 1px 6px, rgba(0, 0, 0, 0.2) 0px 2px 24px',
border: 'medium none',
transition: 'box-shadow 150ms ease-in-out 0s',
position: 'relative',
borderRadius: '50%',
height: '60px',
width: '60px',
cursor: 'pointer'
}}
>
<div
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
display: 'flex',
width: '32px',
height: '30px',
lineHeight: 1
}}
>
{isLoading && Loader ? Loader : <Icon />}
</div>
</button>
</div>
</span>
</div>
)
}

export default HubSpot
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export { default as Intercom } from './components/Intercom'
export { default as Messenger } from './components/Messenger'
export { default as Userlike } from './components/Userlike'
export { default as Chatwoot } from './components/Chatwoot'
export { default as HubSpot } from './components/HubSpot'
export { default as LiveChatLoaderProvider } from './components/LiveChatLoaderProvider'
101 changes: 101 additions & 0 deletions src/providers/hubSpot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { State } from '../types'
import waitForLoad from '../utils/waitForLoad'

export const domain = 'js.hs-scripts.com'
declare global {
interface Window {
//eslint-disable-next-line @typescript-eslint/no-explicit-any
HubSpotConversations: any
//eslint-disable-next-line @typescript-eslint/no-explicit-any
hsConversationsSettings: any
//eslint-disable-next-line @typescript-eslint/no-explicit-any
hsConversationsOnReady: any
}
}

const isHubspotWidgetDefined = () => window.HubSpotConversations && window.HubSpotConversations.widget;

const loadScript = (hsId: string) => {
// Detect the provider is already loaded and return early
if (window.HubSpotConversations) {
return false
}
(function loadHubSpotSDK(d, s, id) {
// fetch customerchat.js
const fjs = d.getElementsByTagName(s)[0]
if (d.getElementById(id)) {
return
}
//eslint-disable-next-line @typescript-eslint/no-explicit-any
const js = d.createElement(s) as any
js.id = id
js.src = `https://${domain}/${hsId}.js`
js.type = 'text/javascript'
js.async = 1
js.defer = 1
if (fjs) {
fjs.parentNode?.insertBefore(js, fjs)
} else {
d.body.appendChild(js)
}
})(window.document, 'script', 'hs-script-loader')

return true
}

const load = ({
providerKey,
setState,
beforeInit = () => undefined,
onReady = () => undefined,
}: {
providerKey: string
setState: (state: State) => void
beforeInit?: () => void
onReady?: () => void
}): boolean => {
window.hsConversationsOnReady = [
() => {
isHubspotWidgetDefined() && window.HubSpotConversations.widget.load()
}
]
const loaded = loadScript(providerKey)
if (loaded) {
beforeInit()

waitForLoad(
() => {
return Boolean(
isHubspotWidgetDefined() &&
window.HubSpotConversations.widget.status().loaded
)
},
// Allow hubspot to complete loading before removing fake widget
() => {
isHubspotWidgetDefined() && window.HubSpotConversations.widget.open()
setState('complete')
onReady()
}
)
}
return loaded
}

const open = (): unknown => {
isHubspotWidgetDefined() &&
!window.HubSpotConversations.widget.status().loaded &&
window.HubSpotConversations.widget.load()

return (
isHubspotWidgetDefined() &&
window.HubSpotConversations.widget.open()
)
} // Open provider
const close = (): unknown => isHubspotWidgetDefined() && window.HubSpotConversations.widget.close() // Close provider

export default {
load,
open,
close,
domain
}
1 change: 1 addition & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { default as intercom } from './intercom'
export { default as messenger } from './messenger'
export { default as userlike } from './userlike'
export { default as chatwoot} from './chatwoot'
export { default as hubSpot } from './hubSpot'
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export type Provider =
| 'drift'
| 'userlike'
| 'chatwoot'
| 'hubSpot'
5 changes: 5 additions & 0 deletions website/components/exampleLinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ const ExampleLinks = () => (
<a>Chatwoot</a>
</Link>
</li>
<li>
<Link href="/hubspot">
<a>HubSpot</a>
</Link>
</li>
</ul>
)

Expand Down
33 changes: 33 additions & 0 deletions website/pages/hubspot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'
import { LiveChatLoaderProvider, HubSpot } from 'react-live-chat-loader'
import type { NextPage } from 'next'

import Layout from '../layouts/main'
import ExampleLinks from '../components/exampleLinks'

const Page: NextPage = () => (
<LiveChatLoaderProvider
provider="hubSpot"
providerKey=""
>
<Layout title="React Live Chat Loader: Hubspot">
<div className="wrapper">
<div className="inner">
<h1>React Live Chat Loader: Hubspot</h1>
<p>
This is an example implementation of the Hubspot widget using{' '}
<a href="https://github.com/calibreapp/react-live-chat-loader">
react-live-chat-loader
</a>
.
</p>
<p>View other demos:</p>
<ExampleLinks />
</div>
</div>
<HubSpot backgroundColor='#017848'/>
</Layout>
</LiveChatLoaderProvider>
)

export default Page

1 comment on commit e9c9537

@vercel
Copy link

@vercel vercel bot commented on e9c9537 Jan 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.