Skip to content

Commit

Permalink
✨ Add audio bubble block
Browse files Browse the repository at this point in the history
Closes #167
  • Loading branch information
baptisteArno committed Nov 17, 2022
1 parent 473d315 commit 7db0e01
Show file tree
Hide file tree
Showing 29 changed files with 306 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ const UploadFileContent = ({
}: ContentProps & { filePath: string; includeFileName?: boolean }) => (
<Flex justify="center" py="2">
<UploadButton
fileType="image"
filePath={filePath}
onFileUploaded={onNewUrl}
includeFileName={includeFileName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { ChangeEvent, useState } from 'react'
import { uploadFiles } from 'utils'

type UploadButtonProps = {
fileType: 'image' | 'audio'
filePath: string
includeFileName?: boolean
onFileUploaded: (url: string) => void
} & ButtonProps

export const UploadButton = ({
fileType,
filePath,
includeFileName,
onFileUploaded,
Expand Down Expand Up @@ -41,7 +43,11 @@ export const UploadButton = ({
id="file-input"
display="none"
onChange={handleInputChange}
accept=".jpg, .jpeg, .png, .svg, .gif"
accept={
fileType === 'image'
? '.jpg, .jpeg, .png, .svg, .gif'
: '.mp3, .wav, .ogg'
}
/>
<Button
as="label"
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/src/components/icons.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IconProps, Icon } from '@chakra-ui/react'

const featherIconsBaseProps: IconProps = {
export const featherIconsBaseProps: IconProps = {
fill: 'none',
stroke: 'currentColor',
strokeWidth: '2px',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const MyAccountForm = () => {
<Stack>
<UploadButton
size="sm"
fileType="image"
filePath={`users/${user?.id}/avatar`}
leftIcon={<UploadIcon />}
onFileUploaded={handleFileUploaded}
Expand Down
41 changes: 41 additions & 0 deletions apps/builder/src/features/blocks/bubbles/audio/audio.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import test, { expect } from '@playwright/test'
import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { BubbleBlockType, defaultAudioBubbleContent } from 'models'
import cuid from 'cuid'
import { getTestAsset } from '@/test/utils/playwright'
import { typebotViewer } from 'utils/playwright/testHelpers'

const audioSampleUrl =
'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'

test('should work as expected', async ({ page }) => {
const typebotId = cuid()
await createTypebots([
{
id: typebotId,
...parseDefaultGroupWithBlock({
type: BubbleBlockType.AUDIO,
content: defaultAudioBubbleContent,
}),
},
])

await page.goto(`/typebots/${typebotId}/edit`)
await page.getByText('Click to edit...').click()
await page
.getByPlaceholder('Paste the audio file link...')
.fill(audioSampleUrl)
await expect(page.locator('audio')).toHaveAttribute('src', audioSampleUrl)
await page.getByRole('button', { name: 'Upload' }).click()
await page.setInputFiles('input[type="file"]', getTestAsset('sample.mp3'))
await expect(page.locator('audio')).toHaveAttribute(
'src',
RegExp(`/public/typebots/${typebotId}/blocks`, 'gm')
)
await page.getByRole('button', { name: 'Preview' }).click()
await expect(typebotViewer(page).locator('audio')).toHaveAttribute(
'src',
RegExp(`/public/typebots/${typebotId}/blocks`, 'gm')
)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Button, Flex, HStack, Stack, Text } from '@chakra-ui/react'
import { AudioBubbleContent } from 'models'
import { Input } from '@/components/inputs'
import { useState } from 'react'
import { UploadButton } from '@/components/ImageUploadContent/UploadButton'

type Props = {
fileUploadPath: string
content: AudioBubbleContent
onSubmit: (content: AudioBubbleContent) => void
}

export const AudioBubbleForm = ({
fileUploadPath,
content,
onSubmit,
}: Props) => {
const [currentTab, setCurrentTab] = useState<'link' | 'upload'>('link')

const submit = (url: string) => onSubmit({ url })

return (
<Stack>
<HStack>
<Button
variant={currentTab === 'upload' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('upload')}
size="sm"
>
Upload
</Button>
<Button
variant={currentTab === 'link' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('link')}
size="sm"
>
Embed link
</Button>
</HStack>
<Stack p="2">
{currentTab === 'upload' && (
<Flex justify="center" py="2">
<UploadButton
fileType="audio"
filePath={fileUploadPath}
onFileUploaded={submit}
colorScheme="blue"
>
Choose a file
</UploadButton>
</Flex>
)}
{currentTab === 'link' && (
<>
<Input
placeholder="Paste the audio file link..."
defaultValue={content.url ?? ''}
onChange={submit}
/>
<Text fontSize="sm" color="gray.400" textAlign="center">
Works with .MP3s, .WAVs and .OGGs
</Text>
</>
)}
</Stack>
</Stack>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { featherIconsBaseProps } from '@/components/icons'
import { Icon, IconProps } from '@chakra-ui/react'
import React from 'react'

export const AudioBubbleIcon = (props: IconProps) => (
<Icon color="blue.500" {...featherIconsBaseProps} {...props}>
<path d="M3 18v-6a9 9 0 0 1 18 0v6"></path>
<path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path>
</Icon>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Text } from '@chakra-ui/react'
import { AudioBubbleContent } from 'models'
import { isDefined } from 'utils'

type Props = {
url: AudioBubbleContent['url']
}

export const AudioBubbleNode = ({ url }: Props) =>
isDefined(url) ? (
<audio src={url} controls />
) : (
<Text color={'gray.500'}>Click to edit...</Text>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './AudioBubbleNode'
export * from './AudioBubbleIcon'
1 change: 1 addition & 0 deletions apps/builder/src/features/blocks/bubbles/audio/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components'
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { NumberInputIcon } from '@/features/blocks/inputs/number'
import { TextInputIcon } from '@/features/blocks/inputs/textInput'
import { EmbedBubbleIcon } from '@/features/blocks/bubbles/embed'
import { GoogleAnalyticsLogo } from '@/features/blocks/integrations/googleAnalytics'
import { AudioBubbleIcon } from '@/features/blocks/bubbles/audio'

type BlockIconProps = { type: BlockType } & IconProps

Expand All @@ -47,37 +48,39 @@ export const BlockIcon = ({ type, ...props }: BlockIconProps) => {
case BubbleBlockType.VIDEO:
return <VideoBubbleIcon {...props} />
case BubbleBlockType.EMBED:
return <EmbedBubbleIcon color="blue.500" {...props} />
return <EmbedBubbleIcon {...props} />
case BubbleBlockType.AUDIO:
return <AudioBubbleIcon {...props} />
case InputBlockType.TEXT:
return <TextInputIcon color="orange.500" {...props} />
return <TextInputIcon {...props} />
case InputBlockType.NUMBER:
return <NumberInputIcon color="orange.500" {...props} />
return <NumberInputIcon {...props} />
case InputBlockType.EMAIL:
return <EmailInputIcon color="orange.500" {...props} />
return <EmailInputIcon {...props} />
case InputBlockType.URL:
return <UrlInputIcon color="orange.500" {...props} />
return <UrlInputIcon {...props} />
case InputBlockType.DATE:
return <DateInputIcon color="orange.500" {...props} />
return <DateInputIcon {...props} />
case InputBlockType.PHONE:
return <PhoneInputIcon color="orange.500" {...props} />
return <PhoneInputIcon {...props} />
case InputBlockType.CHOICE:
return <ButtonsInputIcon color="orange.500" {...props} />
return <ButtonsInputIcon {...props} />
case InputBlockType.PAYMENT:
return <PaymentInputIcon color="orange.500" {...props} />
return <PaymentInputIcon {...props} />
case InputBlockType.RATING:
return <RatingInputIcon color="orange.500" {...props} />
return <RatingInputIcon {...props} />
case InputBlockType.FILE:
return <FileInputIcon color="orange.500" {...props} />
return <FileInputIcon {...props} />
case LogicBlockType.SET_VARIABLE:
return <SetVariableIcon color="purple.500" {...props} />
return <SetVariableIcon {...props} />
case LogicBlockType.CONDITION:
return <ConditionIcon color="purple.500" {...props} />
return <ConditionIcon {...props} />
case LogicBlockType.REDIRECT:
return <RedirectIcon color="purple.500" {...props} />
return <RedirectIcon {...props} />
case LogicBlockType.CODE:
return <CodeIcon color="purple.500" {...props} />
return <CodeIcon {...props} />
case LogicBlockType.TYPEBOT_LINK:
return <TypebotLinkIcon color="purple.500" {...props} />
return <TypebotLinkIcon {...props} />
case IntegrationBlockType.GOOGLE_SHEETS:
return <GoogleSheetsLogo {...props} />
case IntegrationBlockType.GOOGLE_ANALYTICS:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const BlockTypeLabel = ({ type }: Props): JSX.Element => {
<Text>Embed</Text>
</Tooltip>
)
case BubbleBlockType.AUDIO:
return <Text>Audio</Text>
case InputBlockType.NUMBER:
return <Text>Number</Text>
case InputBlockType.EMAIL:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { ZapierContent } from '@/features/blocks/integrations/zapier'
import { SendEmailContent } from '@/features/blocks/integrations/sendEmail'
import { isInputBlock, isChoiceInput, blockHasItems } from 'utils'
import { MakeComNodeContent } from '@/features/blocks/integrations/makeCom'
import { AudioBubbleNode } from '@/features/blocks/bubbles/audio'

type Props = {
block: Block | StartBlock
Expand Down Expand Up @@ -66,6 +67,9 @@ export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
case BubbleBlockType.EMBED: {
return <EmbedBubbleContent block={block} />
}
case BubbleBlockType.AUDIO: {
return <AudioBubbleNode url={block.content.url} />
}
case InputBlockType.TEXT: {
return (
<TextInputNodeContent
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ImageUploadContent } from '@/components/ImageUploadContent'
import { AudioBubbleForm } from '@/features/blocks/bubbles/audio/components/AudioBubbleForm'
import { EmbedUploadContent } from '@/features/blocks/bubbles/embed'
import { VideoUploadContent } from '@/features/blocks/bubbles/video'
import {
Expand Down Expand Up @@ -73,5 +74,14 @@ export const MediaBubbleContent = ({
/>
)
}
case BubbleBlockType.AUDIO: {
return (
<AudioBubbleForm
content={block.content}
fileUploadPath={`typebots/${typebotId}/blocks/${block.id}`}
onSubmit={onContentChange}
/>
)
}
}
}
3 changes: 3 additions & 0 deletions apps/builder/src/features/graph/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
defaultGoogleAnalyticsOptions,
defaultGoogleSheetsOptions,
defaultImageBubbleContent,
defaultAudioBubbleContent,
defaultNumberInputOptions,
defaultPaymentInputOptions,
defaultPhoneInputOptions,
Expand Down Expand Up @@ -400,6 +401,8 @@ const parseDefaultContent = (type: BubbleBlockType): BubbleBlockContent => {
return defaultVideoBubbleContent
case BubbleBlockType.EMBED:
return defaultEmbedBubbleContent
case BubbleBlockType.AUDIO:
return defaultAudioBubbleContent
}
}

Expand Down
Binary file added apps/builder/src/test/assets/sample.mp3
Binary file not shown.
21 changes: 21 additions & 0 deletions apps/docs/docs/editor/blocks/bubbles/audio.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { FlowToBot } from '../../../../src/js/FlowToBot'

# Audio

The Audio bubble block allows you to play a recorded audio to your user. You can upload an audio file or directly paste a URL.

<FlowToBot
flow={
<img
src="/img/blocks/bubbles/audio/editor.png"
width="100%"
style={{ maxWidth: '400px' }}
alt="Audio bubble"
/>
}
bot={
<video controls width="100%" style={{ maxWidth: '400px' }}>
<source src="/img/blocks/bubbles/audio/bot.mp4" type="video/mp4" />
</video>
}
/>
4 changes: 0 additions & 4 deletions apps/docs/docs/editor/blocks/bubbles/embed.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
---
sidebar_position: 4
---

import { FlowToBot } from '../../../../src/js/FlowToBot'

# Embed
Expand Down
4 changes: 0 additions & 4 deletions apps/docs/docs/editor/blocks/bubbles/video.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
---
sidebar_position: 3
---

import { FlowToBot } from '../../../../src/js/FlowToBot'

# Video
Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AudioBubble } from '@/features/blocks/bubbles/audio'
import { EmbedBubble } from '@/features/blocks/bubbles/embed'
import { ImageBubble } from '@/features/blocks/bubbles/image'
import { TextBubble } from '@/features/blocks/bubbles/textBubble'
Expand All @@ -20,5 +21,12 @@ export const HostBubble = ({ block, onTransitionEnd }: Props) => {
return <VideoBubble block={block} onTransitionEnd={onTransitionEnd} />
case BubbleBlockType.EMBED:
return <EmbedBubble block={block} onTransitionEnd={onTransitionEnd} />
case BubbleBlockType.AUDIO:
return (
<AudioBubble
url={block.content.url}
onTransitionEnd={onTransitionEnd}
/>
)
}
}
Loading

5 comments on commit 7db0e01

@vercel
Copy link

@vercel vercel bot commented on 7db0e01 Nov 17, 2022

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
bot.aws.bj
bot.bbc.bj
finplex.be
sat.cr8.ai
bot.aipr.kr
minipost.uk
bt.id8rs.com
bot.krdfy.com
goldorayo.com
vhpage.cr8.ai
am.nigerias.io
an.nigerias.io
ar.nigerias.io
bot.enreso.org
bot.lalmon.com
ticketfute.com
apo.nigerias.io
apr.nigerias.io
aso.nigerias.io
bot.ageenda.com
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.upfunnel.art
botc.ceox.com.br
clo.closeer.work
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
onboarding.growthside.io
reward.onlinebotdemo.xyz
type.opaulovieira.com.br
aibot.angrybranding.co.uk
bot.aidigitalmarketing.kr
bot.arraesecenteno.com.br
bot.blackboxsports.com.br
bot.cabinrentalagency.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
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

@vercel
Copy link

@vercel vercel bot commented on 7db0e01 Nov 17, 2022

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-git-main-typebot-io.vercel.app
docs.typebot.io
docs-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 7db0e01 Nov 17, 2022

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 7db0e01 Nov 17, 2022

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 7db0e01 Nov 17, 2022

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-git-main-typebot-io.vercel.app
app.typebot.io
builder-v2-typebot-io.vercel.app

Please sign in to comment.