🌐 Add pt_BR and more translations (#767)
Original PR: https://github.com/baptisteArno/typebot.io/pull/694 --------- Co-authored-by: Daniel Oliveira <daniel.oliveira@kununu.com> Co-authored-by: Daniel Oliveira <daniel@headdev.com.br>
This commit is contained in:
@@ -19,10 +19,11 @@ import { ChevronDownIcon } from '@/components/icons'
|
||||
import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
|
||||
|
||||
const localeHumanReadable = {
|
||||
en: 'English',
|
||||
fr: 'Français',
|
||||
de: 'Deutsch',
|
||||
pt: 'Português',
|
||||
en: 'English',
|
||||
fr: 'Français',
|
||||
de: 'Deutsch',
|
||||
pt: 'Português',
|
||||
pt_BR: 'Português (BR)'
|
||||
} as const
|
||||
|
||||
export const UserPreferencesForm = () => {
|
||||
|
||||
@@ -30,7 +30,8 @@ export const CurrentSubscriptionSummary = ({ workspace }: Props) => {
|
||||
<PlanTag plan={workspace.plan} />
|
||||
{data?.subscription?.cancelDate && (
|
||||
<Text fontSize="sm">
|
||||
(Will be cancelled on {data.subscription.cancelDate.toDateString()})
|
||||
({scopedT('cancelDate')}{' '}
|
||||
{data.subscription.cancelDate.toDateString()})
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
@@ -88,7 +88,7 @@ export const UsageProgressBars = ({ workspace }: Props) => {
|
||||
<Text>
|
||||
/{' '}
|
||||
{workspaceChatsLimit === -1
|
||||
? 'Unlimited'
|
||||
? scopedT('unlimited')
|
||||
: parseNumberWithCommas(workspaceChatsLimit)}
|
||||
</Text>
|
||||
</HStack>
|
||||
@@ -141,7 +141,7 @@ export const UsageProgressBars = ({ workspace }: Props) => {
|
||||
<Text>
|
||||
/{' '}
|
||||
{workspaceStorageLimit === -1
|
||||
? 'Unlimited'
|
||||
? scopedT('unlimited')
|
||||
: `${workspaceStorageLimit} GB`}
|
||||
</Text>
|
||||
</HStack>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TextInput } from '@/components/inputs'
|
||||
import { useState } from 'react'
|
||||
import { UploadButton } from '@/components/ImageUploadContent/UploadButton'
|
||||
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type Props = {
|
||||
fileUploadPath: string
|
||||
@@ -16,6 +17,7 @@ export const AudioBubbleForm = ({
|
||||
content,
|
||||
onContentChange,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.audio.settings')
|
||||
const [currentTab, setCurrentTab] = useState<'link' | 'upload'>('link')
|
||||
|
||||
const updateUrl = (url: string) => onContentChange({ ...content, url })
|
||||
@@ -31,14 +33,14 @@ export const AudioBubbleForm = ({
|
||||
onClick={() => setCurrentTab('upload')}
|
||||
size="sm"
|
||||
>
|
||||
Upload
|
||||
{scopedT('upload.label')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={currentTab === 'link' ? 'solid' : 'ghost'}
|
||||
onClick={() => setCurrentTab('link')}
|
||||
size="sm"
|
||||
>
|
||||
Embed link
|
||||
{scopedT('embedLink.label')}
|
||||
</Button>
|
||||
</HStack>
|
||||
<Stack p="2" spacing={4}>
|
||||
@@ -51,25 +53,25 @@ export const AudioBubbleForm = ({
|
||||
onFileUploaded={updateUrl}
|
||||
colorScheme="blue"
|
||||
>
|
||||
Choose a file
|
||||
{scopedT('chooseFile.label')}
|
||||
</UploadButton>
|
||||
</Flex>
|
||||
)}
|
||||
{currentTab === 'link' && (
|
||||
<>
|
||||
<TextInput
|
||||
placeholder="Paste the audio file link..."
|
||||
placeholder={scopedT('worksWith.placeholder')}
|
||||
defaultValue={content.url ?? ''}
|
||||
onChange={updateUrl}
|
||||
/>
|
||||
<Text fontSize="sm" color="gray.400" textAlign="center">
|
||||
Works with .MP3s and .WAVs
|
||||
{scopedT('worksWith.text')}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
<SwitchWithLabel
|
||||
label={'Enable autoplay'}
|
||||
label={scopedT('autoplay.label')}
|
||||
initialValue={content.isAutoplayEnabled ?? true}
|
||||
onCheckChange={updateAutoPlay}
|
||||
/>
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { AudioBubbleContent } from '@typebot.io/schemas'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type Props = {
|
||||
url: AudioBubbleContent['url']
|
||||
}
|
||||
|
||||
export const AudioBubbleNode = ({ url }: Props) =>
|
||||
isDefined(url) ? (
|
||||
export const AudioBubbleNode = ({ url }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.audio.node')
|
||||
return isDefined(url) ? (
|
||||
<audio src={url} controls />
|
||||
) : (
|
||||
<Text color={'gray.500'}>Click to edit...</Text>
|
||||
<Text color={'gray.500'}>{scopedT('clickToEdit.text')}</Text>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { EmbedBubbleBlock } from '@typebot.io/schemas'
|
||||
|
||||
export const EmbedBubbleContent = ({ block }: { block: EmbedBubbleBlock }) => {
|
||||
if (!block.content?.url) return <Text color="gray.500">Click to edit...</Text>
|
||||
return <Text>Show embed</Text>
|
||||
type Props = {
|
||||
block: EmbedBubbleBlock
|
||||
}
|
||||
|
||||
export const EmbedBubbleContent = ({ block }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.embed.node')
|
||||
if (!block.content?.url)
|
||||
return <Text color="gray.500">{scopedT('clickToEdit.text')}</Text>
|
||||
return <Text>{scopedT('show.text')}</Text>
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { TextInput, NumberInput } from '@/components/inputs'
|
||||
import { HStack, Stack, Text } from '@chakra-ui/react'
|
||||
import { EmbedBubbleContent } from '@typebot.io/schemas'
|
||||
import { sanitizeUrl } from '@typebot.io/lib'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type Props = {
|
||||
content: EmbedBubbleContent
|
||||
@@ -9,6 +10,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const EmbedUploadContent = ({ content, onSubmit }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.embed.settings')
|
||||
const handleUrlChange = (url: string) => {
|
||||
const iframeUrl = sanitizeUrl(
|
||||
url.trim().startsWith('<iframe') ? extractUrlFromIframe(url) : url
|
||||
@@ -23,12 +25,12 @@ export const EmbedUploadContent = ({ content, onSubmit }: Props) => {
|
||||
<Stack p="2" spacing={6}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
placeholder="Paste the link or code..."
|
||||
placeholder={scopedT('worksWith.placeholder')}
|
||||
defaultValue={content?.url ?? ''}
|
||||
onChange={handleUrlChange}
|
||||
/>
|
||||
<Text fontSize="sm" color="gray.400" textAlign="center">
|
||||
Works with PDFs, iframes, websites...
|
||||
{scopedT('worksWith.text')}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -38,7 +40,7 @@ export const EmbedUploadContent = ({ content, onSubmit }: Props) => {
|
||||
defaultValue={content?.height}
|
||||
onValueChange={handleHeightChange}
|
||||
/>
|
||||
<Text>px</Text>
|
||||
<Text>{scopedT('numberInput.unit')}</Text>
|
||||
</HStack>
|
||||
</Stack>
|
||||
)
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { Box, Text, Image } from '@chakra-ui/react'
|
||||
import { ImageBubbleBlock } from '@typebot.io/schemas'
|
||||
|
||||
export const ImageBubbleContent = ({ block }: { block: ImageBubbleBlock }) => {
|
||||
type Props = {
|
||||
block: ImageBubbleBlock
|
||||
}
|
||||
|
||||
export const ImageBubbleContent = ({ block }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.image.node')
|
||||
const containsVariables =
|
||||
block.content?.url?.includes('{{') && block.content.url.includes('}}')
|
||||
return !block.content?.url ? (
|
||||
<Text color={'gray.500'}>Click to edit...</Text>
|
||||
<Text color={'gray.500'}>{scopedT('clickToEdit.text')}</Text>
|
||||
) : (
|
||||
<Box w="full">
|
||||
<Image
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ImageUploadContent } from '@/components/ImageUploadContent'
|
||||
import { TextInput } from '@/components/inputs'
|
||||
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { isDefined, isNotEmpty } from '@typebot.io/lib'
|
||||
import { ImageBubbleBlock } from '@typebot.io/schemas'
|
||||
@@ -17,6 +18,9 @@ export const ImageBubbleSettings = ({
|
||||
block,
|
||||
onContentChange,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n(
|
||||
'editor.blocks.bubbles.image.switchWithLabel.onClick'
|
||||
)
|
||||
const [showClickLinkInput, setShowClickLinkInput] = useState(
|
||||
isNotEmpty(block.content.clickLink?.url)
|
||||
)
|
||||
@@ -55,7 +59,7 @@ export const ImageBubbleSettings = ({
|
||||
/>
|
||||
<Stack>
|
||||
<SwitchWithLabel
|
||||
label={'On click link'}
|
||||
label={scopedT('label')}
|
||||
initialValue={showClickLinkInput}
|
||||
onCheckChange={toggleClickLink}
|
||||
/>
|
||||
@@ -68,7 +72,7 @@ export const ImageBubbleSettings = ({
|
||||
defaultValue={block.content.clickLink?.url}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Link alt text (description)"
|
||||
placeholder={scopedT('placeholder')}
|
||||
onChange={updateClickLinkAltText}
|
||||
defaultValue={block.content.clickLink?.alt}
|
||||
/>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { colors } from '@/lib/theme'
|
||||
import { useOutsideClick } from '@/hooks/useOutsideClick'
|
||||
import { selectEditor, TElement } from '@udecode/plate-common'
|
||||
import { TextEditorToolBar } from './TextEditorToolBar'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type TextBubbleEditorContentProps = {
|
||||
id: string
|
||||
@@ -30,6 +31,7 @@ const TextBubbleEditorContent = ({
|
||||
textEditorValue,
|
||||
onClose,
|
||||
}: TextBubbleEditorContentProps) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles')
|
||||
const editor = usePlateEditorRef()
|
||||
const varDropdownRef = useRef<HTMLDivElement | null>(null)
|
||||
const rememberedSelection = useRef<BaseSelection | null>(null)
|
||||
@@ -108,7 +110,7 @@ const TextBubbleEditorContent = ({
|
||||
backgroundColor: useColorModeValue('white', 'gray.800'),
|
||||
borderRadius: 'md',
|
||||
transitionProperty: 'background-color',
|
||||
transitionDuration: 'normal'
|
||||
transitionDuration: 'normal',
|
||||
},
|
||||
'[class^="FloatingVerticalDivider___"]': {
|
||||
'--tw-bg-opacity': useColorModeValue('1', '.4') + '!important',
|
||||
@@ -135,7 +137,7 @@ const TextBubbleEditorContent = ({
|
||||
})
|
||||
setIsFirstFocus(false)
|
||||
},
|
||||
'aria-label': 'Text editor',
|
||||
'aria-label': `${scopedT('textEditor.plate.label')}`,
|
||||
onBlur: () => {
|
||||
rememberedSelection.current = editor?.selection
|
||||
},
|
||||
@@ -154,7 +156,7 @@ const TextBubbleEditorContent = ({
|
||||
<VariableSearchInput
|
||||
initialVariableId={undefined}
|
||||
onSelectVariable={handleVariableSelected}
|
||||
placeholder="Search for a variable"
|
||||
placeholder={scopedT('textEditor.searchVariable.placeholder')}
|
||||
autoFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { Box, Text } from '@chakra-ui/react'
|
||||
import { VideoBubbleBlock, VideoBubbleContentType } from '@typebot.io/schemas'
|
||||
|
||||
export const VideoBubbleContent = ({ block }: { block: VideoBubbleBlock }) => {
|
||||
type Props = {
|
||||
block: VideoBubbleBlock
|
||||
}
|
||||
|
||||
export const VideoBubbleContent = ({ block }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.video.node')
|
||||
if (!block.content?.url || !block.content.type)
|
||||
return <Text color="gray.500">Click to edit...</Text>
|
||||
return <Text color="gray.500">{scopedT('clickToEdit.text')}</Text>
|
||||
switch (block.content.type) {
|
||||
case VideoBubbleContentType.URL:
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Stack, Text } from '@chakra-ui/react'
|
||||
import { VideoBubbleContent, VideoBubbleContentType } from '@typebot.io/schemas'
|
||||
import { TextInput } from '@/components/inputs'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
const vimeoRegex = /vimeo\.com\/(\d+)/
|
||||
const youtubeRegex = /youtube\.com\/(watch\?v=|shorts\/)(\w+)|youtu\.be\/(\w+)/
|
||||
@@ -11,6 +12,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const VideoUploadContent = ({ content, onSubmit }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.video.settings')
|
||||
const handleUrlChange = (url: string) => {
|
||||
const info = parseVideoUrl(url)
|
||||
return onSubmit({
|
||||
@@ -22,12 +24,12 @@ export const VideoUploadContent = ({ content, onSubmit }: Props) => {
|
||||
return (
|
||||
<Stack p="2">
|
||||
<TextInput
|
||||
placeholder="Paste the video link..."
|
||||
placeholder={scopedT('worksWith.placeholder')}
|
||||
defaultValue={content?.url ?? ''}
|
||||
onChange={handleUrlChange}
|
||||
/>
|
||||
<Text fontSize="sm" color="gray.400" textAlign="center">
|
||||
Works with Youtube, Vimeo and others
|
||||
{scopedT('worksWith.text')}
|
||||
</Text>
|
||||
</Stack>
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Plan } from '@typebot.io/prisma'
|
||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { BlockLabel } from './BlockLabel'
|
||||
import { LockTag } from '@/features/billing/components/LockTag'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type Props = {
|
||||
type: DraggableBlockType
|
||||
@@ -26,6 +27,7 @@ type Props = {
|
||||
export const BlockCard = (
|
||||
props: Pick<Props, 'type' | 'onMouseDown'>
|
||||
): JSX.Element => {
|
||||
const scopedT = useScopedI18n('editor.blockCard')
|
||||
const { workspace } = useWorkspace()
|
||||
|
||||
switch (props.type) {
|
||||
@@ -33,7 +35,7 @@ export const BlockCard = (
|
||||
return (
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip="Embed a pdf, an iframe, a website..."
|
||||
tooltip={scopedT('bubbleBlock.tooltip.label')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
@@ -41,7 +43,10 @@ export const BlockCard = (
|
||||
)
|
||||
case InputBlockType.FILE:
|
||||
return (
|
||||
<BlockCardLayout {...props} tooltip="Upload Files">
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('inputBlock.tooltip.files.label')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<HStack>
|
||||
<BlockLabel type={props.type} />
|
||||
@@ -51,14 +56,20 @@ export const BlockCard = (
|
||||
)
|
||||
case LogicBlockType.SCRIPT:
|
||||
return (
|
||||
<BlockCardLayout {...props} tooltip="Execute Javascript code">
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('logicBlock.tooltip.code.label')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
</BlockCardLayout>
|
||||
)
|
||||
case LogicBlockType.TYPEBOT_LINK:
|
||||
return (
|
||||
<BlockCardLayout {...props} tooltip="Link and jump to another typebot">
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('logicBlock.tooltip.typebotLink.label')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
</BlockCardLayout>
|
||||
@@ -67,7 +78,7 @@ export const BlockCard = (
|
||||
return (
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip="Fast forward the flow to another group"
|
||||
tooltip={scopedT('logicBlock.tooltip.jump.label')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
@@ -75,14 +86,20 @@ export const BlockCard = (
|
||||
)
|
||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||
return (
|
||||
<BlockCardLayout {...props} tooltip="Google Sheets">
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('integrationBlock.tooltip.googleSheets.label')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
</BlockCardLayout>
|
||||
)
|
||||
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
||||
return (
|
||||
<BlockCardLayout {...props} tooltip="Google Analytics">
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('integrationBlock.tooltip.googleAnalytics.label')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
</BlockCardLayout>
|
||||
|
||||
@@ -7,79 +7,82 @@ import {
|
||||
BlockType,
|
||||
} from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type Props = { type: BlockType }
|
||||
|
||||
export const BlockLabel = ({ type }: Props): JSX.Element => {
|
||||
const scopedT = useScopedI18n('editor.sidebarBlock')
|
||||
|
||||
switch (type) {
|
||||
case 'start':
|
||||
return <Text fontSize="sm">Start</Text>
|
||||
return <Text fontSize="sm">{scopedT('start.label')}</Text>
|
||||
case BubbleBlockType.TEXT:
|
||||
case InputBlockType.TEXT:
|
||||
return <Text fontSize="sm">Text</Text>
|
||||
return <Text fontSize="sm">{scopedT('text.label')}</Text>
|
||||
case BubbleBlockType.IMAGE:
|
||||
return <Text fontSize="sm">Image</Text>
|
||||
return <Text fontSize="sm">{scopedT('image.label')}</Text>
|
||||
case BubbleBlockType.VIDEO:
|
||||
return <Text fontSize="sm">Video</Text>
|
||||
return <Text fontSize="sm">{scopedT('video.label')}</Text>
|
||||
case BubbleBlockType.EMBED:
|
||||
return <Text fontSize="sm">Embed</Text>
|
||||
return <Text fontSize="sm">{scopedT('embed.label')}</Text>
|
||||
case BubbleBlockType.AUDIO:
|
||||
return <Text fontSize="sm">Audio</Text>
|
||||
return <Text fontSize="sm">{scopedT('audio.label')}</Text>
|
||||
case InputBlockType.NUMBER:
|
||||
return <Text fontSize="sm">Number</Text>
|
||||
return <Text fontSize="sm">{scopedT('number.label')}</Text>
|
||||
case InputBlockType.EMAIL:
|
||||
return <Text fontSize="sm">Email</Text>
|
||||
return <Text fontSize="sm">{scopedT('email.label')}</Text>
|
||||
case InputBlockType.URL:
|
||||
return <Text fontSize="sm">Website</Text>
|
||||
return <Text fontSize="sm">{scopedT('website.label')}</Text>
|
||||
case InputBlockType.DATE:
|
||||
return <Text fontSize="sm">Date</Text>
|
||||
return <Text fontSize="sm">{scopedT('date.label')}</Text>
|
||||
case InputBlockType.PHONE:
|
||||
return <Text fontSize="sm">Phone</Text>
|
||||
return <Text fontSize="sm">{scopedT('phone.label')}</Text>
|
||||
case InputBlockType.CHOICE:
|
||||
return <Text fontSize="sm">Button</Text>
|
||||
return <Text fontSize="sm">{scopedT('button.label')}</Text>
|
||||
case InputBlockType.PICTURE_CHOICE:
|
||||
return <Text fontSize="sm">Pic choice</Text>
|
||||
return <Text fontSize="sm">{scopedT('picChoice.label')}</Text>
|
||||
case InputBlockType.PAYMENT:
|
||||
return <Text fontSize="sm">Payment</Text>
|
||||
return <Text fontSize="sm">{scopedT('payment.label')}</Text>
|
||||
case InputBlockType.RATING:
|
||||
return <Text fontSize="sm">Rating</Text>
|
||||
return <Text fontSize="sm">{scopedT('rating.label')}</Text>
|
||||
case InputBlockType.FILE:
|
||||
return <Text fontSize="sm">File</Text>
|
||||
return <Text fontSize="sm">{scopedT('file.label')}</Text>
|
||||
case LogicBlockType.SET_VARIABLE:
|
||||
return <Text fontSize="sm">Set variable</Text>
|
||||
return <Text fontSize="sm">{scopedT('setVariable.label')}</Text>
|
||||
case LogicBlockType.CONDITION:
|
||||
return <Text fontSize="sm">Condition</Text>
|
||||
return <Text fontSize="sm">{scopedT('condition.label')}</Text>
|
||||
case LogicBlockType.REDIRECT:
|
||||
return <Text fontSize="sm">Redirect</Text>
|
||||
return <Text fontSize="sm">{scopedT('redirect.label')}</Text>
|
||||
case LogicBlockType.SCRIPT:
|
||||
return <Text fontSize="sm">Script</Text>
|
||||
return <Text fontSize="sm">{scopedT('script.label')}</Text>
|
||||
case LogicBlockType.TYPEBOT_LINK:
|
||||
return <Text fontSize="sm">Typebot</Text>
|
||||
return <Text fontSize="sm">{scopedT('typebot.label')}</Text>
|
||||
case LogicBlockType.WAIT:
|
||||
return <Text fontSize="sm">Wait</Text>
|
||||
return <Text fontSize="sm">{scopedT('wait.label')}</Text>
|
||||
case LogicBlockType.JUMP:
|
||||
return <Text fontSize="sm">Jump</Text>
|
||||
return <Text fontSize="sm">{scopedT('jump.label')}</Text>
|
||||
case LogicBlockType.AB_TEST:
|
||||
return <Text fontSize="sm">AB Test</Text>
|
||||
return <Text fontSize="sm">{scopedT('abTest.label')}</Text>
|
||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||
return <Text fontSize="sm">Sheets</Text>
|
||||
return <Text fontSize="sm">{scopedT('sheets.label')}</Text>
|
||||
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
||||
return <Text fontSize="sm">Analytics</Text>
|
||||
return <Text fontSize="sm">{scopedT('analytics.label')}</Text>
|
||||
case IntegrationBlockType.WEBHOOK:
|
||||
return <Text fontSize="sm">Webhook</Text>
|
||||
return <Text fontSize="sm">{scopedT('webhook.label')}</Text>
|
||||
case IntegrationBlockType.ZAPIER:
|
||||
return <Text fontSize="sm">Zapier</Text>
|
||||
return <Text fontSize="sm">{scopedT('zapier.label')}</Text>
|
||||
case IntegrationBlockType.MAKE_COM:
|
||||
return <Text fontSize="sm">Make.com</Text>
|
||||
return <Text fontSize="sm">{scopedT('makecom.label')}</Text>
|
||||
case IntegrationBlockType.PABBLY_CONNECT:
|
||||
return <Text fontSize="sm">Pabbly</Text>
|
||||
return <Text fontSize="sm">{scopedT('pabbly.label')}</Text>
|
||||
case IntegrationBlockType.EMAIL:
|
||||
return <Text fontSize="sm">Email</Text>
|
||||
return <Text fontSize="sm">{scopedT('email.label')}</Text>
|
||||
case IntegrationBlockType.CHATWOOT:
|
||||
return <Text fontSize="sm">Chatwoot</Text>
|
||||
return <Text fontSize="sm">{scopedT('chatwoot.label')}</Text>
|
||||
case IntegrationBlockType.OPEN_AI:
|
||||
return <Text fontSize="sm">OpenAI</Text>
|
||||
return <Text fontSize="sm">{scopedT('openai.label')}</Text>
|
||||
case IntegrationBlockType.PIXEL:
|
||||
return <Text fontSize="sm">Pixel</Text>
|
||||
return <Text fontSize="sm">{scopedT('pixel.label')}</Text>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,10 @@ import { BlockCard } from './BlockCard'
|
||||
import { LockedIcon, UnlockedIcon } from '@/components/icons'
|
||||
import { BlockCardOverlay } from './BlockCardOverlay'
|
||||
import { headerHeight } from '../constants'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
export const BlocksSideBar = () => {
|
||||
const scopedT = useScopedI18n('editor.sidebarBlocks')
|
||||
const { setDraggedBlockType, draggedBlockType } = useBlockDnd()
|
||||
const [position, setPosition] = useState({
|
||||
x: 0,
|
||||
@@ -102,10 +104,20 @@ export const BlocksSideBar = () => {
|
||||
className="hide-scrollbar"
|
||||
>
|
||||
<Flex justifyContent="flex-end">
|
||||
<Tooltip label={isLocked ? 'Unlock sidebar' : 'Lock sidebar'}>
|
||||
<Tooltip
|
||||
label={
|
||||
isLocked
|
||||
? scopedT('sidebar.unlock.label')
|
||||
: scopedT('sidebar.lock.label')
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
icon={isLocked ? <LockedIcon /> : <UnlockedIcon />}
|
||||
aria-label={isLocked ? 'Unlock' : 'Lock'}
|
||||
aria-label={
|
||||
isLocked
|
||||
? scopedT('sidebar.icon.unlock.label')
|
||||
: scopedT('sidebar.icon.lock.label')
|
||||
}
|
||||
size="sm"
|
||||
onClick={handleLockClick}
|
||||
/>
|
||||
@@ -114,7 +126,7 @@ export const BlocksSideBar = () => {
|
||||
|
||||
<Stack>
|
||||
<Text fontSize="sm" fontWeight="semibold">
|
||||
Bubbles
|
||||
{scopedT('blockType.bubbles.heading')}
|
||||
</Text>
|
||||
<SimpleGrid columns={2} spacing="3">
|
||||
{Object.values(BubbleBlockType).map((type) => (
|
||||
@@ -125,7 +137,7 @@ export const BlocksSideBar = () => {
|
||||
|
||||
<Stack>
|
||||
<Text fontSize="sm" fontWeight="semibold">
|
||||
Inputs
|
||||
{scopedT('blockType.inputs.heading')}
|
||||
</Text>
|
||||
<SimpleGrid columns={2} spacing="3">
|
||||
{Object.values(InputBlockType).map((type) => (
|
||||
@@ -136,7 +148,7 @@ export const BlocksSideBar = () => {
|
||||
|
||||
<Stack>
|
||||
<Text fontSize="sm" fontWeight="semibold">
|
||||
Logic
|
||||
{scopedT('blockType.logic.heading')}
|
||||
</Text>
|
||||
<SimpleGrid columns={2} spacing="3">
|
||||
{Object.values(LogicBlockType).map((type) => (
|
||||
@@ -147,7 +159,7 @@ export const BlocksSideBar = () => {
|
||||
|
||||
<Stack>
|
||||
<Text fontSize="sm" fontWeight="semibold">
|
||||
Integrations
|
||||
{scopedT('blockType.integrations.heading')}
|
||||
</Text>
|
||||
<SimpleGrid columns={2} spacing="3">
|
||||
{Object.values(IntegrationBlockType).map((type) => (
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react'
|
||||
import React, { useState } from 'react'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type EditableProps = {
|
||||
defaultName: string
|
||||
@@ -15,6 +16,7 @@ export const EditableTypebotName = ({
|
||||
defaultName,
|
||||
onNewName,
|
||||
}: EditableProps) => {
|
||||
const scopedT = useScopedI18n('editor.editableTypebotName')
|
||||
const emptyNameBg = useColorModeValue('gray.100', 'gray.700')
|
||||
const [currentName, setCurrentName] = useState(defaultName)
|
||||
|
||||
@@ -25,7 +27,7 @@ export const EditableTypebotName = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip label="Rename">
|
||||
<Tooltip label={scopedT('tooltip.rename.label')}>
|
||||
<Editable
|
||||
value={currentName}
|
||||
onChange={setCurrentName}
|
||||
|
||||
@@ -21,8 +21,10 @@ import {
|
||||
} from '@chakra-ui/react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
export const GettingStartedModal = () => {
|
||||
const scopedT = useScopedI18n('editor.gettingStartedModal')
|
||||
const { query } = useRouter()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
|
||||
@@ -38,7 +40,7 @@ export const GettingStartedModal = () => {
|
||||
<ModalCloseButton />
|
||||
<ModalBody as={Stack} spacing="8" py="10">
|
||||
<Stack spacing={4}>
|
||||
<Heading fontSize="xl">Editor basics</Heading>
|
||||
<Heading fontSize="xl">{scopedT('editorBasics.heading')}</Heading>
|
||||
<List spacing={4}>
|
||||
<HStack as={ListItem}>
|
||||
<Flex
|
||||
@@ -54,10 +56,7 @@ export const GettingStartedModal = () => {
|
||||
>
|
||||
1
|
||||
</Flex>
|
||||
<Text>
|
||||
The left side bar contains blocks that you can drag and drop
|
||||
to the board.
|
||||
</Text>
|
||||
<Text>{scopedT('editorBasics.list.one.label')}</Text>
|
||||
</HStack>
|
||||
<HStack as={ListItem}>
|
||||
<Flex
|
||||
@@ -73,10 +72,7 @@ export const GettingStartedModal = () => {
|
||||
>
|
||||
2
|
||||
</Flex>
|
||||
<Text>
|
||||
You can group blocks together by dropping them below or above
|
||||
each other
|
||||
</Text>
|
||||
<Text>{scopedT('editorBasics.list.two.label')}</Text>
|
||||
</HStack>
|
||||
<HStack as={ListItem}>
|
||||
<Flex
|
||||
@@ -92,7 +88,7 @@ export const GettingStartedModal = () => {
|
||||
>
|
||||
3
|
||||
</Flex>
|
||||
<Text>Connect the groups together</Text>
|
||||
<Text>{scopedT('editorBasics.list.three.label')}</Text>
|
||||
</HStack>
|
||||
<HStack as={ListItem}>
|
||||
<Flex
|
||||
@@ -108,20 +104,16 @@ export const GettingStartedModal = () => {
|
||||
>
|
||||
4
|
||||
</Flex>
|
||||
<Text>
|
||||
Preview your bot by clicking the preview button on the top
|
||||
right
|
||||
</Text>
|
||||
<Text>{scopedT('editorBasics.list.four.label')}</Text>
|
||||
</HStack>
|
||||
</List>
|
||||
</Stack>
|
||||
|
||||
<Text>
|
||||
Feel free to use the bottom-right bubble to reach out if you have
|
||||
any question. I usually answer within the next 24 hours. 😃
|
||||
</Text>
|
||||
<Text>{scopedT('editorBasics.list.label')}</Text>
|
||||
<Stack spacing={4}>
|
||||
<Heading fontSize="xl">See it in action ({`<`} 5 minutes)</Heading>
|
||||
<Heading fontSize="xl">
|
||||
{scopedT('seeAction.label')} ({`<`} {scopedT('seeAction.time')})
|
||||
</Heading>
|
||||
<iframe
|
||||
width="100%"
|
||||
height="315"
|
||||
@@ -135,7 +127,7 @@ export const GettingStartedModal = () => {
|
||||
<AccordionItem>
|
||||
<AccordionButton>
|
||||
<Box flex="1" textAlign="left">
|
||||
Other videos
|
||||
{scopedT('seeAction.item.label')}
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
|
||||
@@ -30,8 +30,10 @@ import { RightPanel, useEditor } from '../providers/EditorProvider'
|
||||
import { useTypebot } from '../providers/TypebotProvider'
|
||||
import { SupportBubble } from '@/components/SupportBubble'
|
||||
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
export const TypebotHeader = () => {
|
||||
const scopedT = useScopedI18n('editor.headers')
|
||||
const router = useRouter()
|
||||
const {
|
||||
typebot,
|
||||
@@ -103,7 +105,7 @@ export const TypebotHeader = () => {
|
||||
variant={router.pathname.includes('/edit') ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
>
|
||||
Flow
|
||||
{scopedT('flowButton.label')}
|
||||
</Button>
|
||||
<Button
|
||||
as={Link}
|
||||
@@ -112,7 +114,7 @@ export const TypebotHeader = () => {
|
||||
variant={router.pathname.endsWith('theme') ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
>
|
||||
Theme
|
||||
{scopedT('themeButton.label')}
|
||||
</Button>
|
||||
<Button
|
||||
as={Link}
|
||||
@@ -121,7 +123,7 @@ export const TypebotHeader = () => {
|
||||
variant={router.pathname.endsWith('settings') ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
>
|
||||
Settings
|
||||
{scopedT('settingsButton.label')}
|
||||
</Button>
|
||||
<Button
|
||||
as={Link}
|
||||
@@ -130,7 +132,7 @@ export const TypebotHeader = () => {
|
||||
variant={router.pathname.endsWith('share') ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
>
|
||||
Share
|
||||
{scopedT('shareButton.label')}
|
||||
</Button>
|
||||
{isDefined(publishedTypebot) && (
|
||||
<Button
|
||||
@@ -140,7 +142,7 @@ export const TypebotHeader = () => {
|
||||
variant={router.pathname.includes('results') ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
>
|
||||
Results
|
||||
{scopedT('resultsButton.label')}
|
||||
</Button>
|
||||
)}
|
||||
</HStack>
|
||||
@@ -219,14 +221,14 @@ export const TypebotHeader = () => {
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
<Button leftIcon={<BuoyIcon />} onClick={handleHelpClick} size="sm">
|
||||
Help
|
||||
{scopedT('helpButton.label')}
|
||||
</Button>
|
||||
</HStack>
|
||||
{isSavingLoading && (
|
||||
<HStack>
|
||||
<Spinner speed="0.7s" size="sm" color="gray.400" />
|
||||
<Text fontSize="sm" color="gray.400">
|
||||
Saving...
|
||||
{scopedT('savingSpinner.label')}
|
||||
</Text>
|
||||
</HStack>
|
||||
)}
|
||||
@@ -241,7 +243,7 @@ export const TypebotHeader = () => {
|
||||
isLoading={isNotDefined(typebot)}
|
||||
size="sm"
|
||||
>
|
||||
Preview
|
||||
{scopedT('previewButton.label')}
|
||||
</Button>
|
||||
)}
|
||||
<PublishButton size="sm" />
|
||||
|
||||
@@ -23,6 +23,7 @@ import { areTypebotsEqual } from '@/features/publish/helpers/areTypebotsEqual'
|
||||
import { isPublished as isPublishedHelper } from '@/features/publish/helpers/isPublished'
|
||||
import { convertPublicTypebotToTypebot } from '@/features/publish/helpers/convertPublicTypebotToTypebot'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
const autoSaveTimeout = 10000
|
||||
|
||||
@@ -79,6 +80,7 @@ export const TypebotProvider = ({
|
||||
children: ReactNode
|
||||
typebotId?: string
|
||||
}) => {
|
||||
const scopedT = useScopedI18n('editor.provider')
|
||||
const { push } = useRouter()
|
||||
const { showToast } = useToast()
|
||||
|
||||
@@ -92,12 +94,15 @@ export const TypebotProvider = ({
|
||||
enabled: isDefined(typebotId),
|
||||
onError: (error) => {
|
||||
if (error.data?.httpStatus === 404) {
|
||||
showToast({ status: 'info', description: "Couldn't find typebot" })
|
||||
showToast({
|
||||
status: 'info',
|
||||
description: scopedT('messages.getTypebotError.description'),
|
||||
})
|
||||
push('/typebots')
|
||||
return
|
||||
}
|
||||
showToast({
|
||||
title: 'Error while fetching typebot. Refresh the page.',
|
||||
title: scopedT('messages.getTypebotError.title'),
|
||||
description: error.message,
|
||||
})
|
||||
},
|
||||
@@ -112,7 +117,7 @@ export const TypebotProvider = ({
|
||||
onError: (error) => {
|
||||
if (error.data?.httpStatus === 404) return
|
||||
showToast({
|
||||
title: 'Error while fetching published typebot',
|
||||
title: scopedT('messages.publishedTypebotError.title'),
|
||||
description: error.message,
|
||||
})
|
||||
},
|
||||
@@ -123,7 +128,7 @@ export const TypebotProvider = ({
|
||||
trpc.typebot.updateTypebot.useMutation({
|
||||
onError: (error) =>
|
||||
showToast({
|
||||
title: 'Error while updating typebot',
|
||||
title: scopedT('messages.updateTypebotError.title'),
|
||||
description: error.message,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
@@ -253,7 +258,10 @@ export const TypebotProvider = ({
|
||||
isPublished,
|
||||
updateTypebot: updateLocalTypebot,
|
||||
restorePublishedTypebot,
|
||||
...groupsActions(setLocalTypebot as SetTypebot),
|
||||
...groupsActions(
|
||||
setLocalTypebot as SetTypebot,
|
||||
scopedT('groups.copy.title')
|
||||
),
|
||||
...blocksAction(setLocalTypebot as SetTypebot),
|
||||
...variablesAction(setLocalTypebot as SetTypebot),
|
||||
...edgesAction(setLocalTypebot as SetTypebot),
|
||||
|
||||
@@ -28,7 +28,10 @@ export type GroupsActions = {
|
||||
deleteGroup: (groupIndex: number) => void
|
||||
}
|
||||
|
||||
const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
|
||||
const groupsActions = (
|
||||
setTypebot: SetTypebot,
|
||||
groupCopyLabel: string
|
||||
): GroupsActions => ({
|
||||
createGroup: ({
|
||||
id,
|
||||
block,
|
||||
@@ -63,11 +66,12 @@ const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
|
||||
produce(typebot, (typebot) => {
|
||||
const group = typebot.groups[groupIndex]
|
||||
const id = createId()
|
||||
|
||||
const newGroup: Group = {
|
||||
...group,
|
||||
title: isEmpty(group.title)
|
||||
? ''
|
||||
: `${parseGroupTitle(group.title)} copy`,
|
||||
: `${parseGroupTitle(group.title)} ${groupCopyLabel}`,
|
||||
id,
|
||||
blocks: group.blocks.map((block) => duplicateBlockDraft(id)(block)),
|
||||
graphCoordinates: {
|
||||
|
||||
@@ -42,12 +42,14 @@ import { ChatwootNodeBody } from '@/features/blocks/integrations/chatwoot/compon
|
||||
import { AbTestNodeBody } from '@/features/blocks/logic/abTest/components/AbTestNodeBody'
|
||||
import { PictureChoiceNode } from '@/features/blocks/inputs/pictureChoice/components/PictureChoiceNode'
|
||||
import { PixelNodeBody } from '@/features/blocks/integrations/pixel/components/PixelNodeBody'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type Props = {
|
||||
block: Block | StartBlock
|
||||
indices: BlockIndices
|
||||
}
|
||||
export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
|
||||
const scopedT = useScopedI18n('editor.blocks.start')
|
||||
switch (block.type) {
|
||||
case BubbleBlockType.TEXT: {
|
||||
return <TextBubbleContent block={block} />
|
||||
@@ -199,7 +201,7 @@ export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
|
||||
return <PixelNodeBody options={block.options} />
|
||||
}
|
||||
case 'start': {
|
||||
return <Text>Start</Text>
|
||||
return <Text>{scopedT('text')}</Text>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { useUser } from '@/features/account/hooks/useUser'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
export const CreateNewTypebotButtons = () => {
|
||||
const scopedT = useScopedI18n('templates.buttons')
|
||||
const { workspace } = useWorkspace()
|
||||
const { user } = useUser()
|
||||
const router = useRouter()
|
||||
@@ -70,7 +72,7 @@ export const CreateNewTypebotButtons = () => {
|
||||
|
||||
return (
|
||||
<VStack maxW="600px" w="full" flex="1" pt="20" spacing={10}>
|
||||
<Heading>Create a new typebot</Heading>
|
||||
<Heading>{scopedT('heading')}</Heading>
|
||||
<Stack w="full" spacing={6}>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -87,7 +89,7 @@ export const CreateNewTypebotButtons = () => {
|
||||
onClick={() => handleCreateSubmit()}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Start from scratch
|
||||
{scopedT('fromScratchButton.label')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -104,7 +106,7 @@ export const CreateNewTypebotButtons = () => {
|
||||
onClick={onOpen}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Start from a template
|
||||
{scopedT('fromTemplateButton.label')}
|
||||
</Button>
|
||||
<ImportTypebotFromFileButton
|
||||
variant="outline"
|
||||
@@ -121,7 +123,7 @@ export const CreateNewTypebotButtons = () => {
|
||||
isLoading={isLoading}
|
||||
onNewTypebot={handleCreateSubmit}
|
||||
>
|
||||
Import a file
|
||||
{scopedT('importFileButton.label')}
|
||||
</ImportTypebotFromFileButton>
|
||||
</Stack>
|
||||
<TemplatesModal
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Typebot, typebotCreateSchema } from '@typebot.io/schemas'
|
||||
import { preprocessTypebot } from '@typebot.io/schemas/features/typebot/helpers/preprocessTypebot'
|
||||
import React, { ChangeEvent } from 'react'
|
||||
import { z } from 'zod'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type Props = {
|
||||
onNewTypebot: (typebot: Typebot) => void
|
||||
@@ -13,6 +14,7 @@ export const ImportTypebotFromFileButton = ({
|
||||
onNewTypebot,
|
||||
...props
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('templates.importFromFileButon')
|
||||
const { showToast } = useToast()
|
||||
|
||||
const handleInputChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -27,7 +29,7 @@ export const ImportTypebotFromFileButton = ({
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
showToast({
|
||||
description: "Failed to parse the file. Are you sure it's a typebot?",
|
||||
description: scopedT('toastError.description'),
|
||||
details: {
|
||||
content: JSON.stringify(err, null, 2),
|
||||
lang: 'json',
|
||||
|
||||
@@ -19,6 +19,7 @@ import { templates } from '../data'
|
||||
import { TemplateProps } from '../types'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { sendRequest } from '@typebot.io/lib'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
@@ -27,6 +28,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
const scopedT = useScopedI18n('templates.modal')
|
||||
const templateCardBackgroundColor = useColorModeValue('white', 'gray.800')
|
||||
const [typebot, setTypebot] = useState<Typebot>()
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<TemplateProps>(
|
||||
@@ -88,7 +90,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
pl="1"
|
||||
color="gray.500"
|
||||
>
|
||||
Marketing
|
||||
{scopedT('menuHeading.marketing')}
|
||||
</Text>
|
||||
{templates
|
||||
.filter((template) => template.category === 'marketing')
|
||||
@@ -110,7 +112,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
<Text>{template.name}</Text>
|
||||
{template.isNew && (
|
||||
<Tag colorScheme="orange" size="sm" flexShrink={0}>
|
||||
New
|
||||
{scopedT('menuHeading.new.tag')}
|
||||
</Tag>
|
||||
)}
|
||||
</HStack>
|
||||
@@ -124,7 +126,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
pl="1"
|
||||
color="gray.500"
|
||||
>
|
||||
Product
|
||||
{scopedT('menuHeading.product')}
|
||||
</Text>
|
||||
{templates
|
||||
.filter((template) => template.category === 'product')
|
||||
@@ -146,7 +148,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
<Text>{template.name}</Text>
|
||||
{template.isNew && (
|
||||
<Tag colorScheme="orange" size="sm" flexShrink={0}>
|
||||
New
|
||||
{scopedT('menuHeading.new.tag')}
|
||||
</Tag>
|
||||
)}
|
||||
</HStack>
|
||||
@@ -160,7 +162,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
pl="1"
|
||||
color="gray.500"
|
||||
>
|
||||
Other
|
||||
{scopedT('menuHeading.other')}
|
||||
</Text>
|
||||
{templates
|
||||
.filter((template) => template.category === undefined)
|
||||
@@ -182,7 +184,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
<Text>{template.name}</Text>
|
||||
{template.isNew && (
|
||||
<Tag colorScheme="orange" size="sm" flexShrink={0}>
|
||||
New
|
||||
{scopedT('menuHeading.new.tag')}
|
||||
</Tag>
|
||||
)}
|
||||
</HStack>
|
||||
@@ -229,7 +231,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
onClick={onUseThisTemplateClick}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Use this template
|
||||
{scopedT('useTemplateButton.label')}
|
||||
</Button>
|
||||
</HStack>
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user