🧑💻 Migrate to Tolgee (#976)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ### Summary by CodeRabbit - Refactor: Transitioned to a new translation library (`@tolgee/react`) across the application, enhancing the localization capabilities and consistency. - New Feature: Introduced a JSON configuration file for application settings, improving customization and flexibility. - Refactor: Updated SVG attribute naming convention in the `WhatsAppLogo` component to align with React standards. - Chore: Adjusted the `.gitignore` file and added a new line at the end. - Documentation: Added instructions for setting up environment variables for the Tolgee i18n contribution dev tool, improving the self-hosting configuration guide. - Style: Updated the `CollaborationMenuButton` to hide the `PopoverContent` component by scaling it down to zero. - Refactor: Simplified error handling logic for fetching and updating typebots in `TypebotProvider.tsx`, improving code readability and maintenance. - Refactor: Removed the dependency on the `parseGroupTitle` function, simplifying the code in several components. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -25,12 +25,12 @@ import { useApiTokens } from '../hooks/useApiTokens'
|
||||
import { ApiTokenFromServer } from '../types'
|
||||
import { parseTimeSince } from '@/helpers/parseTimeSince'
|
||||
import { deleteApiTokenQuery } from '../queries/deleteApiTokenQuery'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { T, useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = { user: User }
|
||||
|
||||
export const ApiTokensList = ({ user }: Props) => {
|
||||
const scopedT = useScopedI18n('account.apiTokens')
|
||||
const { t } = useTranslate()
|
||||
const { showToast } = useToast()
|
||||
const { apiTokens, isLoading, mutate } = useApiTokens({
|
||||
userId: user.id,
|
||||
@@ -57,10 +57,12 @@ export const ApiTokensList = ({ user }: Props) => {
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Heading fontSize="2xl">{scopedT('heading')}</Heading>
|
||||
<Text>{scopedT('description')}</Text>
|
||||
<Heading fontSize="2xl">{t('account.apiTokens.heading')}</Heading>
|
||||
<Text>{t('account.apiTokens.description')}</Text>
|
||||
<Flex justifyContent="flex-end">
|
||||
<Button onClick={onCreateOpen}>{scopedT('createButton.label')}</Button>
|
||||
<Button onClick={onCreateOpen}>
|
||||
{t('account.apiTokens.createButton.label')}
|
||||
</Button>
|
||||
<CreateTokenModal
|
||||
userId={user.id}
|
||||
isOpen={isCreateOpen}
|
||||
@@ -73,8 +75,8 @@ export const ApiTokensList = ({ user }: Props) => {
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{scopedT('table.nameHeader')}</Th>
|
||||
<Th w="130px">{scopedT('table.createdHeader')}</Th>
|
||||
<Th>{t('account.apiTokens.table.nameHeader')}</Th>
|
||||
<Th w="130px">{t('account.apiTokens.table.createdHeader')}</Th>
|
||||
<Th w="0" />
|
||||
</Tr>
|
||||
</Thead>
|
||||
@@ -90,7 +92,7 @@ export const ApiTokensList = ({ user }: Props) => {
|
||||
variant="outline"
|
||||
onClick={() => setDeletingId(token.id)}
|
||||
>
|
||||
{scopedT('deleteButton.label')}
|
||||
{t('account.apiTokens.deleteButton.label')}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
@@ -118,14 +120,17 @@ export const ApiTokensList = ({ user }: Props) => {
|
||||
onClose={() => setDeletingId(undefined)}
|
||||
message={
|
||||
<Text>
|
||||
{scopedT('deleteConfirmationMessage', {
|
||||
tokenName: (
|
||||
<strong>{apiTokens?.find(byId(deletingId))?.name}</strong>
|
||||
),
|
||||
})}
|
||||
<T
|
||||
keyName="account.apiTokens.deleteConfirmationMessage"
|
||||
params={{
|
||||
strong: (
|
||||
<strong>{apiTokens?.find(byId(deletingId))?.name}</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
}
|
||||
confirmButtonLabel={scopedT('deleteButton.label')}
|
||||
confirmButtonLabel={t('account.apiTokens.deleteButton.label')}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import Image from 'next/image'
|
||||
import lightModeIllustration from 'public/images/light-mode.png'
|
||||
import darkModeIllustration from 'public/images/dark-mode.png'
|
||||
import systemModeIllustration from 'public/images/system-mode.png'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
defaultValue: string
|
||||
@@ -18,22 +18,22 @@ type Props = {
|
||||
}
|
||||
|
||||
export const AppearanceRadioGroup = ({ defaultValue, onChange }: Props) => {
|
||||
const scopedT = useScopedI18n('account.preferences.appearance')
|
||||
const { t } = useTranslate()
|
||||
|
||||
const appearanceData = [
|
||||
{
|
||||
value: 'light',
|
||||
label: scopedT('lightLabel'),
|
||||
label: t('account.preferences.appearance.lightLabel'),
|
||||
image: lightModeIllustration,
|
||||
},
|
||||
{
|
||||
value: 'dark',
|
||||
label: scopedT('darkLabel'),
|
||||
label: t('account.preferences.appearance.darkLabel'),
|
||||
image: darkModeIllustration,
|
||||
},
|
||||
{
|
||||
value: 'system',
|
||||
label: scopedT('systemLabel'),
|
||||
label: t('account.preferences.appearance.systemLabel'),
|
||||
image: systemModeIllustration,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CopyButton } from '@/components/CopyButton'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
@@ -33,7 +33,7 @@ export const CreateTokenModal = ({
|
||||
onNewToken,
|
||||
}: Props) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const scopedT = useScopedI18n('account.apiTokens.createModal')
|
||||
const { t } = useTranslate()
|
||||
const [name, setName] = useState('')
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [newTokenValue, setNewTokenValue] = useState<string>()
|
||||
@@ -54,14 +54,18 @@ export const CreateTokenModal = ({
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
{newTokenValue ? scopedT('createdHeading') : scopedT('createHeading')}
|
||||
{newTokenValue
|
||||
? t('account.apiTokens.createModal.createdHeading')
|
||||
: t('account.apiTokens.createModal.createHeading')}
|
||||
</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
{newTokenValue ? (
|
||||
<ModalBody as={Stack} spacing="4">
|
||||
<Text>
|
||||
{scopedT('copyInstruction')}{' '}
|
||||
<strong>{scopedT('securityWarning')}</strong>
|
||||
{t('account.apiTokens.createModal.copyInstruction')}{' '}
|
||||
<strong>
|
||||
{t('account.apiTokens.createModal.securityWarning')}
|
||||
</strong>
|
||||
</Text>
|
||||
<InputGroup size="md">
|
||||
<Input readOnly pr="4.5rem" value={newTokenValue} />
|
||||
@@ -72,10 +76,14 @@ export const CreateTokenModal = ({
|
||||
</ModalBody>
|
||||
) : (
|
||||
<ModalBody as="form" onSubmit={createToken}>
|
||||
<Text mb="4">{scopedT('nameInput.label')}</Text>
|
||||
<Text mb="4">
|
||||
{t('account.apiTokens.createModal.nameInput.label')}
|
||||
</Text>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
placeholder={scopedT('nameInput.placeholder')}
|
||||
placeholder={t(
|
||||
'account.apiTokens.createModal.nameInput.placeholder'
|
||||
)}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</ModalBody>
|
||||
@@ -84,7 +92,7 @@ export const CreateTokenModal = ({
|
||||
<ModalFooter>
|
||||
{newTokenValue ? (
|
||||
<Button onClick={onClose} colorScheme="blue">
|
||||
{scopedT('doneButton.label')}
|
||||
{t('account.apiTokens.createModal.doneButton.label')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -93,7 +101,7 @@ export const CreateTokenModal = ({
|
||||
isLoading={isSubmitting}
|
||||
onClick={createToken}
|
||||
>
|
||||
{scopedT('createButton.label')}
|
||||
{t('account.apiTokens.createModal.createButton.label')}
|
||||
</Button>
|
||||
)}
|
||||
</ModalFooter>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MouseIcon, LaptopIcon } from '@/components/icons'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import {
|
||||
HStack,
|
||||
Radio,
|
||||
@@ -18,18 +18,20 @@ export const GraphNavigationRadioGroup = ({
|
||||
defaultValue,
|
||||
onChange,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('account.preferences.graphNavigation')
|
||||
const { t } = useTranslate()
|
||||
const graphNavigationData = [
|
||||
{
|
||||
value: GraphNavigation.MOUSE,
|
||||
label: scopedT('mouse.label'),
|
||||
description: scopedT('mouse.description'),
|
||||
label: t('account.preferences.graphNavigation.mouse.label'),
|
||||
description: t('account.preferences.graphNavigation.mouse.description'),
|
||||
icon: <MouseIcon boxSize="35px" />,
|
||||
},
|
||||
{
|
||||
value: GraphNavigation.TRACKPAD,
|
||||
label: scopedT('trackpad.label'),
|
||||
description: scopedT('trackpad.description'),
|
||||
label: t('account.preferences.graphNavigation.trackpad.label'),
|
||||
description: t(
|
||||
'account.preferences.graphNavigation.trackpad.description'
|
||||
),
|
||||
icon: <LaptopIcon boxSize="35px" />,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -5,10 +5,10 @@ import { ApiTokensList } from './ApiTokensList'
|
||||
import { UploadButton } from '@/components/ImageUploadContent/UploadButton'
|
||||
import { useUser } from '../hooks/useUser'
|
||||
import { TextInput } from '@/components/inputs/TextInput'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
export const MyAccountForm = () => {
|
||||
const scopedT = useScopedI18n('account.myAccount')
|
||||
const { t } = useTranslate()
|
||||
const { user, updateUser } = useUser()
|
||||
const [name, setName] = useState(user?.name ?? '')
|
||||
const [email, setEmail] = useState(user?.email ?? '')
|
||||
@@ -47,11 +47,11 @@ export const MyAccountForm = () => {
|
||||
leftIcon={<UploadIcon />}
|
||||
onFileUploaded={handleFileUploaded}
|
||||
>
|
||||
{scopedT('changePhotoButton.label')}
|
||||
{t('account.myAccount.changePhotoButton.label')}
|
||||
</UploadButton>
|
||||
)}
|
||||
<Text color="gray.500" fontSize="sm">
|
||||
{scopedT('changePhotoButton.specification')}
|
||||
{t('account.myAccount.changePhotoButton.specification')}
|
||||
</Text>
|
||||
</Stack>
|
||||
</HStack>
|
||||
@@ -59,17 +59,17 @@ export const MyAccountForm = () => {
|
||||
<TextInput
|
||||
defaultValue={name}
|
||||
onChange={handleNameChange}
|
||||
label={scopedT('nameInput.label')}
|
||||
label={t('account.myAccount.nameInput.label')}
|
||||
withVariableButton={false}
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
<Tooltip label={scopedT('emailInput.disabledTooltip')}>
|
||||
<Tooltip label={t('account.myAccount.emailInput.disabledTooltip')}>
|
||||
<span>
|
||||
<TextInput
|
||||
type="email"
|
||||
defaultValue={email}
|
||||
onChange={handleEmailChange}
|
||||
label={scopedT('emailInput.label')}
|
||||
label={t('account.myAccount.emailInput.label')}
|
||||
withVariableButton={false}
|
||||
debounceTimeout={0}
|
||||
isDisabled
|
||||
|
||||
@@ -14,9 +14,10 @@ import React, { useEffect } from 'react'
|
||||
import { GraphNavigationRadioGroup } from './GraphNavigationRadioGroup'
|
||||
import { AppearanceRadioGroup } from './AppearanceRadioGroup'
|
||||
import { useUser } from '../hooks/useUser'
|
||||
import { useChangeLocale, useCurrentLocale, useScopedI18n } from '@/locales'
|
||||
import { ChevronDownIcon } from '@/components/icons'
|
||||
import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
|
||||
import { useTranslate, useTolgee } from '@tolgee/react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
const localeHumanReadable = {
|
||||
en: 'English',
|
||||
@@ -27,11 +28,11 @@ const localeHumanReadable = {
|
||||
} as const
|
||||
|
||||
export const UserPreferencesForm = () => {
|
||||
const scopedT = useScopedI18n('account.preferences')
|
||||
const { getLanguage } = useTolgee()
|
||||
const router = useRouter()
|
||||
const { t } = useTranslate()
|
||||
const { colorMode } = useColorMode()
|
||||
const { user, updateUser } = useUser()
|
||||
const changeLocale = useChangeLocale()
|
||||
const currentLocale = useCurrentLocale()
|
||||
|
||||
useEffect(() => {
|
||||
if (!user?.graphNavigation)
|
||||
@@ -47,17 +48,23 @@ export const UserPreferencesForm = () => {
|
||||
}
|
||||
|
||||
const updateLocale = (locale: keyof typeof localeHumanReadable) => () => {
|
||||
changeLocale(locale)
|
||||
document.cookie = `NEXT_LOCALE=${locale}; path=/; max-age=31536000`
|
||||
router.replace(router.pathname, undefined, { locale })
|
||||
}
|
||||
|
||||
const currentLanguage = getLanguage()
|
||||
|
||||
return (
|
||||
<Stack spacing={12}>
|
||||
<HStack spacing={4}>
|
||||
<Heading size="md">{scopedT('language.heading')}</Heading>
|
||||
<Heading size="md">{t('account.preferences.language.heading')}</Heading>
|
||||
<Menu>
|
||||
<MenuButton as={Button} rightIcon={<ChevronDownIcon />}>
|
||||
{localeHumanReadable[currentLocale]}
|
||||
{currentLanguage
|
||||
? localeHumanReadable[
|
||||
currentLanguage as keyof typeof localeHumanReadable
|
||||
]
|
||||
: 'Loading...'}
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
{Object.keys(localeHumanReadable).map((locale) => (
|
||||
@@ -76,19 +83,25 @@ export const UserPreferencesForm = () => {
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
{currentLocale !== 'en' && (
|
||||
<MoreInfoTooltip>{scopedT('language.tooltip')}</MoreInfoTooltip>
|
||||
{currentLanguage !== 'en' && (
|
||||
<MoreInfoTooltip>
|
||||
{t('account.preferences.language.tooltip')}
|
||||
</MoreInfoTooltip>
|
||||
)}
|
||||
</HStack>
|
||||
<Stack spacing={6}>
|
||||
<Heading size="md">{scopedT('graphNavigation.heading')}</Heading>
|
||||
<Heading size="md">
|
||||
{t('account.preferences.graphNavigation.heading')}
|
||||
</Heading>
|
||||
<GraphNavigationRadioGroup
|
||||
defaultValue={user?.graphNavigation ?? GraphNavigation.TRACKPAD}
|
||||
onChange={changeGraphNavigation}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing={6}>
|
||||
<Heading size="md">{scopedT('appearance.heading')}</Heading>
|
||||
<Heading size="md">
|
||||
{t('account.preferences.appearance.heading')}
|
||||
</Heading>
|
||||
<AppearanceRadioGroup
|
||||
defaultValue={
|
||||
user?.preferredAppAppearance
|
||||
|
||||
@@ -12,12 +12,12 @@ import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
|
||||
import { Graph } from '@/features/graph/components/Graph'
|
||||
import { GraphProvider } from '@/features/graph/providers/GraphProvider'
|
||||
import { GroupsCoordinatesProvider } from '@/features/graph/providers/GroupsCoordinateProvider'
|
||||
import { useI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
|
||||
export const AnalyticsGraphContainer = ({ stats }: { stats?: Stats }) => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const { typebot, publishedTypebot } = useTypebot()
|
||||
const { data } = trpc.analytics.getTotalAnswersInBlocks.useQuery(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import {
|
||||
GridProps,
|
||||
SimpleGrid,
|
||||
@@ -22,13 +22,13 @@ export const StatsCards = ({
|
||||
stats,
|
||||
...props
|
||||
}: { stats?: Stats } & GridProps) => {
|
||||
const scopedT = useScopedI18n('analytics')
|
||||
const { t } = useTranslate()
|
||||
const bg = useColorModeValue('white', 'gray.900')
|
||||
|
||||
return (
|
||||
<SimpleGrid columns={{ base: 1, md: 3 }} spacing="6" {...props}>
|
||||
<Stat bgColor={bg} p="4" rounded="md" boxShadow="md">
|
||||
<StatLabel>{scopedT('viewsLabel')}</StatLabel>
|
||||
<StatLabel>{t('analytics.viewsLabel')}</StatLabel>
|
||||
{stats ? (
|
||||
<StatNumber>{stats.totalViews}</StatNumber>
|
||||
) : (
|
||||
@@ -36,7 +36,7 @@ export const StatsCards = ({
|
||||
)}
|
||||
</Stat>
|
||||
<Stat bgColor={bg} p="4" rounded="md" boxShadow="md">
|
||||
<StatLabel>{scopedT('startsLabel')}</StatLabel>
|
||||
<StatLabel>{t('analytics.startsLabel')}</StatLabel>
|
||||
{stats ? (
|
||||
<StatNumber>{stats.totalStarts}</StatNumber>
|
||||
) : (
|
||||
@@ -44,10 +44,10 @@ export const StatsCards = ({
|
||||
)}
|
||||
</Stat>
|
||||
<Stat bgColor={bg} p="4" rounded="md" boxShadow="md">
|
||||
<StatLabel>{scopedT('completionRateLabel')}</StatLabel>
|
||||
<StatLabel>{t('analytics.completionRateLabel')}</StatLabel>
|
||||
{stats ? (
|
||||
<StatNumber>
|
||||
{computeCompletionRate(scopedT('notAvailableLabel'))(
|
||||
{computeCompletionRate(t('analytics.notAvailableLabel'))(
|
||||
stats.totalCompleted,
|
||||
stats.totalStarts
|
||||
)}
|
||||
|
||||
@@ -13,13 +13,13 @@ import { useRouter } from 'next/router'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import confetti from 'canvas-confetti'
|
||||
import { useUser } from '@/features/account/hooks/useUser'
|
||||
import { useI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { env } from '@typebot.io/env'
|
||||
|
||||
const totalSteps = 5
|
||||
|
||||
export const OnboardingPage = () => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
const { push, replace } = useRouter()
|
||||
const confettiCanvaContainer = useRef<HTMLCanvasElement | null>(null)
|
||||
const confettiCanon = useRef<confetti.CreateTypes>()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { Alert } from '@chakra-ui/react'
|
||||
|
||||
type Props = {
|
||||
@@ -6,16 +6,16 @@ type Props = {
|
||||
}
|
||||
|
||||
export const SignInError = ({ error }: Props) => {
|
||||
const scopedT = useScopedI18n('auth.error')
|
||||
const { t } = useTranslate()
|
||||
const errors: Record<string, string> = {
|
||||
Signin: scopedT('default'),
|
||||
OAuthSignin: scopedT('default'),
|
||||
OAuthCallback: scopedT('default'),
|
||||
OAuthCreateAccount: scopedT('email'),
|
||||
EmailCreateAccount: scopedT('default'),
|
||||
Callback: scopedT('default'),
|
||||
OAuthAccountNotLinked: scopedT('oauthNotLinked'),
|
||||
default: scopedT('unknown'),
|
||||
Signin: t('auth.error.default'),
|
||||
OAuthSignin: t('auth.error.default'),
|
||||
OAuthCallback: t('auth.error.default'),
|
||||
OAuthCreateAccount: t('auth.error.email'),
|
||||
EmailCreateAccount: t('auth.error.default'),
|
||||
Callback: t('auth.error.default'),
|
||||
OAuthAccountNotLinked: t('auth.error.oauthNotLinked'),
|
||||
default: t('auth.error.unknown'),
|
||||
}
|
||||
return (
|
||||
<Alert status="error" variant="solid" rounded="md">
|
||||
|
||||
@@ -27,7 +27,7 @@ import { BuiltInProviderType } from 'next-auth/providers'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { TextLink } from '@/components/TextLink'
|
||||
import { SignInError } from './SignInError'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
defaultEmail?: string
|
||||
@@ -35,7 +35,7 @@ type Props = {
|
||||
export const SignInForm = ({
|
||||
defaultEmail,
|
||||
}: Props & HTMLChakraProps<'form'>) => {
|
||||
const scopedT = useScopedI18n('auth')
|
||||
const { t } = useTranslate()
|
||||
const router = useRouter()
|
||||
const { status } = useSession()
|
||||
const [authLoading, setAuthLoading] = useState(false)
|
||||
@@ -79,8 +79,8 @@ export const SignInForm = ({
|
||||
})
|
||||
if (response?.error) {
|
||||
showToast({
|
||||
title: scopedT('signinErrorToast.title'),
|
||||
description: scopedT('signinErrorToast.description'),
|
||||
title: t('auth.signinErrorToast.title'),
|
||||
description: t('auth.signinErrorToast.description'),
|
||||
})
|
||||
} else {
|
||||
setIsMagicLinkSent(true)
|
||||
@@ -88,7 +88,7 @@ export const SignInForm = ({
|
||||
} catch {
|
||||
showToast({
|
||||
status: 'info',
|
||||
description: scopedT('signinErrorToast.tooManyRequests'),
|
||||
description: t('auth.signinErrorToast.tooManyRequests'),
|
||||
})
|
||||
}
|
||||
setAuthLoading(false)
|
||||
@@ -98,12 +98,12 @@ export const SignInForm = ({
|
||||
if (hasNoAuthProvider)
|
||||
return (
|
||||
<Text>
|
||||
{scopedT('noProvider.preLink')}{' '}
|
||||
{t('auth.noProvider.preLink')}{' '}
|
||||
<TextLink
|
||||
href="https://docs.typebot.io/self-hosting/configuration"
|
||||
isExternal
|
||||
>
|
||||
{scopedT('noProvider.link')}
|
||||
{t('auth.noProvider.link')}
|
||||
</TextLink>
|
||||
</Text>
|
||||
)
|
||||
@@ -114,9 +114,7 @@ export const SignInForm = ({
|
||||
<SocialLoginButtons providers={providers} />
|
||||
{providers?.email && (
|
||||
<>
|
||||
<DividerWithText mt="6">
|
||||
{scopedT('orEmailLabel')}
|
||||
</DividerWithText>
|
||||
<DividerWithText mt="6">{t('auth.orEmailLabel')}</DividerWithText>
|
||||
<HStack as="form" onSubmit={handleEmailSubmit}>
|
||||
<Input
|
||||
name="email"
|
||||
@@ -134,7 +132,7 @@ export const SignInForm = ({
|
||||
}
|
||||
isDisabled={isMagicLinkSent}
|
||||
>
|
||||
{scopedT('emailSubmitButton.label')}
|
||||
{t('auth.emailSubmitButton.label')}
|
||||
</Button>
|
||||
</HStack>
|
||||
</>
|
||||
@@ -150,8 +148,8 @@ export const SignInForm = ({
|
||||
<HStack>
|
||||
<AlertIcon />
|
||||
<Stack spacing={1}>
|
||||
<Text fontWeight="semibold">{scopedT('magicLink.title')}</Text>
|
||||
<Text fontSize="sm">{scopedT('magicLink.description')}</Text>
|
||||
<Text fontWeight="semibold">{t('auth.magicLink.title')}</Text>
|
||||
<Text fontSize="sm">{t('auth.magicLink.description')}</Text>
|
||||
</Stack>
|
||||
</HStack>
|
||||
</Alert>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Seo } from '@/components/Seo'
|
||||
import { TextLink } from '@/components/TextLink'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { T, useTranslate } from '@tolgee/react'
|
||||
import { VStack, Heading, Text } from '@chakra-ui/react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { SignInForm } from './SignInForm'
|
||||
@@ -11,7 +11,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const SignInPage = ({ type }: Props) => {
|
||||
const scopedT = useScopedI18n('auth')
|
||||
const { t } = useTranslate()
|
||||
const { query } = useRouter()
|
||||
|
||||
return (
|
||||
@@ -19,8 +19,8 @@ export const SignInPage = ({ type }: Props) => {
|
||||
<Seo
|
||||
title={
|
||||
type === 'signin'
|
||||
? scopedT('signin.heading')
|
||||
: scopedT('register.heading')
|
||||
? t('auth.signin.heading')
|
||||
: t('auth.register.heading')
|
||||
}
|
||||
/>
|
||||
<Heading
|
||||
@@ -29,39 +29,36 @@ export const SignInPage = ({ type }: Props) => {
|
||||
}}
|
||||
>
|
||||
{type === 'signin'
|
||||
? scopedT('signin.heading')
|
||||
: scopedT('register.heading')}
|
||||
? t('auth.signin.heading')
|
||||
: t('auth.register.heading')}
|
||||
</Heading>
|
||||
{type === 'signin' ? (
|
||||
<Text>
|
||||
{scopedT('signin.noAccountLabel.preLink')}{' '}
|
||||
{t('auth.signin.noAccountLabel.preLink')}{' '}
|
||||
<TextLink href="/register">
|
||||
{scopedT('signin.noAccountLabel.link')}
|
||||
{t('auth.signin.noAccountLabel.link')}
|
||||
</TextLink>
|
||||
</Text>
|
||||
) : (
|
||||
<Text>
|
||||
{scopedT('register.alreadyHaveAccountLabel.preLink')}{' '}
|
||||
{t('auth.register.alreadyHaveAccountLabel.preLink')}{' '}
|
||||
<TextLink href="/signin">
|
||||
{scopedT('register.alreadyHaveAccountLabel.link')}
|
||||
{t('auth.register.alreadyHaveAccountLabel.link')}
|
||||
</TextLink>
|
||||
</Text>
|
||||
)}
|
||||
<SignInForm defaultEmail={query.g?.toString()} />
|
||||
{type === 'signup' ? (
|
||||
<Text fontSize="sm" maxW="400px" textAlign="center">
|
||||
{scopedT('register.aggreeToTerms', {
|
||||
termsOfService: (
|
||||
<TextLink href={'https://typebot.io/terms-of-service'}>
|
||||
{scopedT('register.termsOfService')}
|
||||
</TextLink>
|
||||
),
|
||||
privacyPolicy: (
|
||||
<TextLink href={'https://typebot.io/privacy-policies'}>
|
||||
{scopedT('register.privacyPolicy')}
|
||||
</TextLink>
|
||||
),
|
||||
})}
|
||||
<T
|
||||
keyName="auth.register.aggreeToTerms"
|
||||
params={{
|
||||
terms: <TextLink href={'https://typebot.io/terms-of-service'} />,
|
||||
privacy: (
|
||||
<TextLink href={'https://typebot.io/privacy-policies'} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
) : null}
|
||||
</VStack>
|
||||
|
||||
@@ -15,7 +15,7 @@ import { omit } from '@typebot.io/lib'
|
||||
import { AzureAdLogo } from '@/components/logos/AzureAdLogo'
|
||||
import { FacebookLogo } from '@/components/logos/FacebookLogo'
|
||||
import { GitlabLogo } from '@/components/logos/GitlabLogo'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
providers:
|
||||
@@ -24,7 +24,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const SocialLoginButtons = ({ providers }: Props) => {
|
||||
const scopedT = useScopedI18n('auth.socialLogin')
|
||||
const { t } = useTranslate()
|
||||
const { query } = useRouter()
|
||||
const { status } = useSession()
|
||||
const [authLoading, setAuthLoading] =
|
||||
@@ -65,7 +65,7 @@ export const SocialLoginButtons = ({ providers }: Props) => {
|
||||
}
|
||||
variant="outline"
|
||||
>
|
||||
{scopedT('githubButton.label')}
|
||||
{t('auth.socialLogin.githubButton.label')}
|
||||
</Button>
|
||||
)}
|
||||
{providers?.google && (
|
||||
@@ -79,7 +79,7 @@ export const SocialLoginButtons = ({ providers }: Props) => {
|
||||
}
|
||||
variant="outline"
|
||||
>
|
||||
{scopedT('googleButton.label')}
|
||||
{t('auth.socialLogin.googleButton.label')}
|
||||
</Button>
|
||||
)}
|
||||
{providers?.facebook && (
|
||||
@@ -93,7 +93,7 @@ export const SocialLoginButtons = ({ providers }: Props) => {
|
||||
}
|
||||
variant="outline"
|
||||
>
|
||||
{scopedT('facebookButton.label')}
|
||||
{t('auth.socialLogin.facebookButton.label')}
|
||||
</Button>
|
||||
)}
|
||||
{providers?.gitlab && (
|
||||
@@ -107,7 +107,7 @@ export const SocialLoginButtons = ({ providers }: Props) => {
|
||||
}
|
||||
variant="outline"
|
||||
>
|
||||
{scopedT('gitlabButton.label', {
|
||||
{t('auth.socialLogin.gitlabButton.label', {
|
||||
gitlabProviderName: providers.gitlab.name,
|
||||
})}
|
||||
</Button>
|
||||
@@ -123,7 +123,7 @@ export const SocialLoginButtons = ({ providers }: Props) => {
|
||||
}
|
||||
variant="outline"
|
||||
>
|
||||
{scopedT('azureButton.label', {
|
||||
{t('auth.socialLogin.azureButton.label', {
|
||||
azureProviderName: providers['azure-ad'].name,
|
||||
})}
|
||||
</Button>
|
||||
@@ -137,7 +137,7 @@ export const SocialLoginButtons = ({ providers }: Props) => {
|
||||
}
|
||||
variant="outline"
|
||||
>
|
||||
{scopedT('customButton.label', {
|
||||
{t('auth.socialLogin.customButton.label', {
|
||||
customProviderName: providers['custom-oauth'].name,
|
||||
})}
|
||||
</Button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { Button, ButtonProps, Link } from '@chakra-ui/react'
|
||||
|
||||
type Props = {
|
||||
@@ -8,7 +8,7 @@ type Props = {
|
||||
} & Pick<ButtonProps, 'colorScheme'>
|
||||
|
||||
export const BillingPortalButton = ({ workspaceId, colorScheme }: Props) => {
|
||||
const scopedT = useScopedI18n('billing')
|
||||
const { t } = useTranslate()
|
||||
const { showToast } = useToast()
|
||||
const { data } = trpc.billing.getBillingPortalUrl.useQuery(
|
||||
{
|
||||
@@ -29,7 +29,7 @@ export const BillingPortalButton = ({ workspaceId, colorScheme }: Props) => {
|
||||
isLoading={!data}
|
||||
colorScheme={colorScheme}
|
||||
>
|
||||
{scopedT('billingPortalButton.label')}
|
||||
{t('billing.billingPortalButton.label')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ParentModalProvider } from '@/features/graph/providers/ParentModalProvi
|
||||
import { useUser } from '@/features/account/hooks/useUser'
|
||||
import { StarterPlanPricingCard } from './StarterPlanPricingCard'
|
||||
import { ProPlanPricingCard } from './ProPlanPricingCard'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { StripeClimateLogo } from './StripeClimateLogo'
|
||||
import { guessIfUserIsEuropean } from '@typebot.io/lib/billing/guessIfUserIsEuropean'
|
||||
|
||||
@@ -20,7 +20,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const ChangePlanForm = ({ workspace, excludedPlans }: Props) => {
|
||||
const scopedT = useScopedI18n('billing')
|
||||
const { t } = useTranslate()
|
||||
|
||||
const { user } = useUser()
|
||||
const { showToast } = useToast()
|
||||
@@ -49,7 +49,7 @@ export const ChangePlanForm = ({ workspace, excludedPlans }: Props) => {
|
||||
trpcContext.workspace.getWorkspace.invalidate()
|
||||
showToast({
|
||||
status: 'success',
|
||||
description: scopedT('updateSuccessToast.description', {
|
||||
description: t('billing.updateSuccessToast.description', {
|
||||
plan: workspace?.plan,
|
||||
}),
|
||||
})
|
||||
@@ -87,9 +87,9 @@ export const ChangePlanForm = ({ workspace, excludedPlans }: Props) => {
|
||||
<HStack maxW="500px">
|
||||
<StripeClimateLogo />
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
{scopedT('contribution.preLink')}{' '}
|
||||
{t('billing.contribution.preLink')}{' '}
|
||||
<TextLink href="https://climate.stripe.com/5VCRAq" isExternal>
|
||||
{scopedT('contribution.link')}
|
||||
{t('billing.contribution.link')}
|
||||
</TextLink>
|
||||
</Text>
|
||||
</HStack>
|
||||
@@ -128,9 +128,9 @@ export const ChangePlanForm = ({ workspace, excludedPlans }: Props) => {
|
||||
)}
|
||||
|
||||
<Text color="gray.500">
|
||||
{scopedT('customLimit.preLink')}{' '}
|
||||
{t('billing.customLimit.preLink')}{' '}
|
||||
<TextLink href={'https://typebot.io/enterprise-lead-form'} isExternal>
|
||||
{scopedT('customLimit.link')}
|
||||
{t('billing.customLimit.link')}
|
||||
</TextLink>
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AlertInfo } from '@/components/AlertInfo'
|
||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { useI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import {
|
||||
Modal,
|
||||
ModalBody,
|
||||
@@ -26,7 +26,7 @@ export const ChangePlanModal = ({
|
||||
type,
|
||||
excludedPlans,
|
||||
}: ChangePlanModalProps) => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
const { workspace } = useWorkspace()
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
Thead,
|
||||
Tr,
|
||||
} from '@chakra-ui/react'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { proChatTiers } from '@typebot.io/lib/billing/constants'
|
||||
import { formatPrice } from '@typebot.io/lib/billing/formatPrice'
|
||||
|
||||
@@ -25,12 +26,14 @@ type Props = {
|
||||
}
|
||||
|
||||
export const ChatsProTiersModal = ({ isOpen, onClose }: Props) => {
|
||||
const { t } = useTranslate()
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
<Heading size="lg">Chats pricing table</Heading>
|
||||
<Heading size="lg">{t('billing.tiersModal.heading')}</Heading>
|
||||
</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody as={Stack} spacing="6">
|
||||
|
||||
@@ -12,14 +12,14 @@ import { PlanTag } from './PlanTag'
|
||||
import { BillingPortalButton } from './BillingPortalButton'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { Workspace } from '@typebot.io/schemas'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
workspace: Pick<Workspace, 'id' | 'plan' | 'stripeId'>
|
||||
}
|
||||
|
||||
export const CurrentSubscriptionSummary = ({ workspace }: Props) => {
|
||||
const scopedT = useScopedI18n('billing.currentSubscription')
|
||||
const { t } = useTranslate()
|
||||
|
||||
const { data } = trpc.billing.getSubscription.useQuery({
|
||||
workspaceId: workspace.id,
|
||||
@@ -31,13 +31,15 @@ export const CurrentSubscriptionSummary = ({ workspace }: Props) => {
|
||||
|
||||
return (
|
||||
<Stack spacing="4">
|
||||
<Heading fontSize="3xl">{scopedT('heading')}</Heading>
|
||||
<Heading fontSize="3xl">
|
||||
{t('billing.currentSubscription.heading')}
|
||||
</Heading>
|
||||
<HStack data-testid="current-subscription">
|
||||
<Text>{scopedT('subheading')} </Text>
|
||||
<Text>{t('billing.currentSubscription.subheading')} </Text>
|
||||
<PlanTag plan={workspace.plan} />
|
||||
{data?.subscription?.cancelDate && (
|
||||
<Text fontSize="sm">
|
||||
({scopedT('cancelDate')}{' '}
|
||||
({t('billing.currentSubscription.cancelDate')}{' '}
|
||||
{data.subscription.cancelDate.toDateString()})
|
||||
</Text>
|
||||
)}
|
||||
@@ -45,7 +47,7 @@ export const CurrentSubscriptionSummary = ({ workspace }: Props) => {
|
||||
{data?.subscription?.status === 'past_due' && (
|
||||
<Alert fontSize="sm" status="error">
|
||||
<AlertIcon />
|
||||
{scopedT('pastDueAlert')}
|
||||
{t('billing.currentSubscription.pastDueAlert')}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
|
||||
@@ -18,14 +18,14 @@ import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
workspaceId: string
|
||||
}
|
||||
|
||||
export const InvoicesList = ({ workspaceId }: Props) => {
|
||||
const scopedT = useScopedI18n('billing.invoices')
|
||||
const { t } = useTranslate()
|
||||
const { showToast } = useToast()
|
||||
const { data, status } = trpc.billing.listInvoices.useQuery(
|
||||
{
|
||||
@@ -40,9 +40,9 @@ export const InvoicesList = ({ workspaceId }: Props) => {
|
||||
|
||||
return (
|
||||
<Stack spacing={6}>
|
||||
<Heading fontSize="3xl">{scopedT('heading')}</Heading>
|
||||
<Heading fontSize="3xl">{t('billing.invoices.heading')}</Heading>
|
||||
{data?.invoices.length === 0 && status !== 'loading' ? (
|
||||
<Text>{scopedT('empty')}</Text>
|
||||
<Text>{t('billing.invoices.empty')}</Text>
|
||||
) : (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
@@ -50,8 +50,8 @@ export const InvoicesList = ({ workspaceId }: Props) => {
|
||||
<Tr>
|
||||
<Th w="0" />
|
||||
<Th>#</Th>
|
||||
<Th>{scopedT('paidAt')}</Th>
|
||||
<Th>{scopedT('subtotal')}</Th>
|
||||
<Th>{t('billing.invoices.paidAt')}</Th>
|
||||
<Th>{t('billing.invoices.subtotal')}</Th>
|
||||
<Th w="0" />
|
||||
</Tr>
|
||||
</Thead>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { useRouter } from 'next/router'
|
||||
import React, { FormEvent, useState } from 'react'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { taxIdTypes } from '../taxIdTypes'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
export type PreCheckoutModalProps = {
|
||||
selectedSubscription:
|
||||
@@ -47,7 +47,7 @@ export const PreCheckoutModal = ({
|
||||
existingEmail,
|
||||
onClose,
|
||||
}: PreCheckoutModalProps) => {
|
||||
const scopedT = useScopedI18n('billing.preCheckoutModal')
|
||||
const { t } = useTranslate()
|
||||
const { ref } = useParentModal()
|
||||
const vatValueInputRef = React.useRef<HTMLInputElement>(null)
|
||||
const router = useRouter()
|
||||
@@ -131,7 +131,7 @@ export const PreCheckoutModal = ({
|
||||
<Stack as="form" spacing="4" onSubmit={goToCheckout}>
|
||||
<TextInput
|
||||
isRequired
|
||||
label={scopedT('companyInput.label')}
|
||||
label={t('billing.preCheckoutModal.companyInput.label')}
|
||||
defaultValue={customer.company}
|
||||
onChange={updateCustomerCompany}
|
||||
withVariableButton={false}
|
||||
@@ -140,17 +140,17 @@ export const PreCheckoutModal = ({
|
||||
<TextInput
|
||||
isRequired
|
||||
type="email"
|
||||
label={scopedT('emailInput.label')}
|
||||
label={t('billing.preCheckoutModal.emailInput.label')}
|
||||
defaultValue={customer.email}
|
||||
onChange={updateCustomerEmail}
|
||||
withVariableButton={false}
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
<FormControl>
|
||||
<FormLabel>{scopedT('taxId.label')}</FormLabel>
|
||||
<FormLabel>{t('billing.preCheckoutModal.taxId.label')}</FormLabel>
|
||||
<HStack>
|
||||
<Select
|
||||
placeholder={scopedT('taxId.placeholder')}
|
||||
placeholder={t('billing.preCheckoutModal.taxId.placeholder')}
|
||||
items={vatCodeLabels}
|
||||
isPopoverMatchingInputWidth={false}
|
||||
onSelect={updateVatType}
|
||||
@@ -171,7 +171,7 @@ export const PreCheckoutModal = ({
|
||||
colorScheme="blue"
|
||||
isDisabled={customer.company === '' || customer.email === ''}
|
||||
>
|
||||
{scopedT('submitButton.label')}
|
||||
{t('billing.preCheckoutModal.submitButton.label')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</ModalBody>
|
||||
|
||||
@@ -14,10 +14,10 @@ import {
|
||||
import { Plan } from '@typebot.io/prisma'
|
||||
import { FeaturesList } from './FeaturesList'
|
||||
import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
|
||||
import { useI18n, useScopedI18n } from '@/locales'
|
||||
import { formatPrice } from '@typebot.io/lib/billing/formatPrice'
|
||||
import { ChatsProTiersModal } from './ChatsProTiersModal'
|
||||
import { prices } from '@typebot.io/lib/billing/constants'
|
||||
import { T, useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
currentPlan: Plan
|
||||
@@ -32,12 +32,12 @@ export const ProPlanPricingCard = ({
|
||||
isLoading,
|
||||
onPayClick,
|
||||
}: Props) => {
|
||||
const t = useI18n()
|
||||
const scopedT = useScopedI18n('billing.pricingCard')
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const { t } = useTranslate()
|
||||
|
||||
const getButtonLabel = () => {
|
||||
if (currentPlan === Plan.PRO) return scopedT('upgradeButton.current')
|
||||
if (currentPlan === Plan.PRO)
|
||||
return t('billing.pricingCard.upgradeButton.current')
|
||||
return t('upgrade')
|
||||
}
|
||||
|
||||
@@ -65,38 +65,41 @@ export const ProPlanPricingCard = ({
|
||||
fontWeight="semibold"
|
||||
style={{ marginTop: 0 }}
|
||||
>
|
||||
{scopedT('pro.mostPopularLabel')}
|
||||
{t('billing.pricingCard.pro.mostPopularLabel')}
|
||||
</Tag>
|
||||
</Flex>
|
||||
<Stack justifyContent="space-between" h="full">
|
||||
<Stack spacing="4" mt={2}>
|
||||
<Heading fontSize="2xl">
|
||||
{scopedT('heading', {
|
||||
plan: (
|
||||
<chakra.span
|
||||
color={useColorModeValue('blue.400', 'blue.300')}
|
||||
>
|
||||
Pro
|
||||
</chakra.span>
|
||||
),
|
||||
})}
|
||||
<T
|
||||
keyName="billing.pricingCard.heading"
|
||||
params={{
|
||||
strong: (
|
||||
<chakra.span
|
||||
color={useColorModeValue('blue.400', 'blue.300')}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Heading>
|
||||
<Text>{scopedT('pro.description')}</Text>
|
||||
<Text>{t('billing.pricingCard.pro.description')}</Text>
|
||||
</Stack>
|
||||
<Stack spacing="8">
|
||||
<Stack spacing="4">
|
||||
<Heading>
|
||||
{formatPrice(prices.PRO, { currency })}
|
||||
<chakra.span fontSize="md">{scopedT('perMonth')}</chakra.span>
|
||||
<chakra.span fontSize="md">
|
||||
{t('billing.pricingCard.perMonth')}
|
||||
</chakra.span>
|
||||
</Heading>
|
||||
<Text fontWeight="bold">
|
||||
<Tooltip
|
||||
label={
|
||||
<FeaturesList
|
||||
features={[
|
||||
scopedT('starter.brandingRemoved'),
|
||||
scopedT('starter.fileUploadBlock'),
|
||||
scopedT('starter.createFolders'),
|
||||
t('billing.pricingCard.starter.brandingRemoved'),
|
||||
t('billing.pricingCard.starter.fileUploadBlock'),
|
||||
t('billing.pricingCard.starter.createFolders'),
|
||||
]}
|
||||
spacing="0"
|
||||
/>
|
||||
@@ -105,19 +108,21 @@ export const ProPlanPricingCard = ({
|
||||
placement="top"
|
||||
>
|
||||
<chakra.span textDecoration="underline" cursor="pointer">
|
||||
{scopedT('pro.everythingFromStarter')}
|
||||
{t('billing.pricingCard.pro.everythingFromStarter')}
|
||||
</chakra.span>
|
||||
</Tooltip>
|
||||
{scopedT('plus')}
|
||||
{t('billing.pricingCard.plus')}
|
||||
</Text>
|
||||
<FeaturesList
|
||||
features={[
|
||||
scopedT('pro.includedSeats'),
|
||||
t('billing.pricingCard.pro.includedSeats'),
|
||||
<Stack key="starter-chats" spacing={1}>
|
||||
<HStack key="test">
|
||||
<Text>10,000 {scopedT('chatsPerMonth')}</Text>
|
||||
<Text>
|
||||
10,000 {t('billing.pricingCard.chatsPerMonth')}
|
||||
</Text>
|
||||
<MoreInfoTooltip>
|
||||
{scopedT('chatsTooltip')}
|
||||
{t('billing.pricingCard.chatsTooltip')}
|
||||
</MoreInfoTooltip>
|
||||
</HStack>
|
||||
<Text
|
||||
@@ -130,9 +135,9 @@ export const ProPlanPricingCard = ({
|
||||
</Button>
|
||||
</Text>
|
||||
</Stack>,
|
||||
scopedT('pro.whatsAppIntegration'),
|
||||
scopedT('pro.customDomains'),
|
||||
scopedT('pro.analytics'),
|
||||
t('billing.pricingCard.pro.whatsAppIntegration'),
|
||||
t('billing.pricingCard.pro.customDomains'),
|
||||
t('billing.pricingCard.pro.analytics'),
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
import { Plan } from '@typebot.io/prisma'
|
||||
import { FeaturesList } from './FeaturesList'
|
||||
import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
|
||||
import { useI18n, useScopedI18n } from '@/locales'
|
||||
import { formatPrice } from '@typebot.io/lib/billing/formatPrice'
|
||||
import { prices } from '@typebot.io/lib/billing/constants'
|
||||
import { T, useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
currentPlan: Plan
|
||||
@@ -27,12 +27,12 @@ export const StarterPlanPricingCard = ({
|
||||
currency,
|
||||
onPayClick,
|
||||
}: Props) => {
|
||||
const t = useI18n()
|
||||
const scopedT = useScopedI18n('billing.pricingCard')
|
||||
const { t } = useTranslate()
|
||||
|
||||
const getButtonLabel = () => {
|
||||
if (currentPlan === Plan.PRO) return t('downgrade')
|
||||
if (currentPlan === Plan.STARTER) return scopedT('upgradeButton.current')
|
||||
if (currentPlan === Plan.STARTER)
|
||||
return t('billing.pricingCard.upgradeButton.current')
|
||||
return t('upgrade')
|
||||
}
|
||||
|
||||
@@ -51,25 +51,30 @@ export const StarterPlanPricingCard = ({
|
||||
<Stack>
|
||||
<Stack spacing="4">
|
||||
<Heading fontSize="2xl">
|
||||
{scopedT('heading', {
|
||||
plan: <chakra.span color="orange.400">Starter</chakra.span>,
|
||||
})}
|
||||
<T
|
||||
keyName="billing.pricingCard.heading"
|
||||
params={{ strong: <chakra.span color="orange.400" /> }}
|
||||
/>
|
||||
</Heading>
|
||||
<Text>{scopedT('starter.description')}</Text>
|
||||
<Text>{t('billing.pricingCard.starter.description')}</Text>
|
||||
</Stack>
|
||||
<Heading>
|
||||
{formatPrice(prices.STARTER, { currency })}
|
||||
<chakra.span fontSize="md">{scopedT('perMonth')}</chakra.span>
|
||||
<chakra.span fontSize="md">
|
||||
{t('billing.pricingCard.perMonth')}
|
||||
</chakra.span>
|
||||
</Heading>
|
||||
</Stack>
|
||||
|
||||
<FeaturesList
|
||||
features={[
|
||||
scopedT('starter.includedSeats'),
|
||||
t('billing.pricingCard.starter.includedSeats'),
|
||||
<Stack key="starter-chats" spacing={0}>
|
||||
<HStack>
|
||||
<Text>2,000 {scopedT('chatsPerMonth')}</Text>
|
||||
<MoreInfoTooltip>{scopedT('chatsTooltip')}</MoreInfoTooltip>
|
||||
<Text>2,000 {t('billing.pricingCard.chatsPerMonth')}</Text>
|
||||
<MoreInfoTooltip>
|
||||
{t('billing.pricingCard.chatsTooltip')}
|
||||
</MoreInfoTooltip>
|
||||
</HStack>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
@@ -78,9 +83,9 @@ export const StarterPlanPricingCard = ({
|
||||
Extra chats: $10 per 500
|
||||
</Text>
|
||||
</Stack>,
|
||||
scopedT('starter.brandingRemoved'),
|
||||
scopedT('starter.fileUploadBlock'),
|
||||
scopedT('starter.createFolders'),
|
||||
t('billing.pricingCard.starter.brandingRemoved'),
|
||||
t('billing.pricingCard.starter.fileUploadBlock'),
|
||||
t('billing.pricingCard.starter.createFolders'),
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import React from 'react'
|
||||
import { isNotDefined } from '@typebot.io/lib'
|
||||
import { ChangePlanModal } from './ChangePlanModal'
|
||||
import { useI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
limitReachedType?: string
|
||||
@@ -15,7 +15,7 @@ export const UpgradeButton = ({
|
||||
excludedPlans,
|
||||
...props
|
||||
}: Props) => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const { workspace } = useWorkspace()
|
||||
return (
|
||||
|
||||
@@ -13,15 +13,15 @@ import { Workspace } from '@typebot.io/prisma'
|
||||
import React from 'react'
|
||||
import { parseNumberWithCommas } from '@typebot.io/lib'
|
||||
import { defaultQueryOptions, trpc } from '@/lib/trpc'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { getChatsLimit } from '@typebot.io/lib/billing/getChatsLimit'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
workspace: Workspace
|
||||
}
|
||||
|
||||
export const UsageProgressBars = ({ workspace }: Props) => {
|
||||
const scopedT = useScopedI18n('billing.usage')
|
||||
const { t } = useTranslate()
|
||||
const { data, isLoading } = trpc.billing.getUsage.useQuery(
|
||||
{
|
||||
workspaceId: workspace.id,
|
||||
@@ -39,12 +39,12 @@ export const UsageProgressBars = ({ workspace }: Props) => {
|
||||
|
||||
return (
|
||||
<Stack spacing={6}>
|
||||
<Heading fontSize="3xl">{scopedT('heading')}</Heading>
|
||||
<Heading fontSize="3xl">{t('billing.usage.heading')}</Heading>
|
||||
<Stack spacing={3}>
|
||||
<Flex justifyContent="space-between">
|
||||
<HStack>
|
||||
<Heading fontSize="xl" as="h3">
|
||||
{scopedT('chats.heading')}
|
||||
{t('billing.usage.chats.heading')}
|
||||
</Heading>
|
||||
{chatsPercentage >= 80 && (
|
||||
<Tooltip
|
||||
@@ -53,10 +53,10 @@ export const UsageProgressBars = ({ workspace }: Props) => {
|
||||
p="3"
|
||||
label={
|
||||
<Text>
|
||||
{scopedT('chats.alert.soonReach')}
|
||||
{t('billing.usage.chats.alert.soonReach')}
|
||||
<br />
|
||||
<br />
|
||||
{scopedT('chats.alert.updatePlan')}
|
||||
{t('billing.usage.chats.alert.updatePlan')}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
@@ -81,7 +81,7 @@ export const UsageProgressBars = ({ workspace }: Props) => {
|
||||
<Text>
|
||||
/{' '}
|
||||
{workspaceChatsLimit === 'inf'
|
||||
? scopedT('unlimited')
|
||||
? t('billing.usage.unlimited')
|
||||
: parseNumberWithCommas(workspaceChatsLimit)}
|
||||
</Text>
|
||||
</HStack>
|
||||
|
||||
@@ -4,7 +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'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { FilePathUploadProps } from '@/features/upload/api/generateUploadUrl'
|
||||
|
||||
type Props = {
|
||||
@@ -18,7 +18,7 @@ export const AudioBubbleForm = ({
|
||||
content,
|
||||
onContentChange,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.audio.settings')
|
||||
const { t } = useTranslate()
|
||||
const [currentTab, setCurrentTab] = useState<'link' | 'upload'>('link')
|
||||
|
||||
const updateUrl = (url: string) => onContentChange({ ...content, url })
|
||||
@@ -34,14 +34,14 @@ export const AudioBubbleForm = ({
|
||||
onClick={() => setCurrentTab('upload')}
|
||||
size="sm"
|
||||
>
|
||||
{scopedT('upload.label')}
|
||||
{t('editor.blocks.bubbles.audio.settings.upload.label')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={currentTab === 'link' ? 'solid' : 'ghost'}
|
||||
onClick={() => setCurrentTab('link')}
|
||||
size="sm"
|
||||
>
|
||||
{scopedT('embedLink.label')}
|
||||
{t('editor.blocks.bubbles.audio.settings.embedLink.label')}
|
||||
</Button>
|
||||
</HStack>
|
||||
<Stack p="2" spacing={4}>
|
||||
@@ -54,25 +54,27 @@ export const AudioBubbleForm = ({
|
||||
onFileUploaded={updateUrl}
|
||||
colorScheme="blue"
|
||||
>
|
||||
{scopedT('chooseFile.label')}
|
||||
{t('editor.blocks.bubbles.audio.settings.chooseFile.label')}
|
||||
</UploadButton>
|
||||
</Flex>
|
||||
)}
|
||||
{currentTab === 'link' && (
|
||||
<>
|
||||
<TextInput
|
||||
placeholder={scopedT('worksWith.placeholder')}
|
||||
placeholder={t(
|
||||
'editor.blocks.bubbles.audio.settings.worksWith.placeholder'
|
||||
)}
|
||||
defaultValue={content.url ?? ''}
|
||||
onChange={updateUrl}
|
||||
/>
|
||||
<Text fontSize="sm" color="gray.400" textAlign="center">
|
||||
{scopedT('worksWith.text')}
|
||||
{t('editor.blocks.bubbles.audio.settings.worksWith.text')}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
<SwitchWithLabel
|
||||
label={scopedT('autoplay.label')}
|
||||
label={t('editor.blocks.bubbles.audio.settings.autoplay.label')}
|
||||
initialValue={content.isAutoplayEnabled ?? true}
|
||||
onCheckChange={updateAutoPlay}
|
||||
/>
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { AudioBubbleContent } from '@typebot.io/schemas'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
url: AudioBubbleContent['url']
|
||||
}
|
||||
|
||||
export const AudioBubbleNode = ({ url }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.audio.node')
|
||||
const { t } = useTranslate()
|
||||
return isDefined(url) ? (
|
||||
<audio src={url} controls />
|
||||
) : (
|
||||
<Text color={'gray.500'}>{scopedT('clickToEdit.text')}</Text>
|
||||
<Text color={'gray.500'}>
|
||||
{t('editor.blocks.bubbles.audio.node.clickToEdit.text')}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { EmbedBubbleBlock } from '@typebot.io/schemas'
|
||||
|
||||
@@ -7,8 +7,12 @@ type Props = {
|
||||
}
|
||||
|
||||
export const EmbedBubbleContent = ({ block }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.embed.node')
|
||||
const { t } = useTranslate()
|
||||
if (!block.content?.url)
|
||||
return <Text color="gray.500">{scopedT('clickToEdit.text')}</Text>
|
||||
return <Text>{scopedT('show.text')}</Text>
|
||||
return (
|
||||
<Text color="gray.500">
|
||||
{t('editor.blocks.bubbles.embed.node.clickToEdit.text')}
|
||||
</Text>
|
||||
)
|
||||
return <Text>{t('editor.blocks.bubbles.embed.node.show.text')}</Text>
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { TextInput, NumberInput } from '@/components/inputs'
|
||||
import { Stack, Text } from '@chakra-ui/react'
|
||||
import { EmbedBubbleContent } from '@typebot.io/schemas'
|
||||
import { sanitizeUrl } from '@typebot.io/lib'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
content: EmbedBubbleContent
|
||||
@@ -10,7 +10,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const EmbedUploadContent = ({ content, onSubmit }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.embed.settings')
|
||||
const { t } = useTranslate()
|
||||
const handleUrlChange = (url: string) => {
|
||||
const iframeUrl = sanitizeUrl(
|
||||
url.trim().startsWith('<iframe') ? extractUrlFromIframe(url) : url
|
||||
@@ -25,12 +25,14 @@ export const EmbedUploadContent = ({ content, onSubmit }: Props) => {
|
||||
<Stack p="2" spacing={6}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
placeholder={scopedT('worksWith.placeholder')}
|
||||
placeholder={t(
|
||||
'editor.blocks.bubbles.embed.settings.worksWith.placeholder'
|
||||
)}
|
||||
defaultValue={content?.url ?? ''}
|
||||
onChange={handleUrlChange}
|
||||
/>
|
||||
<Text fontSize="sm" color="gray.400" textAlign="center">
|
||||
{scopedT('worksWith.text')}
|
||||
{t('editor.blocks.bubbles.embed.settings.worksWith.text')}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -38,7 +40,7 @@ export const EmbedUploadContent = ({ content, onSubmit }: Props) => {
|
||||
label="Height:"
|
||||
defaultValue={content?.height}
|
||||
onValueChange={handleHeightChange}
|
||||
suffix={scopedT('numberInput.unit')}
|
||||
suffix={t('editor.blocks.bubbles.embed.settings.numberInput.unit')}
|
||||
width="150px"
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { Box, Text, Image } from '@chakra-ui/react'
|
||||
import { ImageBubbleBlock } from '@typebot.io/schemas'
|
||||
|
||||
@@ -7,11 +7,13 @@ type Props = {
|
||||
}
|
||||
|
||||
export const ImageBubbleContent = ({ block }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.image.node')
|
||||
const { t } = useTranslate()
|
||||
const containsVariables =
|
||||
block.content?.url?.includes('{{') && block.content.url.includes('}}')
|
||||
return !block.content?.url ? (
|
||||
<Text color={'gray.500'}>{scopedT('clickToEdit.text')}</Text>
|
||||
<Text color={'gray.500'}>
|
||||
{t('editor.blocks.bubbles.image.node.clickToEdit.text')}
|
||||
</Text>
|
||||
) : (
|
||||
<Box w="full">
|
||||
<Image
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ImageUploadContent } from '@/components/ImageUploadContent'
|
||||
import { TextInput } from '@/components/inputs'
|
||||
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
||||
import { FilePathUploadProps } from '@/features/upload/api/generateUploadUrl'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { isDefined, isNotEmpty } from '@typebot.io/lib'
|
||||
import { ImageBubbleBlock } from '@typebot.io/schemas'
|
||||
@@ -19,9 +19,7 @@ export const ImageBubbleSettings = ({
|
||||
block,
|
||||
onContentChange,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n(
|
||||
'editor.blocks.bubbles.image.switchWithLabel.onClick'
|
||||
)
|
||||
const { t } = useTranslate()
|
||||
const [showClickLinkInput, setShowClickLinkInput] = useState(
|
||||
isNotEmpty(block.content.clickLink?.url)
|
||||
)
|
||||
@@ -60,7 +58,7 @@ export const ImageBubbleSettings = ({
|
||||
/>
|
||||
<Stack>
|
||||
<SwitchWithLabel
|
||||
label={scopedT('label')}
|
||||
label={t('editor.blocks.bubbles.image.switchWithLabel.onClick.label')}
|
||||
initialValue={showClickLinkInput}
|
||||
onCheckChange={toggleClickLink}
|
||||
/>
|
||||
@@ -73,7 +71,9 @@ export const ImageBubbleSettings = ({
|
||||
defaultValue={block.content.clickLink?.url}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={scopedT('placeholder')}
|
||||
placeholder={t(
|
||||
'editor.blocks.bubbles.image.switchWithLabel.onClick.placeholder'
|
||||
)}
|
||||
onChange={updateClickLinkAltText}
|
||||
defaultValue={block.content.clickLink?.alt}
|
||||
/>
|
||||
|
||||
@@ -18,7 +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'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type TextBubbleEditorContentProps = {
|
||||
id: string
|
||||
@@ -31,7 +31,7 @@ const TextBubbleEditorContent = ({
|
||||
textEditorValue,
|
||||
onClose,
|
||||
}: TextBubbleEditorContentProps) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles')
|
||||
const { t } = useTranslate()
|
||||
const editor = usePlateEditorRef()
|
||||
const varDropdownRef = useRef<HTMLDivElement | null>(null)
|
||||
const rememberedSelection = useRef<BaseSelection | null>(null)
|
||||
@@ -137,7 +137,7 @@ const TextBubbleEditorContent = ({
|
||||
})
|
||||
setIsFirstFocus(false)
|
||||
},
|
||||
'aria-label': `${scopedT('textEditor.plate.label')}`,
|
||||
'aria-label': `${t('editor.blocks.bubbles.textEditor.plate.label')}`,
|
||||
onBlur: () => {
|
||||
rememberedSelection.current = editor?.selection
|
||||
},
|
||||
@@ -156,7 +156,9 @@ const TextBubbleEditorContent = ({
|
||||
<VariableSearchInput
|
||||
initialVariableId={undefined}
|
||||
onSelectVariable={handleVariableSelected}
|
||||
placeholder={scopedT('textEditor.searchVariable.placeholder')}
|
||||
placeholder={t(
|
||||
'editor.blocks.bubbles.textEditor.searchVariable.placeholder'
|
||||
)}
|
||||
autoFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { Box, Text, Image } from '@chakra-ui/react'
|
||||
import { VideoBubbleBlock, VideoBubbleContentType } from '@typebot.io/schemas'
|
||||
|
||||
@@ -7,9 +7,13 @@ type Props = {
|
||||
}
|
||||
|
||||
export const VideoBubbleContent = ({ block }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.video.node')
|
||||
const { t } = useTranslate()
|
||||
if (!block.content?.url || !block.content.type)
|
||||
return <Text color="gray.500">{scopedT('clickToEdit.text')}</Text>
|
||||
return (
|
||||
<Text color="gray.500">
|
||||
{t('editor.blocks.bubbles.video.node.clickToEdit.text')}
|
||||
</Text>
|
||||
)
|
||||
const containsVariables =
|
||||
block.content?.url?.includes('{{') && block.content.url.includes('}}')
|
||||
switch (block.content.type) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
VideoBubbleContentType,
|
||||
} from '@typebot.io/schemas'
|
||||
import { NumberInput, TextInput } from '@/components/inputs'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { parseVideoUrl } from '@typebot.io/lib/parseVideoUrl'
|
||||
|
||||
type Props = {
|
||||
@@ -14,7 +14,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const VideoUploadContent = ({ content, onSubmit }: Props) => {
|
||||
const scopedT = useScopedI18n('editor.blocks.bubbles.video.settings')
|
||||
const { t } = useTranslate()
|
||||
const updateUrl = (url: string) => {
|
||||
const info = parseVideoUrl(url)
|
||||
return onSubmit({
|
||||
@@ -33,12 +33,14 @@ export const VideoUploadContent = ({ content, onSubmit }: Props) => {
|
||||
<Stack p="2" spacing={4}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
placeholder={scopedT('worksWith.placeholder')}
|
||||
placeholder={t(
|
||||
'editor.blocks.bubbles.video.settings.worksWith.placeholder'
|
||||
)}
|
||||
defaultValue={content?.url ?? ''}
|
||||
onChange={updateUrl}
|
||||
/>
|
||||
<Text fontSize="sm" color="gray.400" textAlign="center">
|
||||
{scopedT('worksWith.text')}
|
||||
{t('editor.blocks.bubbles.video.settings.worksWith.text')}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -47,7 +49,7 @@ export const VideoUploadContent = ({ content, onSubmit }: Props) => {
|
||||
label="Height:"
|
||||
defaultValue={content?.height ?? 400}
|
||||
onValueChange={updateHeight}
|
||||
suffix={scopedT('numberInput.unit')}
|
||||
suffix={t('editor.blocks.bubbles.video.settings.numberInput.unit')}
|
||||
width="150px"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { canReadTypebots } from '@/helpers/databaseRules'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Group, IntegrationBlockType, Typebot } from '@typebot.io/schemas'
|
||||
import { byId, isWebhookBlock, parseGroupTitle } from '@typebot.io/lib'
|
||||
import { byId, isWebhookBlock } from '@typebot.io/lib'
|
||||
import { z } from 'zod'
|
||||
import { Webhook } from '@typebot.io/prisma'
|
||||
|
||||
@@ -70,7 +70,7 @@ export const listWebhookBlocks = authenticatedProcedure
|
||||
...blocks.map((block) => ({
|
||||
id: block.id,
|
||||
type: block.type,
|
||||
label: `${parseGroupTitle(group.title)} > ${block.id}`,
|
||||
label: `${group.title} > ${block.id}`,
|
||||
url: block.options.webhook
|
||||
? block.options.webhook.url
|
||||
: typebot?.webhooks.find(byId(block.webhookId))?.url ?? undefined,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Tag, Text } from '@chakra-ui/react'
|
||||
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
||||
import { byId, isDefined, parseGroupTitle } from '@typebot.io/lib'
|
||||
import { byId, isDefined } from '@typebot.io/lib'
|
||||
import { JumpBlock } from '@typebot.io/schemas/features/blocks/logic/jump'
|
||||
|
||||
type Props = {
|
||||
@@ -15,8 +15,7 @@ export const JumpNodeBody = ({ options }: Props) => {
|
||||
if (!selectedGroup) return <Text color="gray.500">Configure...</Text>
|
||||
return (
|
||||
<Text>
|
||||
Jump to{' '}
|
||||
<Tag colorScheme="blue">{parseGroupTitle(selectedGroup.title)}</Tag>{' '}
|
||||
Jump to <Tag colorScheme="blue">{selectedGroup.title}</Tag>{' '}
|
||||
{isDefined(blockIndex) && blockIndex >= 0 ? (
|
||||
<>
|
||||
at block <Tag colorScheme="blue">{blockIndex + 1}</Tag>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { JumpBlock } from '@typebot.io/schemas/features/blocks/logic/jump'
|
||||
import React from 'react'
|
||||
import { byId, parseGroupTitle } from '@typebot.io/lib'
|
||||
import { byId } from '@typebot.io/lib'
|
||||
|
||||
type Props = {
|
||||
groupId: string
|
||||
@@ -32,7 +32,7 @@ export const JumpSettings = ({ groupId, options, onOptionsChange }: Props) => {
|
||||
items={typebot.groups
|
||||
.filter((group) => group.id !== currentGroupId)
|
||||
.map((group) => ({
|
||||
label: parseGroupTitle(group.title),
|
||||
label: group.title,
|
||||
value: group.id,
|
||||
}))}
|
||||
selectedItem={selectedGroup?.id}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Select } from '@/components/inputs/Select'
|
||||
import { Input } from '@chakra-ui/react'
|
||||
import { Group } from '@typebot.io/schemas'
|
||||
import { parseGroupTitle } from '@typebot.io/lib'
|
||||
|
||||
type Props = {
|
||||
groups: Group[]
|
||||
@@ -23,7 +22,7 @@ export const GroupsDropdown = ({
|
||||
<Select
|
||||
selectedItem={groupId}
|
||||
items={(groups ?? []).map((group) => ({
|
||||
label: parseGroupTitle(group.title),
|
||||
label: group.title,
|
||||
value: group.id,
|
||||
}))}
|
||||
onSelect={onGroupIdSelected}
|
||||
|
||||
@@ -28,7 +28,11 @@ export const CollaborationMenuButton = ({
|
||||
</Tooltip>
|
||||
</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent shadow="lg" width="430px">
|
||||
<PopoverContent
|
||||
shadow="lg"
|
||||
width="430px"
|
||||
rootProps={{ style: { transform: 'scale(0)' } }}
|
||||
>
|
||||
<CollaborationList />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -5,13 +5,13 @@ import { useUser } from '@/features/account/hooks/useUser'
|
||||
import { isNotDefined } from '@typebot.io/lib'
|
||||
import Link from 'next/link'
|
||||
import { EmojiOrImageIcon } from '@/components/EmojiOrImageIcon'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { WorkspaceDropdown } from '@/features/workspace/components/WorkspaceDropdown'
|
||||
import { WorkspaceSettingsModal } from '@/features/workspace/components/WorkspaceSettingsModal'
|
||||
|
||||
export const DashboardHeader = () => {
|
||||
const scopedT = useScopedI18n('dashboard.header')
|
||||
const { t } = useTranslate()
|
||||
const { user, logOut } = useUser()
|
||||
const { workspace, switchWorkspace, createWorkspace } = useWorkspace()
|
||||
|
||||
@@ -50,7 +50,7 @@ export const DashboardHeader = () => {
|
||||
onClick={onOpen}
|
||||
isLoading={isNotDefined(workspace)}
|
||||
>
|
||||
{scopedT('settingsButton.label')}
|
||||
{t('dashboard.header.settingsButton.label')}
|
||||
</Button>
|
||||
<WorkspaceDropdown
|
||||
currentWorkspace={workspace}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
PreCheckoutModalProps,
|
||||
} from '@/features/billing/components/PreCheckoutModal'
|
||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { Stack, VStack, Spinner, Text } from '@chakra-ui/react'
|
||||
import { Plan } from '@typebot.io/prisma'
|
||||
import { useRouter } from 'next/router'
|
||||
@@ -16,9 +15,10 @@ import { TypebotDndProvider } from '@/features/folders/TypebotDndProvider'
|
||||
import { ParentModalProvider } from '@/features/graph/providers/ParentModalProvider'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { guessIfUserIsEuropean } from '@typebot.io/lib/billing/guessIfUserIsEuropean'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
export const DashboardPage = () => {
|
||||
const scopedT = useScopedI18n('dashboard')
|
||||
const { t } = useTranslate()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const router = useRouter()
|
||||
const { user } = useUser()
|
||||
@@ -58,7 +58,7 @@ export const DashboardPage = () => {
|
||||
|
||||
return (
|
||||
<Stack minH="100vh">
|
||||
<Seo title={workspace?.name ?? scopedT('title')} />
|
||||
<Seo title={workspace?.name ?? t('dashboard.title')} />
|
||||
<DashboardHeader />
|
||||
{!workspace?.stripeId && (
|
||||
<ParentModalProvider>
|
||||
@@ -73,7 +73,7 @@ export const DashboardPage = () => {
|
||||
<TypebotDndProvider>
|
||||
{isLoading ? (
|
||||
<VStack w="full" justifyContent="center" pt="10" spacing={6}>
|
||||
<Text>{scopedT('redirectionMessage')}</Text>
|
||||
<Text>{t('dashboard.redirectionMessage')}</Text>
|
||||
<Spinner />
|
||||
</VStack>
|
||||
) : (
|
||||
|
||||
@@ -14,7 +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'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
type: DraggableBlockType
|
||||
@@ -27,7 +27,7 @@ type Props = {
|
||||
export const BlockCard = (
|
||||
props: Pick<Props, 'type' | 'onMouseDown'>
|
||||
): JSX.Element => {
|
||||
const scopedT = useScopedI18n('editor.blockCard')
|
||||
const { t } = useTranslate()
|
||||
const { workspace } = useWorkspace()
|
||||
|
||||
switch (props.type) {
|
||||
@@ -35,7 +35,7 @@ export const BlockCard = (
|
||||
return (
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('bubbleBlock.tooltip.label')}
|
||||
tooltip={t('blocks.bubbles.embed.blockCard.tooltip')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
@@ -45,7 +45,7 @@ export const BlockCard = (
|
||||
return (
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('inputBlock.tooltip.files.label')}
|
||||
tooltip={t('blocks.inputs.fileUpload.blockCard.tooltip')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<HStack>
|
||||
@@ -58,7 +58,7 @@ export const BlockCard = (
|
||||
return (
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('logicBlock.tooltip.code.label')}
|
||||
tooltip={t('editor.blockCard.logicBlock.tooltip.code.label')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
@@ -68,7 +68,7 @@ export const BlockCard = (
|
||||
return (
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('logicBlock.tooltip.typebotLink.label')}
|
||||
tooltip={t('editor.blockCard.logicBlock.tooltip.typebotLink.label')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
@@ -78,7 +78,7 @@ export const BlockCard = (
|
||||
return (
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('logicBlock.tooltip.jump.label')}
|
||||
tooltip={t('editor.blockCard.logicBlock.tooltip.jump.label')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
@@ -88,7 +88,7 @@ export const BlockCard = (
|
||||
return (
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('integrationBlock.tooltip.googleSheets.label')}
|
||||
tooltip={t('blocks.integrations.googleSheets.blockCard.tooltip')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
@@ -98,7 +98,7 @@ export const BlockCard = (
|
||||
return (
|
||||
<BlockCardLayout
|
||||
{...props}
|
||||
tooltip={scopedT('integrationBlock.tooltip.googleAnalytics.label')}
|
||||
tooltip={t('blocks.integrations.googleAnalytics.blockCard.tooltip')}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<BlockLabel type={props.type} />
|
||||
|
||||
@@ -7,84 +7,98 @@ import {
|
||||
BlockType,
|
||||
} from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = { type: BlockType }
|
||||
|
||||
export const BlockLabel = ({ type }: Props): JSX.Element => {
|
||||
const scopedT = useScopedI18n('editor.sidebarBlock')
|
||||
const { t } = useTranslate()
|
||||
|
||||
switch (type) {
|
||||
case 'start':
|
||||
return <Text fontSize="sm">{scopedT('start.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.start.label')}</Text>
|
||||
case BubbleBlockType.TEXT:
|
||||
case InputBlockType.TEXT:
|
||||
return <Text fontSize="sm">{scopedT('text.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.text.label')}</Text>
|
||||
case BubbleBlockType.IMAGE:
|
||||
return <Text fontSize="sm">{scopedT('image.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.image.label')}</Text>
|
||||
case BubbleBlockType.VIDEO:
|
||||
return <Text fontSize="sm">{scopedT('video.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.video.label')}</Text>
|
||||
case BubbleBlockType.EMBED:
|
||||
return <Text fontSize="sm">{scopedT('embed.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.embed.label')}</Text>
|
||||
case BubbleBlockType.AUDIO:
|
||||
return <Text fontSize="sm">{scopedT('audio.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.audio.label')}</Text>
|
||||
case InputBlockType.NUMBER:
|
||||
return <Text fontSize="sm">{scopedT('number.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.number.label')}</Text>
|
||||
case InputBlockType.EMAIL:
|
||||
return <Text fontSize="sm">{scopedT('email.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.email.label')}</Text>
|
||||
case InputBlockType.URL:
|
||||
return <Text fontSize="sm">{scopedT('website.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.website.label')}</Text>
|
||||
case InputBlockType.DATE:
|
||||
return <Text fontSize="sm">{scopedT('date.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.date.label')}</Text>
|
||||
case InputBlockType.PHONE:
|
||||
return <Text fontSize="sm">{scopedT('phone.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.phone.label')}</Text>
|
||||
case InputBlockType.CHOICE:
|
||||
return <Text fontSize="sm">{scopedT('button.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.button.label')}</Text>
|
||||
case InputBlockType.PICTURE_CHOICE:
|
||||
return <Text fontSize="sm">{scopedT('picChoice.label')}</Text>
|
||||
return (
|
||||
<Text fontSize="sm">{t('editor.sidebarBlock.picChoice.label')}</Text>
|
||||
)
|
||||
case InputBlockType.PAYMENT:
|
||||
return <Text fontSize="sm">{scopedT('payment.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.payment.label')}</Text>
|
||||
case InputBlockType.RATING:
|
||||
return <Text fontSize="sm">{scopedT('rating.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.rating.label')}</Text>
|
||||
case InputBlockType.FILE:
|
||||
return <Text fontSize="sm">{scopedT('file.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.file.label')}</Text>
|
||||
case LogicBlockType.SET_VARIABLE:
|
||||
return <Text fontSize="sm">{scopedT('setVariable.label')}</Text>
|
||||
return (
|
||||
<Text fontSize="sm">{t('editor.sidebarBlock.setVariable.label')}</Text>
|
||||
)
|
||||
case LogicBlockType.CONDITION:
|
||||
return <Text fontSize="sm">{scopedT('condition.label')}</Text>
|
||||
return (
|
||||
<Text fontSize="sm">{t('editor.sidebarBlock.condition.label')}</Text>
|
||||
)
|
||||
case LogicBlockType.REDIRECT:
|
||||
return <Text fontSize="sm">{scopedT('redirect.label')}</Text>
|
||||
return (
|
||||
<Text fontSize="sm">{t('editor.sidebarBlock.redirect.label')}</Text>
|
||||
)
|
||||
case LogicBlockType.SCRIPT:
|
||||
return <Text fontSize="sm">{scopedT('script.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.script.label')}</Text>
|
||||
case LogicBlockType.TYPEBOT_LINK:
|
||||
return <Text fontSize="sm">{scopedT('typebot.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.typebot.label')}</Text>
|
||||
case LogicBlockType.WAIT:
|
||||
return <Text fontSize="sm">{scopedT('wait.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.wait.label')}</Text>
|
||||
case LogicBlockType.JUMP:
|
||||
return <Text fontSize="sm">{scopedT('jump.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.jump.label')}</Text>
|
||||
case LogicBlockType.AB_TEST:
|
||||
return <Text fontSize="sm">{scopedT('abTest.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.abTest.label')}</Text>
|
||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||
return <Text fontSize="sm">{scopedT('sheets.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.sheets.label')}</Text>
|
||||
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
||||
return <Text fontSize="sm">{scopedT('analytics.label')}</Text>
|
||||
return (
|
||||
<Text fontSize="sm">{t('editor.sidebarBlock.analytics.label')}</Text>
|
||||
)
|
||||
case IntegrationBlockType.WEBHOOK:
|
||||
return <Text fontSize="sm">{scopedT('webhook.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.webhook.label')}</Text>
|
||||
case IntegrationBlockType.ZAPIER:
|
||||
return <Text fontSize="sm">{scopedT('zapier.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.zapier.label')}</Text>
|
||||
case IntegrationBlockType.MAKE_COM:
|
||||
return <Text fontSize="sm">{scopedT('makecom.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.makecom.label')}</Text>
|
||||
case IntegrationBlockType.PABBLY_CONNECT:
|
||||
return <Text fontSize="sm">{scopedT('pabbly.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.pabbly.label')}</Text>
|
||||
case IntegrationBlockType.EMAIL:
|
||||
return <Text fontSize="sm">{scopedT('email.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.email.label')}</Text>
|
||||
case IntegrationBlockType.CHATWOOT:
|
||||
return <Text fontSize="sm">{scopedT('chatwoot.label')}</Text>
|
||||
return (
|
||||
<Text fontSize="sm">{t('editor.sidebarBlock.chatwoot.label')}</Text>
|
||||
)
|
||||
case IntegrationBlockType.OPEN_AI:
|
||||
return <Text fontSize="sm">{scopedT('openai.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.openai.label')}</Text>
|
||||
case IntegrationBlockType.PIXEL:
|
||||
return <Text fontSize="sm">{scopedT('pixel.label')}</Text>
|
||||
return <Text fontSize="sm">{t('editor.sidebarBlock.pixel.label')}</Text>
|
||||
case IntegrationBlockType.ZEMANTIC_AI:
|
||||
return <Text fontSize="sm">{scopedT('zemanticAi.label')}</Text>
|
||||
return (
|
||||
<Text fontSize="sm">{t('editor.sidebarBlock.zemanticAi.label')}</Text>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@ import { BlockCard } from './BlockCard'
|
||||
import { LockedIcon, UnlockedIcon } from '@/components/icons'
|
||||
import { BlockCardOverlay } from './BlockCardOverlay'
|
||||
import { headerHeight } from '../constants'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
export const BlocksSideBar = () => {
|
||||
const scopedT = useScopedI18n('editor.sidebarBlocks')
|
||||
const { t } = useTranslate()
|
||||
const { setDraggedBlockType, draggedBlockType } = useBlockDnd()
|
||||
const [position, setPosition] = useState({
|
||||
x: 0,
|
||||
@@ -107,16 +107,16 @@ export const BlocksSideBar = () => {
|
||||
<Tooltip
|
||||
label={
|
||||
isLocked
|
||||
? scopedT('sidebar.unlock.label')
|
||||
: scopedT('sidebar.lock.label')
|
||||
? t('editor.sidebarBlocks.sidebar.unlock.label')
|
||||
: t('editor.sidebarBlocks.sidebar.lock.label')
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
icon={isLocked ? <LockedIcon /> : <UnlockedIcon />}
|
||||
aria-label={
|
||||
isLocked
|
||||
? scopedT('sidebar.icon.unlock.label')
|
||||
: scopedT('sidebar.icon.lock.label')
|
||||
? t('editor.sidebarBlocks.sidebar.icon.unlock.label')
|
||||
: t('editor.sidebarBlocks.sidebar.icon.lock.label')
|
||||
}
|
||||
size="sm"
|
||||
onClick={handleLockClick}
|
||||
@@ -126,7 +126,7 @@ export const BlocksSideBar = () => {
|
||||
|
||||
<Stack>
|
||||
<Text fontSize="sm" fontWeight="semibold">
|
||||
{scopedT('blockType.bubbles.heading')}
|
||||
{t('editor.sidebarBlocks.blockType.bubbles.heading')}
|
||||
</Text>
|
||||
<SimpleGrid columns={2} spacing="3">
|
||||
{Object.values(BubbleBlockType).map((type) => (
|
||||
@@ -137,7 +137,7 @@ export const BlocksSideBar = () => {
|
||||
|
||||
<Stack>
|
||||
<Text fontSize="sm" fontWeight="semibold">
|
||||
{scopedT('blockType.inputs.heading')}
|
||||
{t('editor.sidebarBlocks.blockType.inputs.heading')}
|
||||
</Text>
|
||||
<SimpleGrid columns={2} spacing="3">
|
||||
{Object.values(InputBlockType).map((type) => (
|
||||
@@ -148,7 +148,7 @@ export const BlocksSideBar = () => {
|
||||
|
||||
<Stack>
|
||||
<Text fontSize="sm" fontWeight="semibold">
|
||||
{scopedT('blockType.logic.heading')}
|
||||
{t('editor.sidebarBlocks.blockType.logic.heading')}
|
||||
</Text>
|
||||
<SimpleGrid columns={2} spacing="3">
|
||||
{Object.values(LogicBlockType).map((type) => (
|
||||
@@ -159,7 +159,7 @@ export const BlocksSideBar = () => {
|
||||
|
||||
<Stack>
|
||||
<Text fontSize="sm" fontWeight="semibold">
|
||||
{scopedT('blockType.integrations.heading')}
|
||||
{t('editor.sidebarBlocks.blockType.integrations.heading')}
|
||||
</Text>
|
||||
<SimpleGrid columns={2} spacing="3">
|
||||
{Object.values(IntegrationBlockType).map((type) => (
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react'
|
||||
import React, { useState } from 'react'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type EditableProps = {
|
||||
defaultName: string
|
||||
@@ -16,7 +16,7 @@ export const EditableTypebotName = ({
|
||||
defaultName,
|
||||
onNewName,
|
||||
}: EditableProps) => {
|
||||
const scopedT = useScopedI18n('editor.editableTypebotName')
|
||||
const { t } = useTranslate()
|
||||
const emptyNameBg = useColorModeValue('gray.100', 'gray.700')
|
||||
const [currentName, setCurrentName] = useState(defaultName)
|
||||
|
||||
@@ -27,7 +27,7 @@ export const EditableTypebotName = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip label={scopedT('tooltip.rename.label')}>
|
||||
<Tooltip label={t('editor.editableTypebotName.tooltip.rename.label')}>
|
||||
<Editable
|
||||
value={currentName}
|
||||
onChange={setCurrentName}
|
||||
|
||||
@@ -21,10 +21,10 @@ import {
|
||||
} from '@chakra-ui/react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
export const GettingStartedModal = () => {
|
||||
const scopedT = useScopedI18n('editor.gettingStartedModal')
|
||||
const { t } = useTranslate()
|
||||
const { query } = useRouter()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
|
||||
@@ -40,7 +40,9 @@ export const GettingStartedModal = () => {
|
||||
<ModalCloseButton />
|
||||
<ModalBody as={Stack} spacing="8" py="10">
|
||||
<Stack spacing={4}>
|
||||
<Heading fontSize="xl">{scopedT('editorBasics.heading')}</Heading>
|
||||
<Heading fontSize="xl">
|
||||
{t('editor.gettingStartedModal.editorBasics.heading')}
|
||||
</Heading>
|
||||
<List spacing={4}>
|
||||
<HStack as={ListItem}>
|
||||
<Flex
|
||||
@@ -56,7 +58,9 @@ export const GettingStartedModal = () => {
|
||||
>
|
||||
1
|
||||
</Flex>
|
||||
<Text>{scopedT('editorBasics.list.one.label')}</Text>
|
||||
<Text>
|
||||
{t('editor.gettingStartedModal.editorBasics.list.one.label')}
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack as={ListItem}>
|
||||
<Flex
|
||||
@@ -72,7 +76,9 @@ export const GettingStartedModal = () => {
|
||||
>
|
||||
2
|
||||
</Flex>
|
||||
<Text>{scopedT('editorBasics.list.two.label')}</Text>
|
||||
<Text>
|
||||
{t('editor.gettingStartedModal.editorBasics.list.two.label')}
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack as={ListItem}>
|
||||
<Flex
|
||||
@@ -88,7 +94,11 @@ export const GettingStartedModal = () => {
|
||||
>
|
||||
3
|
||||
</Flex>
|
||||
<Text>{scopedT('editorBasics.list.three.label')}</Text>
|
||||
<Text>
|
||||
{t(
|
||||
'editor.gettingStartedModal.editorBasics.list.three.label'
|
||||
)}
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack as={ListItem}>
|
||||
<Flex
|
||||
@@ -104,15 +114,18 @@ export const GettingStartedModal = () => {
|
||||
>
|
||||
4
|
||||
</Flex>
|
||||
<Text>{scopedT('editorBasics.list.four.label')}</Text>
|
||||
<Text>
|
||||
{t('editor.gettingStartedModal.editorBasics.list.four.label')}
|
||||
</Text>
|
||||
</HStack>
|
||||
</List>
|
||||
</Stack>
|
||||
|
||||
<Text>{scopedT('editorBasics.list.label')}</Text>
|
||||
<Text>{t('editor.gettingStartedModal.editorBasics.list.label')}</Text>
|
||||
<Stack spacing={4}>
|
||||
<Heading fontSize="xl">
|
||||
{scopedT('seeAction.label')} ({`<`} {scopedT('seeAction.time')})
|
||||
{t('editor.gettingStartedModal.seeAction.label')} ({`<`}{' '}
|
||||
{t('editor.gettingStartedModal.seeAction.time')})
|
||||
</Heading>
|
||||
<iframe
|
||||
width="100%"
|
||||
@@ -127,7 +140,7 @@ export const GettingStartedModal = () => {
|
||||
<AccordionItem>
|
||||
<AccordionButton>
|
||||
<Box flex="1" textAlign="left">
|
||||
{scopedT('seeAction.item.label')}
|
||||
{t('editor.gettingStartedModal.seeAction.item.label')}
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
|
||||
@@ -30,10 +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'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
export const TypebotHeader = () => {
|
||||
const scopedT = useScopedI18n('editor.headers')
|
||||
const { t } = useTranslate()
|
||||
const router = useRouter()
|
||||
const {
|
||||
typebot,
|
||||
@@ -105,7 +105,7 @@ export const TypebotHeader = () => {
|
||||
variant={router.pathname.includes('/edit') ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
>
|
||||
{scopedT('flowButton.label')}
|
||||
{t('editor.headers.flowButton.label')}
|
||||
</Button>
|
||||
<Button
|
||||
as={Link}
|
||||
@@ -114,7 +114,7 @@ export const TypebotHeader = () => {
|
||||
variant={router.pathname.endsWith('theme') ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
>
|
||||
{scopedT('themeButton.label')}
|
||||
{t('editor.headers.themeButton.label')}
|
||||
</Button>
|
||||
<Button
|
||||
as={Link}
|
||||
@@ -123,7 +123,7 @@ export const TypebotHeader = () => {
|
||||
variant={router.pathname.endsWith('settings') ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
>
|
||||
{scopedT('settingsButton.label')}
|
||||
{t('editor.headers.settingsButton.label')}
|
||||
</Button>
|
||||
<Button
|
||||
as={Link}
|
||||
@@ -132,7 +132,7 @@ export const TypebotHeader = () => {
|
||||
variant={router.pathname.endsWith('share') ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
>
|
||||
{scopedT('shareButton.label')}
|
||||
{t('editor.headers.shareButton.label')}
|
||||
</Button>
|
||||
{isDefined(publishedTypebot) && (
|
||||
<Button
|
||||
@@ -142,7 +142,7 @@ export const TypebotHeader = () => {
|
||||
variant={router.pathname.includes('results') ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
>
|
||||
{scopedT('resultsButton.label')}
|
||||
{t('editor.headers.resultsButton.label')}
|
||||
</Button>
|
||||
)}
|
||||
</HStack>
|
||||
@@ -225,21 +225,23 @@ export const TypebotHeader = () => {
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
<Button leftIcon={<BuoyIcon />} onClick={handleHelpClick} size="sm">
|
||||
{scopedT('helpButton.label')}
|
||||
{t('editor.headers.helpButton.label')}
|
||||
</Button>
|
||||
</HStack>
|
||||
{isSavingLoading && (
|
||||
<HStack>
|
||||
<Spinner speed="0.7s" size="sm" color="gray.400" />
|
||||
<Text fontSize="sm" color="gray.400">
|
||||
{scopedT('savingSpinner.label')}
|
||||
{t('editor.headers.savingSpinner.label')}
|
||||
</Text>
|
||||
</HStack>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
<HStack right="40px" pos="absolute" display={['none', 'flex']}>
|
||||
<CollaborationMenuButton isLoading={isNotDefined(typebot)} />
|
||||
<Flex pos="relative">
|
||||
<CollaborationMenuButton isLoading={isNotDefined(typebot)} />
|
||||
</Flex>
|
||||
{router.pathname.includes('/edit') && isNotDefined(rightPanel) && (
|
||||
<Button
|
||||
colorScheme="gray"
|
||||
@@ -247,7 +249,7 @@ export const TypebotHeader = () => {
|
||||
isLoading={isNotDefined(typebot)}
|
||||
size="sm"
|
||||
>
|
||||
{scopedT('previewButton.label')}
|
||||
{t('editor.headers.previewButton.label')}
|
||||
</Button>
|
||||
)}
|
||||
<PublishButton size="sm" />
|
||||
|
||||
@@ -23,7 +23,6 @@ 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
|
||||
|
||||
@@ -80,7 +79,6 @@ export const TypebotProvider = ({
|
||||
children: ReactNode
|
||||
typebotId?: string
|
||||
}) => {
|
||||
const scopedT = useScopedI18n('editor.provider')
|
||||
const { push } = useRouter()
|
||||
const { showToast } = useToast()
|
||||
|
||||
@@ -96,15 +94,11 @@ export const TypebotProvider = ({
|
||||
if (error.data?.httpStatus === 404) {
|
||||
showToast({
|
||||
status: 'info',
|
||||
description: scopedT('messages.getTypebotError.description'),
|
||||
description: "Couldn't find typebot.",
|
||||
})
|
||||
push('/typebots')
|
||||
return
|
||||
}
|
||||
showToast({
|
||||
title: scopedT('messages.getTypebotError.title'),
|
||||
description: error.message,
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -114,13 +108,6 @@ export const TypebotProvider = ({
|
||||
{ typebotId: typebotId as string },
|
||||
{
|
||||
enabled: isDefined(typebotId),
|
||||
onError: (error) => {
|
||||
if (error.data?.httpStatus === 404) return
|
||||
showToast({
|
||||
title: scopedT('messages.publishedTypebotError.title'),
|
||||
description: error.message,
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -128,7 +115,7 @@ export const TypebotProvider = ({
|
||||
trpc.typebot.updateTypebot.useMutation({
|
||||
onError: (error) =>
|
||||
showToast({
|
||||
title: scopedT('messages.updateTypebotError.title'),
|
||||
title: 'Error while updating typebot',
|
||||
description: error.message,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
@@ -264,10 +251,7 @@ export const TypebotProvider = ({
|
||||
isPublished,
|
||||
updateTypebot: updateLocalTypebot,
|
||||
restorePublishedTypebot,
|
||||
...groupsActions(
|
||||
setLocalTypebot as SetTypebot,
|
||||
scopedT('groups.copy.title')
|
||||
),
|
||||
...groupsActions(setLocalTypebot as SetTypebot),
|
||||
...blocksAction(setLocalTypebot as SetTypebot),
|
||||
...variablesAction(setLocalTypebot as SetTypebot),
|
||||
...edgesAction(setLocalTypebot as SetTypebot),
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
createBlockDraft,
|
||||
duplicateBlockDraft,
|
||||
} from './blocks'
|
||||
import { isEmpty, parseGroupTitle } from '@typebot.io/lib'
|
||||
import { isEmpty } from '@typebot.io/lib'
|
||||
import { Coordinates } from '@/features/graph/types'
|
||||
|
||||
export type GroupsActions = {
|
||||
@@ -28,10 +28,7 @@ export type GroupsActions = {
|
||||
deleteGroup: (groupIndex: number) => void
|
||||
}
|
||||
|
||||
const groupsActions = (
|
||||
setTypebot: SetTypebot,
|
||||
groupCopyLabel: string
|
||||
): GroupsActions => ({
|
||||
const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
|
||||
createGroup: ({
|
||||
id,
|
||||
block,
|
||||
@@ -67,11 +64,19 @@ const groupsActions = (
|
||||
const group = typebot.groups[groupIndex]
|
||||
const id = createId()
|
||||
|
||||
const totalGroupsWithSameTitle = typebot.groups.filter(
|
||||
(group) => group.title === group.title
|
||||
).length
|
||||
|
||||
const newGroup: Group = {
|
||||
...group,
|
||||
title: isEmpty(group.title)
|
||||
? ''
|
||||
: `${parseGroupTitle(group.title)} ${groupCopyLabel}`,
|
||||
: `${group.title}${
|
||||
totalGroupsWithSameTitle > 0
|
||||
? ` (${totalGroupsWithSameTitle})`
|
||||
: ''
|
||||
}}`,
|
||||
id,
|
||||
blocks: group.blocks.map((block) => duplicateBlockDraft(id)(block)),
|
||||
graphCoordinates: {
|
||||
|
||||
@@ -3,10 +3,10 @@ import { ChevronLeftIcon } from '@/components/icons'
|
||||
import { useTypebotDnd } from '../TypebotDndProvider'
|
||||
import Link from 'next/link'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
export const BackButton = ({ id }: { id: string | null }) => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
const { draggedTypebot, setMouseOverFolderId, mouseOverFolderId } =
|
||||
useTypebotDnd()
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ import { PlusIcon } from '@/components/icons'
|
||||
import { useRouter } from 'next/router'
|
||||
import { stringify } from 'qs'
|
||||
import React from 'react'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
export const CreateBotButton = ({
|
||||
folderId,
|
||||
isFirstBot,
|
||||
...props
|
||||
}: { folderId?: string; isFirstBot: boolean } & ButtonProps) => {
|
||||
const scopedT = useScopedI18n('folders.createTypebotButton')
|
||||
const { t } = useTranslate()
|
||||
const router = useRouter()
|
||||
|
||||
const handleClick = () =>
|
||||
@@ -39,7 +39,7 @@ export const CreateBotButton = ({
|
||||
textAlign="center"
|
||||
mt="6"
|
||||
>
|
||||
{scopedT('label')}
|
||||
{t('folders.createTypebotButton.label')}
|
||||
</Text>
|
||||
</VStack>
|
||||
</Button>
|
||||
|
||||
@@ -3,16 +3,15 @@ import { FolderPlusIcon } from '@/components/icons'
|
||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { Plan } from '@typebot.io/prisma'
|
||||
import React from 'react'
|
||||
import { useI18n, useScopedI18n } from '@/locales'
|
||||
import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
|
||||
import { LockTag } from '@/features/billing/components/LockTag'
|
||||
import { isFreePlan } from '@/features/billing/helpers/isFreePlan'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = { isLoading: boolean; onClick: () => void }
|
||||
|
||||
export const CreateFolderButton = ({ isLoading, onClick }: Props) => {
|
||||
const t = useI18n()
|
||||
const scopedT = useScopedI18n('folders.createFolderButton')
|
||||
const { t } = useTranslate()
|
||||
const { workspace } = useWorkspace()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
|
||||
@@ -27,7 +26,7 @@ export const CreateFolderButton = ({ isLoading, onClick }: Props) => {
|
||||
isLoading={isLoading}
|
||||
>
|
||||
<HStack>
|
||||
<Text>{scopedT('label')}</Text>
|
||||
<Text>{t('folders.createFolderButton.label')}</Text>
|
||||
{isFreePlan(workspace) && <LockTag plan={Plan.STARTER} />}
|
||||
</HStack>
|
||||
<ChangePlanModal
|
||||
|
||||
@@ -24,8 +24,8 @@ import { useRouter } from 'next/router'
|
||||
import React, { useMemo } from 'react'
|
||||
import { deleteFolderQuery } from '../queries/deleteFolderQuery'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { useI18n, useScopedI18n } from '@/locales'
|
||||
import { updateFolderQuery } from '../queries/updateFolderQuery'
|
||||
import { T, useTranslate } from '@tolgee/react'
|
||||
|
||||
export const FolderButton = ({
|
||||
folder,
|
||||
@@ -36,8 +36,7 @@ export const FolderButton = ({
|
||||
onFolderDeleted: () => void
|
||||
onFolderRenamed: (newName: string) => void
|
||||
}) => {
|
||||
const t = useI18n()
|
||||
const scopedT = useScopedI18n('folders.folderButton')
|
||||
const { t } = useTranslate()
|
||||
const router = useRouter()
|
||||
const { draggedTypebot, setMouseOverFolderId, mouseOverFolderId } =
|
||||
useTypebotDnd()
|
||||
@@ -140,9 +139,12 @@ export const FolderButton = ({
|
||||
confirmButtonLabel={'Delete'}
|
||||
message={
|
||||
<Text>
|
||||
{scopedT('deleteConfirmationMessage', {
|
||||
folderName: <strong>{folder.name}</strong>,
|
||||
})}
|
||||
<T
|
||||
keyName="folders.folderButton.deleteConfirmationMessage"
|
||||
params={{
|
||||
strong: <strong>{folder.name}</strong>,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
}
|
||||
title={`Delete ${folder.name}?`}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { CreateFolderButton } from './CreateFolderButton'
|
||||
import { ButtonSkeleton, FolderButton } from './FolderButton'
|
||||
import { TypebotButton } from './TypebotButton'
|
||||
import { TypebotCardOverlay } from './TypebotButtonOverlay'
|
||||
import { useI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { useTypebots } from '@/features/dashboard/hooks/useTypebots'
|
||||
import { TypebotInDashboard } from '@/features/dashboard/types'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
@@ -31,7 +31,7 @@ type Props = { folder: DashboardFolder | null }
|
||||
const dragDistanceTolerance = 20
|
||||
|
||||
export const FolderContent = ({ folder }: Props) => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
const { workspace, currentRole } = useWorkspace()
|
||||
const [isCreatingFolder, setIsCreatingFolder] = useState(false)
|
||||
const {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Seo } from '@/components/Seo'
|
||||
import { DashboardHeader } from '@/features/dashboard/components/DashboardHeader'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { useI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { Stack, Flex, Spinner } from '@chakra-ui/react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useFolder } from '../hooks/useFolder'
|
||||
@@ -9,7 +9,7 @@ import { TypebotDndProvider } from '../TypebotDndProvider'
|
||||
import { FolderContent } from './FolderContent'
|
||||
|
||||
export const FolderPage = () => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
const router = useRouter()
|
||||
|
||||
const { showToast } = useToast()
|
||||
|
||||
@@ -21,7 +21,7 @@ import { useDebounce } from 'use-debounce'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { MoreButton } from './MoreButton'
|
||||
import { EmojiOrImageIcon } from '@/components/EmojiOrImageIcon'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { T, useTranslate } from '@tolgee/react'
|
||||
import { TypebotInDashboard } from '@/features/dashboard/types'
|
||||
import { isMobile } from '@/helpers/isMobile'
|
||||
import { trpc, trpcVanilla } from '@/lib/trpc'
|
||||
@@ -40,7 +40,7 @@ export const TypebotButton = ({
|
||||
onTypebotUpdated,
|
||||
onMouseDown,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('folders.typebotButton')
|
||||
const { t } = useTranslate()
|
||||
const router = useRouter()
|
||||
const { draggedTypebot } = useTypebotDnd()
|
||||
const [draggedTypebotDebounced] = useDebounce(draggedTypebot, 200)
|
||||
@@ -149,7 +149,7 @@ export const TypebotButton = ({
|
||||
top="27px"
|
||||
size="sm"
|
||||
>
|
||||
{scopedT('live')}
|
||||
{t('folders.typebotButton.live')}
|
||||
</Tag>
|
||||
)}
|
||||
{!isReadOnly && (
|
||||
@@ -169,18 +169,18 @@ export const TypebotButton = ({
|
||||
pos="absolute"
|
||||
top="20px"
|
||||
right="20px"
|
||||
aria-label={scopedT('showMoreOptions')}
|
||||
aria-label={t('folders.typebotButton.showMoreOptions')}
|
||||
>
|
||||
{typebot.publishedTypebotId && (
|
||||
<MenuItem onClick={handleUnpublishClick}>
|
||||
{scopedT('unpublish')}
|
||||
{t('folders.typebotButton.unpublish')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem onClick={handleDuplicateClick}>
|
||||
{scopedT('duplicate')}
|
||||
{t('folders.typebotButton.duplicate')}
|
||||
</MenuItem>
|
||||
<MenuItem color="red.400" onClick={handleDeleteClick}>
|
||||
{scopedT('delete')}
|
||||
{t('folders.typebotButton.delete')}
|
||||
</MenuItem>
|
||||
</MoreButton>
|
||||
</>
|
||||
@@ -203,13 +203,16 @@ export const TypebotButton = ({
|
||||
message={
|
||||
<Stack spacing="4">
|
||||
<Text>
|
||||
{scopedT('deleteConfirmationMessage', {
|
||||
typebotName: <strong>{typebot.name}</strong>,
|
||||
})}
|
||||
<T
|
||||
keyName="folders.typebotButton.deleteConfirmationMessage"
|
||||
params={{
|
||||
strong: <strong>{typebot.name}</strong>,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
<Alert status="warning">
|
||||
<AlertIcon />
|
||||
{scopedT('deleteConfirmationMessageWarning')}
|
||||
{t('folders.typebotButton.deleteConfirmationMessageWarning')}
|
||||
</Alert>
|
||||
</Stack>
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ 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'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { ZemanticAiNodeBody } from '@/features/blocks/integrations/zemanticAi/ZemanticAiNodeBody'
|
||||
|
||||
type Props = {
|
||||
@@ -50,7 +50,7 @@ type Props = {
|
||||
indices: BlockIndices
|
||||
}
|
||||
export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
|
||||
const scopedT = useScopedI18n('editor.blocks.start')
|
||||
const { t } = useTranslate()
|
||||
switch (block.type) {
|
||||
case BubbleBlockType.TEXT: {
|
||||
return <TextBubbleContent block={block} />
|
||||
@@ -201,7 +201,7 @@ export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
|
||||
return <ZemanticAiNodeBody options={block.options} />
|
||||
}
|
||||
case 'start': {
|
||||
return <Text>{scopedT('text')}</Text>
|
||||
return <Text>{t('editor.blocks.start.text')}</Text>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import { isNotDefined } from '@typebot.io/lib'
|
||||
import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
|
||||
import { isFreePlan } from '@/features/billing/helpers/isFreePlan'
|
||||
import { parseTimeSince } from '@/helpers/parseTimeSince'
|
||||
import { useI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { parseDefaultPublicId } from '../helpers/parseDefaultPublicId'
|
||||
@@ -39,7 +39,7 @@ export const PublishButton = ({
|
||||
isMoreMenuDisabled = false,
|
||||
...props
|
||||
}: Props) => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
const warningTextColor = useColorModeValue('red.300', 'red.600')
|
||||
const { workspace } = useWorkspace()
|
||||
const { push, query, pathname } = useRouter()
|
||||
|
||||
@@ -24,12 +24,12 @@ import { hasProPerks } from '@/features/billing/helpers/hasProPerks'
|
||||
import { CustomDomainsDropdown } from '@/features/customDomains/components/CustomDomainsDropdown'
|
||||
import { TypebotHeader } from '@/features/editor/components/TypebotHeader'
|
||||
import { parseDefaultPublicId } from '../helpers/parseDefaultPublicId'
|
||||
import { useI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { env } from '@typebot.io/env'
|
||||
import DomainStatusIcon from '@/features/customDomains/components/DomainStatusIcon'
|
||||
|
||||
export const SharePage = () => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
const { workspace } = useWorkspace()
|
||||
const { typebot, updateTypebot, publishedTypebot } = useTypebot()
|
||||
const { showToast } = useToast()
|
||||
|
||||
@@ -16,10 +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'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
export const CreateNewTypebotButtons = () => {
|
||||
const scopedT = useScopedI18n('templates.buttons')
|
||||
const { t } = useTranslate()
|
||||
const { workspace } = useWorkspace()
|
||||
const { user } = useUser()
|
||||
const router = useRouter()
|
||||
@@ -72,7 +72,7 @@ export const CreateNewTypebotButtons = () => {
|
||||
|
||||
return (
|
||||
<VStack maxW="600px" w="full" flex="1" pt="20" spacing={10}>
|
||||
<Heading>{scopedT('heading')}</Heading>
|
||||
<Heading>{t('templates.buttons.heading')}</Heading>
|
||||
<Stack w="full" spacing={6}>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -89,7 +89,7 @@ export const CreateNewTypebotButtons = () => {
|
||||
onClick={() => handleCreateSubmit()}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{scopedT('fromScratchButton.label')}
|
||||
{t('templates.buttons.fromScratchButton.label')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -106,7 +106,7 @@ export const CreateNewTypebotButtons = () => {
|
||||
onClick={onOpen}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{scopedT('fromTemplateButton.label')}
|
||||
{t('templates.buttons.fromTemplateButton.label')}
|
||||
</Button>
|
||||
<ImportTypebotFromFileButton
|
||||
variant="outline"
|
||||
@@ -123,7 +123,7 @@ export const CreateNewTypebotButtons = () => {
|
||||
isLoading={isLoading}
|
||||
onNewTypebot={handleCreateSubmit}
|
||||
>
|
||||
{scopedT('importFileButton.label')}
|
||||
{t('templates.buttons.importFileButton.label')}
|
||||
</ImportTypebotFromFileButton>
|
||||
</Stack>
|
||||
<TemplatesModal
|
||||
|
||||
@@ -4,7 +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'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
onNewTypebot: (typebot: Typebot) => void
|
||||
@@ -14,7 +14,7 @@ export const ImportTypebotFromFileButton = ({
|
||||
onNewTypebot,
|
||||
...props
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('templates.importFromFileButon')
|
||||
const { t } = useTranslate()
|
||||
const { showToast } = useToast()
|
||||
|
||||
const handleInputChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -29,7 +29,7 @@ export const ImportTypebotFromFileButton = ({
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
showToast({
|
||||
description: scopedT('toastError.description'),
|
||||
description: t('templates.importFromFileButon.toastError.description'),
|
||||
details: {
|
||||
content: JSON.stringify(err, null, 2),
|
||||
lang: 'json',
|
||||
|
||||
@@ -19,7 +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'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
@@ -28,7 +28,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
const scopedT = useScopedI18n('templates.modal')
|
||||
const { t } = useTranslate()
|
||||
const templateCardBackgroundColor = useColorModeValue('white', 'gray.800')
|
||||
const [typebot, setTypebot] = useState<Typebot>()
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<TemplateProps>(
|
||||
@@ -90,7 +90,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
pl="1"
|
||||
color="gray.500"
|
||||
>
|
||||
{scopedT('menuHeading.marketing')}
|
||||
{t('templates.modal.menuHeading.marketing')}
|
||||
</Text>
|
||||
{templates
|
||||
.filter((template) => template.category === 'marketing')
|
||||
@@ -112,7 +112,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
<Text>{template.name}</Text>
|
||||
{template.isNew && (
|
||||
<Tag colorScheme="orange" size="sm" flexShrink={0}>
|
||||
{scopedT('menuHeading.new.tag')}
|
||||
{t('templates.modal.menuHeading.new.tag')}
|
||||
</Tag>
|
||||
)}
|
||||
</HStack>
|
||||
@@ -126,7 +126,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
pl="1"
|
||||
color="gray.500"
|
||||
>
|
||||
{scopedT('menuHeading.product')}
|
||||
{t('templates.modal.menuHeading.product')}
|
||||
</Text>
|
||||
{templates
|
||||
.filter((template) => template.category === 'product')
|
||||
@@ -148,7 +148,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
<Text>{template.name}</Text>
|
||||
{template.isNew && (
|
||||
<Tag colorScheme="orange" size="sm" flexShrink={0}>
|
||||
{scopedT('menuHeading.new.tag')}
|
||||
{t('templates.modal.menuHeading.new.tag')}
|
||||
</Tag>
|
||||
)}
|
||||
</HStack>
|
||||
@@ -162,7 +162,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
pl="1"
|
||||
color="gray.500"
|
||||
>
|
||||
{scopedT('menuHeading.other')}
|
||||
{t('templates.modal.menuHeading.other')}
|
||||
</Text>
|
||||
{templates
|
||||
.filter((template) => template.category === undefined)
|
||||
@@ -184,7 +184,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
<Text>{template.name}</Text>
|
||||
{template.isNew && (
|
||||
<Tag colorScheme="orange" size="sm" flexShrink={0}>
|
||||
{scopedT('menuHeading.new.tag')}
|
||||
{t('templates.modal.menuHeading.new.tag')}
|
||||
</Tag>
|
||||
)}
|
||||
</HStack>
|
||||
@@ -231,7 +231,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
onClick={onUseThisTemplateClick}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{scopedT('useTemplateButton.label')}
|
||||
{t('templates.modal.useTemplateButton.label')}
|
||||
</Button>
|
||||
</HStack>
|
||||
</Stack>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Plan } from '@typebot.io/prisma'
|
||||
import { isFreePlan } from '@/features/billing/helpers/isFreePlan'
|
||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
|
||||
import { useI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
isBrandingEnabled: boolean
|
||||
@@ -23,7 +23,7 @@ export const GeneralSettings = ({
|
||||
onGeneralThemeChange,
|
||||
onBrandingChange,
|
||||
}: Props) => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const { workspace } = useWorkspace()
|
||||
const isWorkspaceFreePlan = isFreePlan(workspace)
|
||||
|
||||
@@ -13,7 +13,7 @@ import { WorkspaceInvitation, WorkspaceRole } from '@typebot.io/prisma'
|
||||
import { FormEvent, useState } from 'react'
|
||||
import { Member } from '../types'
|
||||
import { sendInvitationQuery } from '../queries/sendInvitationQuery'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
workspaceId: string
|
||||
@@ -29,7 +29,7 @@ export const AddMemberForm = ({
|
||||
isLoading,
|
||||
isLocked,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('workspace.membersList')
|
||||
const { t } = useTranslate()
|
||||
const [invitationEmail, setInvitationEmail] = useState('')
|
||||
const [invitationRole, setInvitationRole] = useState<WorkspaceRole>(
|
||||
WorkspaceRole.MEMBER
|
||||
@@ -54,7 +54,7 @@ export const AddMemberForm = ({
|
||||
return (
|
||||
<HStack as="form" onSubmit={handleInvitationSubmit}>
|
||||
<Input
|
||||
placeholder={scopedT('inviteInput.placeholder')}
|
||||
placeholder={t('workspace.membersList.inviteInput.placeholder')}
|
||||
name="inviteEmail"
|
||||
value={invitationEmail}
|
||||
onChange={(e) => setInvitationEmail(e.target.value)}
|
||||
@@ -75,7 +75,7 @@ export const AddMemberForm = ({
|
||||
type="submit"
|
||||
isDisabled={isLoading || isLocked || invitationEmail === ''}
|
||||
>
|
||||
{scopedT('inviteButton.label')}
|
||||
{t('workspace.membersList.inviteButton.label')}
|
||||
</Button>
|
||||
</HStack>
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { WorkspaceRole } from '@typebot.io/prisma'
|
||||
import React from 'react'
|
||||
import { convertWorkspaceRoleToReadable } from './AddMemberForm'
|
||||
import { useI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
image?: string
|
||||
@@ -38,7 +38,7 @@ export const MemberItem = ({
|
||||
onDeleteClick,
|
||||
onSelectNewRole,
|
||||
}: Props) => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
const handleAdminClick = () => onSelectNewRole(WorkspaceRole.ADMIN)
|
||||
const handleMemberClick = () => onSelectNewRole(WorkspaceRole.MEMBER)
|
||||
|
||||
@@ -88,7 +88,7 @@ export const MemberIdentityContent = ({
|
||||
isGuest?: boolean
|
||||
email: string
|
||||
}) => {
|
||||
const t = useI18n()
|
||||
const { t } = useTranslate()
|
||||
|
||||
return (
|
||||
<HStack justifyContent="space-between" maxW="full" p="2">
|
||||
|
||||
@@ -19,11 +19,11 @@ import { updateInvitationQuery } from '../queries/updateInvitationQuery'
|
||||
import { updateMemberQuery } from '../queries/updateMemberQuery'
|
||||
import { Member } from '../types'
|
||||
import { useWorkspace } from '../WorkspaceProvider'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { getSeatsLimit } from '@typebot.io/lib/billing/getSeatsLimit'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
export const MembersList = () => {
|
||||
const scopedT = useScopedI18n('workspace.membersList')
|
||||
const { t } = useTranslate()
|
||||
const { user } = useUser()
|
||||
const { workspace, currentRole } = useWorkspace()
|
||||
const { members, invitations, isLoading, mutate } = useMembers({
|
||||
@@ -103,12 +103,12 @@ export const MembersList = () => {
|
||||
<Stack w="full" spacing={3}>
|
||||
{!canInviteNewMember && (
|
||||
<UnlockPlanAlertInfo>
|
||||
{scopedT('unlockBanner.label')}
|
||||
{t('workspace.membersList.unlockBanner.label')}
|
||||
</UnlockPlanAlertInfo>
|
||||
)}
|
||||
{isDefined(seatsLimit) && (
|
||||
<Heading fontSize="2xl">
|
||||
{scopedT('title')}{' '}
|
||||
{t('workspace.membersList.title')}{' '}
|
||||
{seatsLimit === -1 ? '' : `(${currentMembersCount}/${seatsLimit})`}
|
||||
</Heading>
|
||||
)}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@/components/icons'
|
||||
import { PlanTag } from '@/features/billing/components/PlanTag'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
@@ -32,7 +32,7 @@ export const WorkspaceDropdown = ({
|
||||
onLogoutClick,
|
||||
onCreateNewWorkspaceClick,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('workspace.dropdown')
|
||||
const { t } = useTranslate()
|
||||
const { data } = trpc.workspace.listWorkspaces.useQuery()
|
||||
|
||||
const workspaces = data?.workspaces ?? []
|
||||
@@ -72,14 +72,14 @@ export const WorkspaceDropdown = ({
|
||||
</MenuItem>
|
||||
))}
|
||||
<MenuItem onClick={onCreateNewWorkspaceClick} icon={<PlusIcon />}>
|
||||
{scopedT('newButton.label')}
|
||||
{t('workspace.dropdown.newButton.label')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={onLogoutClick}
|
||||
icon={<LogOutIcon />}
|
||||
color="orange.500"
|
||||
>
|
||||
{scopedT('logoutButton.label')}
|
||||
{t('workspace.dropdown.logoutButton.label')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
@@ -12,10 +12,10 @@ import React from 'react'
|
||||
import { EditableEmojiOrImageIcon } from '@/components/EditableEmojiOrImageIcon'
|
||||
import { useWorkspace } from '../WorkspaceProvider'
|
||||
import { TextInput } from '@/components/inputs'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
export const WorkspaceSettingsForm = ({ onClose }: { onClose: () => void }) => {
|
||||
const scopedT = useScopedI18n('workspace.settings')
|
||||
const { t } = useTranslate()
|
||||
const { workspace, workspaces, updateWorkspace, deleteCurrentWorkspace } =
|
||||
useWorkspace()
|
||||
|
||||
@@ -34,7 +34,7 @@ export const WorkspaceSettingsForm = ({ onClose }: { onClose: () => void }) => {
|
||||
return (
|
||||
<Stack spacing="6" w="full">
|
||||
<FormControl>
|
||||
<FormLabel>{scopedT('icon.title')}</FormLabel>
|
||||
<FormLabel>{t('workspace.settings.icon.title')}</FormLabel>
|
||||
<Flex>
|
||||
{workspace && (
|
||||
<EditableEmojiOrImageIcon
|
||||
@@ -51,7 +51,7 @@ export const WorkspaceSettingsForm = ({ onClose }: { onClose: () => void }) => {
|
||||
</FormControl>
|
||||
{workspace && (
|
||||
<TextInput
|
||||
label={scopedT('name.label')}
|
||||
label={t('workspace.settings.name.label')}
|
||||
withVariableButton={false}
|
||||
defaultValue={workspace?.name}
|
||||
onChange={handleNameChange}
|
||||
@@ -74,12 +74,12 @@ const DeleteWorkspaceButton = ({
|
||||
workspaceName: string
|
||||
onConfirm: () => Promise<void>
|
||||
}) => {
|
||||
const scopedT = useScopedI18n('workspace.settings')
|
||||
const { t } = useTranslate()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
return (
|
||||
<>
|
||||
<Button colorScheme="red" variant="outline" onClick={onOpen}>
|
||||
{scopedT('deleteButton.label')}
|
||||
{t('workspace.settings.deleteButton.label')}
|
||||
</Button>
|
||||
<ConfirmModal
|
||||
isOpen={isOpen}
|
||||
@@ -87,7 +87,7 @@ const DeleteWorkspaceButton = ({
|
||||
onClose={onClose}
|
||||
message={
|
||||
<Text>
|
||||
{scopedT('deleteButton.confirmMessage', {
|
||||
{t('workspace.settings.deleteButton.confirmMessage', {
|
||||
workspaceName,
|
||||
})}
|
||||
</Text>
|
||||
|
||||
@@ -24,7 +24,7 @@ import packageJson from '../../../../../../package.json'
|
||||
import { UserPreferencesForm } from '@/features/account/components/UserPreferencesForm'
|
||||
import { MyAccountForm } from '@/features/account/components/MyAccountForm'
|
||||
import { BillingSettingsLayout } from '@/features/billing/components/BillingSettingsLayout'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
@@ -46,7 +46,7 @@ export const WorkspaceSettingsModal = ({
|
||||
workspace,
|
||||
onClose,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('workspace.settings.modal')
|
||||
const { t } = useTranslate()
|
||||
const { currentRole } = useWorkspace()
|
||||
const [selectedTab, setSelectedTab] = useState<SettingsTab>('my-account')
|
||||
|
||||
@@ -82,7 +82,7 @@ export const WorkspaceSettingsModal = ({
|
||||
justifyContent="flex-start"
|
||||
pl="4"
|
||||
>
|
||||
{scopedT('menu.myAccount.label')}
|
||||
{t('workspace.settings.modal.menu.myAccount.label')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={selectedTab === 'user-settings' ? 'solid' : 'ghost'}
|
||||
@@ -92,12 +92,12 @@ export const WorkspaceSettingsModal = ({
|
||||
justifyContent="flex-start"
|
||||
pl="4"
|
||||
>
|
||||
{scopedT('menu.preferences.label')}
|
||||
{t('workspace.settings.modal.menu.preferences.label')}
|
||||
</Button>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text pl="4" color="gray.500">
|
||||
{scopedT('menu.workspace.label')}
|
||||
{t('workspace.settings.modal.menu.workspace.label')}
|
||||
</Text>
|
||||
{canEditWorkspace && (
|
||||
<Button
|
||||
@@ -116,7 +116,7 @@ export const WorkspaceSettingsModal = ({
|
||||
justifyContent="flex-start"
|
||||
pl="4"
|
||||
>
|
||||
{scopedT('menu.settings.label')}
|
||||
{t('workspace.settings.modal.menu.settings.label')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
@@ -127,7 +127,7 @@ export const WorkspaceSettingsModal = ({
|
||||
justifyContent="flex-start"
|
||||
pl="4"
|
||||
>
|
||||
{scopedT('menu.members.label')}
|
||||
{t('workspace.settings.modal.menu.members.label')}
|
||||
</Button>
|
||||
{canEditWorkspace && (
|
||||
<Button
|
||||
@@ -140,7 +140,7 @@ export const WorkspaceSettingsModal = ({
|
||||
overflow="scroll"
|
||||
className="hide-scrollbar"
|
||||
>
|
||||
{scopedT('menu.billingAndUsage.label')}
|
||||
{t('workspace.settings.modal.menu.billingAndUsage.label')}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -148,7 +148,9 @@ export const WorkspaceSettingsModal = ({
|
||||
|
||||
<Flex justify="center" pt="10">
|
||||
<Text color="gray.500" fontSize="xs">
|
||||
{scopedT('menu.version.label', { version: packageJson.version })}
|
||||
{t('workspace.settings.modal.menu.version.label', {
|
||||
version: packageJson.version,
|
||||
})}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user