Skip to content

Commit

Permalink
✨ Add Bubble and Popup in embed lib v2
Browse files Browse the repository at this point in the history
Closes #214
  • Loading branch information
baptisteArno committed Jan 9, 2023
1 parent 4bf93b4 commit 21f1c7a
Show file tree
Hide file tree
Showing 38 changed files with 1,586 additions and 96 deletions.
8 changes: 3 additions & 5 deletions apps/viewer/src/components/TypebotPageV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
getExistingResultFromSession,
setResultInSession,
} from '@/utils/sessionStorage'
import Bot from '@typebot.io/react'
import { Standard } from '@typebot.io/react'
import { BackgroundType, InitialChatReply, Typebot } from 'models'
import { useRouter } from 'next/router'
import { useCallback, useEffect, useState } from 'react'
Expand Down Expand Up @@ -38,6 +38,7 @@ export const TypebotPageV2 = ({ url, typebot }: TypebotPageV2Props) => {
}, [asPath, push, typebot.settings.general.isHideQueryParamsEnabled])

useEffect(() => {
console.log(open)
clearQueryParamsIfNecessary()
}, [clearQueryParamsIfNecessary])

Expand Down Expand Up @@ -89,10 +90,7 @@ export const TypebotPageV2 = ({ url, typebot }: TypebotPageV2Props) => {
metadata={typebot.settings.metadata}
/>
{initialChatReply && (
<Bot.Standard
typebotId={typebot.id}
initialChatReply={initialChatReply}
/>
<Standard typebotId={typebot.id} initialChatReply={initialChatReply} />
)}
</div>
)
Expand Down
6 changes: 5 additions & 1 deletion apps/viewer/src/pages/api/v1/[...trpc].ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { NextApiRequest, NextApiResponse } from 'next'

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await cors(req, res, {
origin: ['https://docs.typebot.io', 'http://localhost:3005'],
origin: [
'https://docs.typebot.io',
'http://localhost:3005',
'http://localhost:3006',
],
})

