Skip to content

Commit

Permalink
🚸 (bot) Show a popup when the redirect is blocked by browser
Browse files Browse the repository at this point in the history
Allows us to show a link button to redirect the user anyway
  • Loading branch information
baptisteArno committed Feb 20, 2023
1 parent e6ec84b commit b2d1235
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 34 deletions.
50 changes: 35 additions & 15 deletions packages/bot-engine/src/components/ChatGroup/ChatGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { getLastChatBlockType } from '@/utils/chat'
import { executeIntegration } from '@/utils/executeIntegration'
import { executeLogic } from '@/utils/executeLogic'
import { blockCanBeRetried, parseRetryBlock } from '@/utils/inputs'
import { PopupBlockedToast } from '../PopupBlockedToast'

type ChatGroupProps = {
blocks: Block[]
Expand Down Expand Up @@ -72,6 +73,7 @@ export const ChatGroup = ({
const { scroll } = useChat()
const [processedBlocks, setProcessedBlocks] = useState<Block[]>([])
const [displayedChunks, setDisplayedChunks] = useState<ChatDisplayChunk[]>([])
const [blockedPopupUrl, setBlockedPopupUrl] = useState<string>()

const insertBlockInStack = (nextBlock: Block) => {
setProcessedBlocks([...processedBlocks, nextBlock])
Expand Down Expand Up @@ -120,21 +122,25 @@ export const ChatGroup = ({
const currentBlock = [...processedBlocks].pop()
if (!currentBlock) return
if (isLogicBlock(currentBlock)) {
const { nextEdgeId, linkedTypebot } = await executeLogic(currentBlock, {
isPreview,
apiHost,
typebot,
linkedTypebots,
updateVariableValue,
updateVariables,
injectLinkedTypebot,
onNewLog,
createEdge,
setCurrentTypebotId,
pushEdgeIdInLinkedTypebotQueue,
currentTypebotId,
pushParentTypebotId,
})
const { nextEdgeId, linkedTypebot, blockedPopupUrl } = await executeLogic(
currentBlock,
{
isPreview,
apiHost,
typebot,
linkedTypebots,
updateVariableValue,
updateVariables,
injectLinkedTypebot,
onNewLog,
createEdge,
setCurrentTypebotId,
pushEdgeIdInLinkedTypebotQueue,
currentTypebotId,
pushParentTypebotId,
}
)
if (blockedPopupUrl) setBlockedPopupUrl(blockedPopupUrl)
const isRedirecting =
currentBlock.type === LogicBlockType.REDIRECT &&
currentBlock.options.isNewTab === false
Expand Down Expand Up @@ -224,6 +230,8 @@ export const ChatGroup = ({
hasGuestAvatar={typebot.theme.chat.guestAvatar?.isEnabled ?? false}
onDisplayNextBlock={displayNextBlock}
keepShowingHostAvatar={keepShowingHostAvatar}
blockedPopupUrl={blockedPopupUrl}
onBlockedPopupLinkClick={() => setBlockedPopupUrl(undefined)}
/>
))}
</div>
Expand All @@ -236,6 +244,8 @@ type Props = {
hostAvatar: { isEnabled: boolean; src?: string }
hasGuestAvatar: boolean
keepShowingHostAvatar: boolean
blockedPopupUrl?: string
onBlockedPopupLinkClick: () => void
onDisplayNextBlock: (
answerContent?: InputSubmitContent,
isRetry?: boolean
Expand All @@ -246,6 +256,8 @@ const ChatChunks = ({
hostAvatar,
hasGuestAvatar,
keepShowingHostAvatar,
blockedPopupUrl,
onBlockedPopupLinkClick,
onDisplayNextBlock,
}: Props) => {
const [isSkipped, setIsSkipped] = useState(false)
Expand Down Expand Up @@ -320,6 +332,14 @@ const ChatChunks = ({
)}
</CSSTransition>
)}
{blockedPopupUrl ? (
<div className="flex justify-end">
<PopupBlockedToast
url={blockedPopupUrl}
onLinkClick={onBlockedPopupLinkClick}
/>
</div>
) : null}
</>
)
}
30 changes: 30 additions & 0 deletions packages/bot-engine/src/components/PopupBlockedToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
type Props = {
url: string
onLinkClick: () => void
}

export const PopupBlockedToast = ({ url, onLinkClick }: Props) => {
return (
<div
className="w-full max-w-xs p-4 text-gray-500 bg-white rounded-lg shadow flex flex-col gap-2"
role="alert"
>
<span className="mb-1 text-sm font-semibold text-gray-900">
Popup blocked
</span>
<div className="mb-2 text-sm font-normal">
The bot wants to open a new tab but it was blocked by your broswer. It
needs a manual approval.
</div>
<a
href={url}
target="_blank"
className="py-1 px-4 justify-center text-sm font-semibold rounded-md text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button"
rel="noreferrer"
onClick={onLinkClick}
>
Continue in new tab
</a>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,33 @@ import { sanitizeUrl } from 'utils'
export const executeRedirect = (
block: RedirectBlock,
{ typebot: { variables } }: LogicState
): EdgeId | undefined => {
if (!block.options?.url) return block.outgoingEdgeId
): {
nextEdgeId?: EdgeId
blockedPopupUrl?: string
} => {
if (!block.options?.url) return { nextEdgeId: block.outgoingEdgeId }
const formattedUrl = sanitizeUrl(parseVariables(variables)(block.options.url))
const isEmbedded = window.parent && window.location !== window.top?.location
let newWindow: Window | null = null
if (isEmbedded) {
if (!block.options.isNewTab)
return ((window.top as Window).location.href = formattedUrl)
if (!block.options.isNewTab) {
;(window.top as Window).location.href = formattedUrl
return { nextEdgeId: block.outgoingEdgeId }
}

try {
window.open(formattedUrl)
newWindow = window.open(formattedUrl)
} catch (err) {
sendEventToParent({ redirectUrl: formattedUrl })
}
} else {
window.open(formattedUrl, block.options.isNewTab ? '_blank' : '_self')
newWindow = window.open(
formattedUrl,
block.options.isNewTab ? '_blank' : '_self'
)
}
return {
nextEdgeId: block.outgoingEdgeId,
blockedPopupUrl: newWindow ? undefined : formattedUrl,
}
return block.outgoingEdgeId
}
3 changes: 2 additions & 1 deletion packages/bot-engine/src/utils/executeLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ export const executeLogic = async (
): Promise<{
nextEdgeId?: EdgeId
linkedTypebot?: TypebotViewerProps['typebot'] | LinkedTypebot
blockedPopupUrl?: string
}> => {
switch (block.type) {
case LogicBlockType.SET_VARIABLE:
return { nextEdgeId: executeSetVariable(block, context) }
case LogicBlockType.CONDITION:
return { nextEdgeId: executeCondition(block, context) }
case LogicBlockType.REDIRECT:
return { nextEdgeId: executeRedirect(block, context) }
return executeRedirect(block, context)
case LogicBlockType.SCRIPT:
return { nextEdgeId: await executeScript(block, context) }
case LogicBlockType.TYPEBOT_LINK:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { BotContext, InitialChatReply } from '@/types'
import { isNotDefined } from 'utils'
import { executeClientSideAction } from '@/utils/executeClientSideActions'
import { LoadingChunk } from './LoadingChunk'
import { PopupBlockedToast } from './PopupBlockedToast'

const parseDynamicTheme = (
initialTheme: Theme,
Expand Down Expand Up @@ -57,6 +58,7 @@ export const ConversationContainer = (props: Props) => {
>(props.initialChatReply.dynamicTheme)
const [theme, setTheme] = createSignal(props.initialChatReply.typebot.theme)
const [isSending, setIsSending] = createSignal(false)
const [blockedPopupUrl, setBlockedPopupUrl] = createSignal<string>()

createEffect(() => {
setTheme(
Expand Down Expand Up @@ -112,7 +114,8 @@ export const ConversationContainer = (props: Props) => {
isNotDefined(action.lastBubbleBlockId)
)
for (const action of actionsToExecute) {
await executeClientSideAction(action)
const response = await executeClientSideAction(action)
if (response) setBlockedPopupUrl(response.blockedPopupUrl)
}
}
if (isNotDefined(lastChunk.input)) {
Expand All @@ -128,7 +131,8 @@ export const ConversationContainer = (props: Props) => {
(action) => action.lastBubbleBlockId === blockId
)
for (const action of actionsToExecute) {
await executeClientSideAction(action)
const response = await executeClientSideAction(action)
if (response) setBlockedPopupUrl(response.blockedPopupUrl)
}
}
}
Expand Down Expand Up @@ -161,6 +165,16 @@ export const ConversationContainer = (props: Props) => {
<Show when={isSending()}>
<LoadingChunk theme={theme()} />
</Show>
<Show when={blockedPopupUrl()} keyed>
{(blockedPopupUrl) => (
<div class="flex justify-end">
<PopupBlockedToast
url={blockedPopupUrl}
onLinkClick={() => setBlockedPopupUrl(undefined)}
/>
</div>
)}
</Show>
<BottomSpacer ref={bottomSpacer} />
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
type Props = {
url: string
onLinkClick: () => void
}

export const PopupBlockedToast = (props: Props) => {
return (
<div
class="w-full max-w-xs p-4 text-gray-500 bg-white rounded-lg shadow flex flex-col gap-2"
role="alert"
>
<span class="mb-1 text-sm font-semibold text-gray-900">
Popup blocked
</span>
<div class="mb-2 text-sm font-normal">
The bot wants to open a new tab but it was blocked by your broswer. It
needs a manual approval.
</div>
<a
href={props.url}
target="_blank"
class="py-1 px-4 justify-center text-sm font-semibold rounded-md text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button"
rel="noreferrer"
onClick={() => props.onLinkClick()}
>
Continue in new tab
</a>
</div>
)
}
2 changes: 1 addition & 1 deletion packages/js/src/components/bubbles/GuestBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const GuestBubble = (props: Props) => (
style={{ 'margin-left': '50px' }}
>
<span
class="px-4 py-2 rounded-lg mr-2 whitespace-pre-wrap max-w-full typebot-guest-bubble cursor-pointer"
class="px-4 py-2 rounded-lg mr-2 whitespace-pre-wrap max-w-full typebot-guest-bubble"
data-testid="guest-bubble"
>
{props.message}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import type { RedirectOptions } from 'models'

export const executeRedirect = ({ url, isNewTab }: RedirectOptions) => {
export const executeRedirect = ({
url,
isNewTab,
}: RedirectOptions): { blockedPopupUrl: string } | undefined => {
if (!url) return
window.open(url, isNewTab ? '_blank' : '_self')
const updatedWindow = window.open(url, isNewTab ? '_blank' : '_self')
if (!updatedWindow)
return {
blockedPopupUrl: url,
}
}
12 changes: 6 additions & 6 deletions packages/js/src/utils/executeClientSideActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ import type { ChatReply } from 'models'

export const executeClientSideAction = async (
clientSideAction: NonNullable<ChatReply['clientSideActions']>[0]
) => {
): Promise<{ blockedPopupUrl: string } | void> => {
if ('chatwoot' in clientSideAction) {
executeChatwoot(clientSideAction.chatwoot)
return executeChatwoot(clientSideAction.chatwoot)
}
if ('googleAnalytics' in clientSideAction) {
executeGoogleAnalyticsBlock(clientSideAction.googleAnalytics)
return executeGoogleAnalyticsBlock(clientSideAction.googleAnalytics)
}
if ('scriptToExecute' in clientSideAction) {
await executeScript(clientSideAction.scriptToExecute)
return executeScript(clientSideAction.scriptToExecute)
}
if ('redirect' in clientSideAction) {
executeRedirect(clientSideAction.redirect)
return executeRedirect(clientSideAction.redirect)
}
if ('wait' in clientSideAction) {
await executeWait(clientSideAction.wait)
return executeWait(clientSideAction.wait)
}
}

4 comments on commit b2d1235

@vercel
Copy link

@vercel vercel bot commented on b2d1235 Feb 20, 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-typebot-io.vercel.app
docs-git-main-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on b2d1235 Feb 20, 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

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

@vercel
Copy link

@vercel vercel bot commented on b2d1235 Feb 20, 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 b2d1235 Feb 20, 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

ns8.vn
1stop.au
yobot.me
klujo.com
247987.com
8jours.top
aginap.com
bee.cr8.ai
myrentalhost.com
stan.vselise.com
start.taxtree.io
typebot.aloe.bot
voicehelp.cr8.ai
zap.fundviser.in
app.chatforms.net
bot.hostnation.de
bot.maitempah.com
bot.phuonghub.com
bot.reviewzer.com
bot.rihabilita.it
cares.urlabout.me
chat.gaswadern.de
fmm.wpwakanda.com
gentleman-shop.fr
k1.kandabrand.com
lb.ticketfute.com
ov1.wpwakanda.com
ov2.wpwakanda.com
ov3.wpwakanda.com
support.triplo.ai
viewer.typebot.io
1988.bouclidom.com
andreimayer.com.br
bot.danyservice.it
bot.iconicbrows.it
bot.megafox.com.br
bot.neferlopez.com
bots.robomotion.io
cadu.uninta.edu.br
dicanatural.online
digitalhelp.com.au
goalsettingbot.com
pant.maxbot.com.br
positivobra.com.br
survey.digienge.io
this-is-a-test.com
zap.techadviser.in
bot.boston-voip.com
bot.cabinpromos.com
bot.digitalbled.com
bot.dsignagency.com
bot.eventhub.com.au
bot.jepierre.com.br
bot.ltmidias.com.br
bot.viralsangat.com
bot.winglabs.com.br
carsalesenquiry.com
chat.marius.digital
chatbot.matthesv.de
chatbot.repplai.com
demo.botscientis.us
demo.wemakebots.xyz
forms.webisharp.com
kbsub.wpwakanda.com
live.botscientis.us
mentoria.omelhor.vc
nutrisamirbayde.com
order.maitempah.com
quest.wpwakanda.com
support.wawplus.com
survey1.digienge.io
surveys.essiell.com
test.botscientis.us
test.reventepro.com
typebot.stillio.com
wordsandimagery.com
88584434.therpm.club
92109660.therpm.club
abbonamento.bwell.it
bium.gratirabbit.com
bot.ansuraniphone.my
bot.barrettamario.it
bot.cotemeuplano.com
bot.leadbooster.help
bot.mycompay.reviews
chat.hayurihijab.com
chatbee.agfunnel.com
click.sevenoways.com
connect.growthguy.in
forms.bonanza.design
hello.advergreen.com
kuiz.sistemniaga.com
offer.botscientis.us
sellmycarglasgow.com
talkbot.agfunnel.com
tenorioadvogados.com
uppity.wpwakanda.com
abutton.wpwakanda.com
acelera.maxbot.com.br
aidigitalmarketing.kr

Please sign in to comment.