return createOpenApiNextHandler({
Expand Down
1 change: 0 additions & 1 deletion packages/bot-engine/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './components/TypebotViewer'
export { parseVariables } from '@/features/variables'
export * from 'util'
8 changes: 8 additions & 0 deletions packages/db/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
export * from '@prisma/client'

// Named export for enums to avoid vite barrel export bug (https://github.com/nrwl/nx/issues/13704)
export {
Plan,
WorkspaceRole,
GraphNavigation,
CollaborationType,
} from '@prisma/client'
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const AvatarSideContainer = (props: Props) => {
<div
ref={avatarContainer}
class={
'flex w-10 mr-2 mb-2 flex-shrink-0 items-center relative typebot-avatar-container ' +
'flex mr-2 mb-2 flex-shrink-0 items-center relative typebot-avatar-container ' +
(isMobile() ? 'w-6' : 'w-10')
}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const ConversationContainer = (props: Props) => {
}

return (
<div class="overflow-y-scroll w-full lg:w-3/4 min-h-full rounded lg:px-5 px-3 pt-10 relative scrollable-container typebot-chat-view">
<div class="overflow-y-scroll w-full min-h-full rounded px-3 pt-10 relative scrollable-container typebot-chat-view">
<For each={chatChunks()}>
{(chatChunk, index) => (
<ChatChunk
Expand Down
154 changes: 115 additions & 39 deletions packages/js/src/features/bubble/components/Bubble.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,139 @@
import styles from '../../../assets/index.css'
import { createSignal } from 'solid-js'
import { createSignal, onMount, Show, splitProps, onCleanup } from 'solid-js'
import { Bot, BotProps } from '../../../components/Bot'
import { CommandData } from '@/features/commands'
import { BubbleButton } from './BubbleButton'
import { PreviewMessage, PreviewMessageProps } from './PreviewMessage'
import { isDefined } from 'utils'
import { BubbleParams } from '../types'

export type BubbleProps = BotProps &
BubbleParams & {
onOpen?: () => void
onClose?: () => void
onPreviewMessageClick?: () => void
}

export const Bubble = (props: BubbleProps) => {
const [bubbleProps, botProps] = splitProps(props, [
'onOpen',
'onClose',
'previewMessage',
'onPreviewMessageClick',
'button',
])
const [prefilledVariables, setPrefilledVariables] = createSignal(
// eslint-disable-next-line solid/reactivity
botProps.prefilledVariables
)
const [isPreviewMessageDisplayed, setIsPreviewMessageDisplayed] =
createSignal(false)
const [previewMessage, setPreviewMessage] = createSignal<
Pick<PreviewMessageProps, 'avatarUrl' | 'message'>
>({
message: bubbleProps.previewMessage?.message ?? '',
avatarUrl: bubbleProps.previewMessage?.avatarUrl,
})

export const Bubble = () => {
const [isBotOpened, setIsBotOpened] = createSignal(false)
const [isBotStarted, setIsBotStarted] = createSignal(false)

onMount(() => {
window.addEventListener('message', processIncomingEvent)
const autoShowDelay = bubbleProps.previewMessage?.autoShowDelay
if (isDefined(autoShowDelay)) {
setTimeout(() => {
showMessage()
}, autoShowDelay)
}
})

onCleanup(() => {
window.removeEventListener('message', processIncomingEvent)
})

const processIncomingEvent = (event: MessageEvent<CommandData>) => {
const { data } = event
if (!data.isFromTypebot) return
if (data.command === 'open') openBot()
if (data.command === 'close') closeBot()
if (data.command === 'toggle') toggleBot()
if (data.command === 'showPreviewMessage') showMessage(data.message)
if (data.command === 'hidePreviewMessage') hideMessage()
if (data.command === 'setPrefilledVariables')
setPrefilledVariables((existingPrefilledVariables) => ({
...existingPrefilledVariables,
...data.variables,
}))
}

const openBot = () => {
if (!isBotStarted()) setIsBotStarted(true)
hideMessage()
setIsBotOpened(true)
if (isBotOpened()) bubbleProps.onOpen?.()
}

const closeBot = () => {
setIsBotOpened(false)
if (isBotOpened()) bubbleProps.onClose?.()
}

const toggleBot = () => {
setIsBotOpened(!isBotOpened())
isBotOpened() ? closeBot() : openBot()
}

const handlePreviewMessageClick = () => {
bubbleProps.onPreviewMessageClick?.()
openBot()
}

const showMessage = (
previewMessage?: Pick<PreviewMessageProps, 'avatarUrl' | 'message'>
) => {
if (previewMessage) setPreviewMessage(previewMessage)
if (isBotOpened()) return
setIsPreviewMessageDisplayed(true)
}

const hideMessage = () => {
setIsPreviewMessageDisplayed(false)
}

return (
<>
<style>{styles}</style>
<button
onClick={toggleBot}
class="bg-blue-500 text-red-300 absolute bottom-4 right-4 w-12 h-12 rounded-full hover:scale-110 active:scale-95 transition-transform duration-200 flex justify-center items-center"
>
<svg
viewBox="0 0 24 24"
style={{ transition: 'transform 200ms, opacity 200ms' }}
class={
'w-7 stroke-white stroke-2 fill-transparent absolute ' +
(isBotOpened() ? 'scale-0 opacity-0' : 'scale-100 opacity-100')
}
>
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
</svg>
<svg
viewBox="0 0 24 24"
style={{ transition: 'transform 200ms, opacity 200ms' }}
class={
'w-7 fill-white absolute ' +
(isBotOpened()
? 'scale-100 rotate-0 opacity-100'
: 'scale-0 -rotate-180 opacity-0')
}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18.601 8.39897C18.269 8.06702 17.7309 8.06702 17.3989 8.39897L12 13.7979L6.60099 8.39897C6.26904 8.06702 5.73086 8.06702 5.39891 8.39897C5.06696 8.73091 5.06696 9.2691 5.39891 9.60105L11.3989 15.601C11.7309 15.933 12.269 15.933 12.601 15.601L18.601 9.60105C18.9329 9.2691 18.9329 8.73091 18.601 8.39897Z"
/>
</svg>
</button>
<Show when={isPreviewMessageDisplayed()}>
<PreviewMessage
{...previewMessage()}
button={bubbleProps.button}
onClick={handlePreviewMessageClick}
onCloseClick={hideMessage}
/>
</Show>
<BubbleButton
{...bubbleProps.button}
toggleBot={toggleBot}
isBotOpened={isBotOpened()}
/>
<div
style={{
width: '400px',
height: 'calc(100% - 104px)',
'max-height': '704px',
height: 'calc(100% - 80px)',
transition:
'transform 200ms cubic-bezier(0, 1.2, 1, 1), opacity 150ms ease-out',
'transform-origin': 'bottom right',
transform: isBotOpened() ? 'scale3d(1, 1, 1)' : 'scale3d(0, 0, 1)',
'box-shadow': 'rgb(0 0 0 / 16%) 0px 5px 40px',
}}
class={
'absolute bottom-20 right-4 rounded-2xl ' +
'absolute bottom-20 sm:right-4 rounded-lg bg-white w-full sm:w-[400px] max-h-[704px] ' +
(isBotOpened() ? 'opacity-1' : 'opacity-0 pointer-events-none')
}
/>
>
<Show when={isBotStarted()}>
<Bot {...botProps} prefilledVariables={prefilledVariables()} />
</Show>
</div>
</>
)
}
67 changes: 67 additions & 0 deletions packages/js/src/features/bubble/components/BubbleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Show } from 'solid-js'
import { ButtonParams } from '../types'

type Props = ButtonParams & {
isBotOpened: boolean
toggleBot: () => void
}

const defaultButtonColor = '#0042DA'

export const BubbleButton = (props: Props) => {
return (
<button
// eslint-disable-next-line solid/reactivity
onClick={props.toggleBot}
class={
'absolute bottom-4 right-4 shadow-md w-12 h-12 rounded-full hover:scale-110 active:scale-95 transition-transform duration-200 flex justify-center items-center animate-fade-in'
}
style={{
'background-color': props.backgroundColor ?? defaultButtonColor,
}}
>
<Show when={props.icon?.color} keyed>
{(color) => (
<svg
viewBox="0 0 24 24"
style={{
stroke: color,
}}
class={
`w-7 stroke-2 fill-transparent absolute duration-200 transition ` +
(props.isBotOpened
? 'scale-0 opacity-0'
: 'scale-100 opacity-100')
}
>
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
</svg>
)}
</Show>
<Show when={props.icon?.url}>
<img
src={props.icon?.url}
class="w-7 h-7 rounded-full object-cover"
alt="Bubble button icon"
/>
</Show>

<svg
viewBox="0 0 24 24"
style={{ fill: props.icon?.color ?? 'white' }}
class={
`w-7 absolute duration-200 transition ` +
(props.isBotOpened
? 'scale-100 rotate-0 opacity-100'
: 'scale-0 -rotate-180 opacity-0')
}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18.601 8.39897C18.269 8.06702 17.7309 8.06702 17.3989 8.39897L12 13.7979L6.60099 8.39897C6.26904 8.06702 5.73086 8.06702 5.39891 8.39897C5.06696 8.73091 5.06696 9.2691 5.39891 9.60105L11.3989 15.601C11.7309 15.933 12.269 15.933 12.601 15.601L18.601 9.60105C18.9329 9.2691 18.9329 8.73091 18.601 8.39897Z"
/>
</svg>
</button>
)
}
68 changes: 68 additions & 0 deletions packages/js/src/features/bubble/components/PreviewMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { createSignal } from 'solid-js'
import { BubbleParams, PreviewMessageParams } from '../types'

export type PreviewMessageProps = Pick<
PreviewMessageParams,
'avatarUrl' | 'message' | 'style'
> &
Pick<BubbleParams, 'button'> & {
onClick: () => void
onCloseClick: () => void
}

const defaultFontFamily =
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'"

export const PreviewMessage = (props: PreviewMessageProps) => {
const [isPreviewMessageHovered, setIsPreviewMessageHovered] =
createSignal(false)

return (
<div
// eslint-disable-next-line solid/reactivity
onClick={props.onClick}
class="absolute bottom-20 right-4 w-64 rounded-md duration-200 flex items-center gap-4 shadow-md animate-fade-in cursor-pointer hover:shadow-lg p-4"
style={{
'font-family': props.style?.fontFamily ?? defaultFontFamily,
'background-color': props.style?.backgroundColor ?? '#F7F8FF',
color: props.style?.color ?? '#303235',
}}
onMouseEnter={() => setIsPreviewMessageHovered(true)}
onMouseLeave={() => setIsPreviewMessageHovered(false)}
>
<button
class={
`absolute -top-3 -right-3 rounded-full w-6 h-6 p-1 hover:brightness-95 active:brightness-90 transition-all ` +
(isPreviewMessageHovered() ? 'opacity-100' : 'opacity-0')
}
onClick={(e) => {
e.stopPropagation()
return props.onCloseClick()
}}
style={{
'background-color': props.style?.closeButtonBgColor ?? '#F7F8FF',
color: props.style?.closeButtonColor ?? '#303235',
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
<img
src={props.avatarUrl}
class="rounded-full w-8 h-8 object-cover"
alt="Bot avatar"
/>
<p>{props.message}</p>
</div>
)
}
Loading

5 comments on commit 21f1c7a

@vercel
Copy link

@vercel vercel bot commented on 21f1c7a Jan 9, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

docs – ./apps/docs

docs.typebot.io
docs-git-main-typebot-io.vercel.app
docs-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 21f1c7a Jan 9, 2023

Choose a reason for hiding this comment

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

@vercel
Copy link

@vercel vercel bot commented on 21f1c7a Jan 9, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

viewer-v2 – ./apps/viewer

1stop.au
aginap.com
nepkit.com
bot.joof.it
bull.cr8.ai
cinecorn.com
snake.cr8.ai
yoda.riku.ai
bergamo.store
bot.tvbeat.it
app.yvon.earth
bots.bridge.ai
chat.hayuri.id
gollum.riku.ai
talk.gocare.io
bot.jesopizz.it
fitness.riku.ai
bot.contakit.com
zap.fundviser.in
bot.rihabilita.it
viewer.typebot.io
bot.danyservice.it
digitalhelp.com.au
bot.boston-voip.com
bot.dsignagency.com
chatbot.matthesv.de
demo.wemakebots.xyz
88584434.therpm.club
92109660.therpm.club
bot.barrettamario.it
hello.advergreen.com
bot.coachayongzul.com
bot.digitalpointer.id
bot.eikju.photography
bot.outstandbrand.com
bot.robertohairlab.it
criar.somaperuzzo.com
bot.ilmuseoaiborghi.it
bot.pratikmandalia.com
form.bridesquadapp.com
michaeljackson.riku.ai
87656003.actualizar.xyz
88152257.actualizar.xyz
91375310.actualizar.xyz
arrivalx2.wpwakanda.com
bot.hotelplayarimini.it
link.venturasuceder.com
manualhandlingcourse.ie
invite.bridesquadapp.com
bot.amicidisanfaustino.it
chat.thehomebuyersusa.com
forms.hiabhaykulkarni.com
healthandsafetycourses.uk
typebot-viewer.vercel.app
bot.adventureconsulting.hu
casestudyemb.wpwakanda.com
chat.atlasoutfittersk9.com
herbalife.barrettamario.it
homepageonly.wpwakanda.com
liveconvert.kandalearn.com
mainmenu1one.wpwakanda.com
tarian.theiofoundation.org
bot.pinpointinteractive.com
bot.polychromes-project.com
bot.seidinembroseanchetu.it
liveconvert2.kandalearn.com
bot.seidibergamoseanchetu.it
desabafe.sergiolimajr.com.br
forms.escoladeautomacao.com.br
viewer-v2-typebot-io.vercel.app
gerador.verificadordehospedes.com
bot.studiotecnicoimmobiliaremerelli.it
viewer-v2-git-main-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 21f1c7a Jan 9, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

app.typebot.io
builder-v2-typebot-io.vercel.app
builder-v2-git-main-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 21f1c7a Jan 9, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

viewer-v2-alpha – ./apps/viewer

ns8.vn
yobot.me
247987.com
8jours.top
bee.cr8.ai
bot.aws.bj
bot.bbc.bj
cat.cr8.ai
finplex.be
pig.cr8.ai
sat.cr8.ai
bot.aipr.kr
docs.cr8.ai
minipost.uk
mole.cr8.ai
wolf.cr8.ai
bot.artiweb.app
bot.devitus.com
bot.tc-mail.com
chat.sureb4.com
eventhub.com.au
games.klujo.com
sakuranembro.it
typebot.aloe.do
bot.piccinato.co
bot.sv-energy.it
botc.ceox.com.br
clo.closeer.work
cockroach.cr8.ai
faqs.nigerias.io
feedback.ofx.one
form.syncwin.com
kw.wpwakanda.com
myrentalhost.com
stan.vselise.com
start.taxtree.io
typebot.aloe.bot
voicehelp.cr8.ai
app.chatforms.net
bot.agfunnel.tech
bot.hostnation.de
bot.maitempah.com
bot.phuonghub.com
bot.reviewzer.com
cares.urlabout.me
fmm.wpwakanda.com
gentleman-shop.fr
k1.kandabrand.com
lb.ticketfute.com
ov1.wpwakanda.com
ov2.wpwakanda.com
ov3.wpwakanda.com
1988.bouclidom.com
andreimayer.com.br
bot.megafox.com.br
bot.neferlopez.com
bots.robomotion.io
cadu.uninta.edu.br
dicanatural.online
goalsettingbot.com
bot.cabinrentalagency.com
bot.fusionstarreviews.com
boyfriend-breakup.riku.ai
brigadeirosemdrama.com.br
chat.ertcrebateportal.com
chat.thisiscrushhouse.com
sellmyharleylouisiana.com
verfica.botmachine.com.br
configurator.bouclidom.com
help.atlasoutfittersk9.com
ted.meujalecobrasil.com.br
type.dericsoncalari.com.br
chatbot.berbelanjabiz.trade
designguide.techyscouts.com
presente.empresarias.com.mx
sell.sellthemotorhome.co.uk
anamnese.odontopavani.com.br
austin.channelautomation.com
bot.marketingplusmindset.com
piazzatorre.barrettamario.it
requests.swamprecordsgnv.com
type.cookieacademyonline.com
bot.brigadeirosemdrama.com.br
onboarding.libertydreamcare.ie
type.talitasouzamarques.com.br
agendamento.sergiolimajr.com.br
anamnese.clinicamegasjdr.com.br
bookings.littlepartymonkeys.com
bot.comercializadoraomicron.com
elevateyourmind.groovepages.com
yourfeedback.comebackreward.com
personal-trainer.barrettamario.it
preagendamento.sergiolimajr.com.br
studiotecnicoimmobiliaremerelli.it
download.thailandmicespecialist.com
register.thailandmicespecialist.com
viewer-v2-alpha-typebot-io.vercel.app
pesquisa.escolamodacomproposito.com.br
anamnese.clinicaramosodontologia.com.br
viewer-v2-alpha-git-main-typebot-io.vercel.app

Please sign in to comment.