2
0

🌐 Introduce i18n

Only translate dashboard page for now

Closes #322
This commit is contained in:
Baptiste Arnaud
2023-03-11 11:05:07 +01:00
parent 8df830721c
commit 138f3f8b07
24 changed files with 237 additions and 183 deletions

View File

@ -7,6 +7,10 @@ const withTM = require('next-transpile-modules')(['utils', 'models', 'emails'])
const nextConfig = withTM({
reactStrictMode: true,
output: 'standalone',
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr', 'pr'],
},
experimental: {
outputFileTracingRoot: path.join(__dirname, '../../'),
},

View File

@ -28,6 +28,7 @@
"@giphy/react-components": "6.5.2",
"@googleapis/drive": "4.0.2",
"@lezer/css": "^1.1.1",
"@paralleldrive/cuid2": "2.2.0",
"@sentry/nextjs": "7.38.0",
"@stripe/stripe-js": "1.47.0",
"@tanstack/react-query": "^4.24.10",
@ -36,6 +37,8 @@
"@trpc/next": "10.12.0",
"@trpc/react-query": "10.12.0",
"@trpc/server": "10.12.0",
"@typebot.io/js": "workspace:*",
"@typebot.io/react": "workspace:*",
"@udecode/plate-basic-marks": "19.2.0",
"@udecode/plate-common": "^7.0.2",
"@udecode/plate-core": "19.2.0",
@ -43,18 +46,15 @@
"@udecode/plate-serializer-html": "19.2.0",
"@udecode/plate-ui-link": "19.2.0",
"@udecode/plate-ui-toolbar": "19.2.0",
"@uiw/codemirror-extensions-langs": "^4.19.9",
"@uiw/codemirror-theme-github": "^4.19.9",
"@uiw/codemirror-theme-tokyo-night": "^4.19.9",
"@uiw/react-codemirror": "^4.19.9",
"@use-gesture/react": "^10.2.24",
"aws-sdk": "2.1321.0",
"browser-image-compression": "2.0.0",
"canvas-confetti": "1.6.0",
"codemirror": "6.0.1",
"@paralleldrive/cuid2": "2.2.0",
"@typebot.io/js": "workspace:*",
"@typebot.io/react": "workspace:*",
"@uiw/codemirror-extensions-langs": "^4.19.9",
"@uiw/codemirror-theme-github": "^4.19.9",
"@uiw/codemirror-theme-tokyo-night": "^4.19.9",
"@uiw/react-codemirror": "^4.19.9",
"deep-object-diff": "1.1.9",
"dequal": "2.0.3",
"emails": "workspace:*",
@ -73,6 +73,7 @@
"minio": "7.0.32",
"next": "13.1.6",
"next-auth": "4.19.2",
"@typebot.io/next-international": "^0.3.8",
"nextjs-cors": "^2.1.2",
"nodemailer": "6.9.1",
"nprogress": "0.2.0",

View File

@ -8,6 +8,7 @@ import {
AlertDialogOverlay,
Button,
} from '@chakra-ui/react'
import { useScopedI18n } from '@/locales'
type ConfirmDeleteModalProps = {
isOpen: boolean
@ -28,6 +29,7 @@ export const ConfirmModal = ({
onConfirm,
confirmButtonColor = 'red',
}: ConfirmDeleteModalProps) => {
const scopedT = useScopedI18n('confirmModal')
const [confirmLoading, setConfirmLoading] = useState(false)
const cancelRef = useRef(null)
@ -52,7 +54,7 @@ export const ConfirmModal = ({
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
{title ?? 'Are you sure?'}
{title ?? scopedT('defaultTitle')}
</AlertDialogHeader>
<AlertDialogBody>{message}</AlertDialogBody>

View File

@ -1,37 +0,0 @@
import { CloseButton, Flex, HStack, StackProps } from '@chakra-ui/react'
import React, { useEffect, useState } from 'react'
type VerifyEmailBannerProps = { id: string } & StackProps
export const Banner = ({ id, ...props }: VerifyEmailBannerProps) => {
const [show, setShow] = useState(false)
const localStorageKey = `banner-${id}`
useEffect(() => {
if (!localStorage.getItem(localStorageKey)) setShow(true)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const handleCloseClick = () => {
localStorage.setItem(localStorageKey, 'hide')
setShow(false)
}
if (!show) return <></>
return (
<HStack
h="50px"
bgColor="blue.400"
color="white"
justifyContent="center"
align="center"
w="full"
{...props}
>
<Flex maxW="1000px" justifyContent="space-between" w="full">
<HStack>{props.children}</HStack>
<CloseButton rounded="full" onClick={handleCloseClick} />
</Flex>
</HStack>
)
}

View File

@ -1,70 +0,0 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
Text,
Stack,
Link,
} from '@chakra-ui/react'
import React, { useEffect, useState } from 'react'
type Props = {
isOpen: boolean
onClose: () => void
}
const localStorageKey = 'typebot-20-modal'
export const AnnoucementModal = ({ isOpen, onClose }: Props) => {
const [show, setShow] = useState(false)
useEffect(() => {
if (!localStorage.getItem(localStorageKey)) setShow(true)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const handleCloseClick = () => {
localStorage.setItem(localStorageKey, 'hide')
setShow(false)
onClose()
}
if (!show) return <></>
return (
<Modal isOpen={isOpen} onClose={handleCloseClick} size="2xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>What&apos;s new in Typebot 2.0?</ModalHeader>
<ModalCloseButton />
<ModalBody as={Stack} spacing="6" pb="10">
<Text>Typebo 2.0 has been launched February the 15th 🎉.</Text>
<iframe
width="620"
height="315"
src="https://www.youtube.com/embed/u8FZHvlYviw"
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
style={{ borderRadius: '5px' }}
/>
<Text>
Most questions are answered in this{' '}
<Link
href="https://docs.typebot.io"
color="blue.500"
textDecor="underline"
>
FAQ
</Link>
. If you have other questions, open up the bot on the bottom right
corner. 😃
</Text>
<Text>Baptiste.</Text>
</ModalBody>
</ModalContent>
</Modal>
)
}

View File

@ -8,8 +8,10 @@ import { isNotDefined } from 'utils'
import Link from 'next/link'
import { WorkspaceSettingsModal } from '@/features/workspace'
import { EmojiOrImageIcon } from '@/components/EmojiOrImageIcon'
import { useScopedI18n } from '@/locales'
export const DashboardHeader = () => {
const scopedT = useScopedI18n('dashboard.header')
const { user } = useUser()
const { workspace, switchWorkspace, createWorkspace } = useWorkspace()
@ -52,7 +54,7 @@ export const DashboardHeader = () => {
onClick={onOpen}
isLoading={isNotDefined(workspace)}
>
Settings & Members
{scopedT('settingsButton.label')}
</Button>
<WorkspaceDropdown
currentWorkspace={workspace}

View File

@ -7,6 +7,7 @@ import {
import { TypebotDndProvider, FolderContent } from '@/features/folders'
import { ParentModalProvider } from '@/features/graph'
import { useWorkspace } from '@/features/workspace'
import { useScopedI18n } from '@/locales'
import { Stack, VStack, Spinner, Text } from '@chakra-ui/react'
import { Plan } from 'db'
import { useRouter } from 'next/router'
@ -15,6 +16,7 @@ import { guessIfUserIsEuropean } from 'utils/pricing'
import { DashboardHeader } from './DashboardHeader'
export const DashboardPage = () => {
const scopedT = useScopedI18n('dashboard')
const [isLoading, setIsLoading] = useState(false)
const { query } = useRouter()
const { user } = useUser()
@ -42,7 +44,7 @@ export const DashboardPage = () => {
return (
<Stack minH="100vh">
<Seo title={workspace?.name ?? 'My typebots'} />
<Seo title={workspace?.name ?? scopedT('title')} />
<DashboardHeader />
{!workspace?.stripeId && (
<ParentModalProvider>
@ -57,7 +59,7 @@ export const DashboardPage = () => {
<TypebotDndProvider>
{isLoading ? (
<VStack w="full" justifyContent="center" pt="10" spacing={6}>
<Text>You are being redirected...</Text>
<Text>{scopedT('redirectionMessage')}</Text>
<Spinner />
</VStack>
) : (

View File

@ -40,7 +40,7 @@ test('folders and typebots should be deletable', async ({ page }) => {
await page.click('li:has-text("Folder #1") >> button:has-text("Delete")')
await deleteButtonInConfirmDialog(page).click()
await expect(page.locator('span >> text="Folder #1"')).not.toBeVisible()
await page.click('button[aria-label="Show Typebot #1 menu"]')
await page.click('button[aria-label="Show more options"]')
await page.click('li:has-text("Typebot #1") >> button:has-text("Delete")')
await deleteButtonInConfirmDialog(page).click()
await expect(page.locator('span >> text="Typebot #1"')).not.toBeVisible()

View File

@ -3,8 +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'
export const BackButton = ({ id }: { id: string | null }) => {
const t = useI18n()
const { draggedTypebot, setMouseOverFolderId, mouseOverFolderId } =
useTypebotDnd()
@ -26,7 +28,7 @@ export const BackButton = ({ id }: { id: string | null }) => {
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
Back
{t('back')}
</Button>
)
}

View File

@ -3,12 +3,14 @@ import { PlusIcon } from '@/components/icons'
import { useRouter } from 'next/router'
import { stringify } from 'qs'
import React from 'react'
import { useScopedI18n } from '@/locales'
export const CreateBotButton = ({
folderId,
isFirstBot,
...props
}: { folderId?: string; isFirstBot: boolean } & ButtonProps) => {
const scopedT = useScopedI18n('folders.createTypebotButton')
const router = useRouter()
const handleClick = () =>
@ -39,7 +41,7 @@ export const CreateBotButton = ({
textAlign="center"
mt="6"
>
Create a typebot
{scopedT('label')}
</Text>
</VStack>
</Button>

View File

@ -9,10 +9,12 @@ import {
import { useWorkspace } from '@/features/workspace'
import { Plan } from 'db'
import React from 'react'
import { useScopedI18n } from '@/locales'
type Props = { isLoading: boolean; onClick: () => void }
export const CreateFolderButton = ({ isLoading, onClick }: Props) => {
const scopedT = useScopedI18n('folders.createFolderButton')
const { workspace } = useWorkspace()
const { isOpen, onOpen, onClose } = useDisclosure()
@ -27,7 +29,7 @@ export const CreateFolderButton = ({ isLoading, onClick }: Props) => {
isLoading={isLoading}
>
<HStack>
<Text>Create a folder</Text>
<Text>{scopedT('label')}</Text>
{isFreePlan(workspace) && <LockTag plan={Plan.STARTER} />}
</HStack>
<ChangePlanModal

View File

@ -25,6 +25,7 @@ import React, { useMemo } from 'react'
import { deleteFolderQuery } from '../queries/deleteFolderQuery'
import { useToast } from '@/hooks/useToast'
import { updateFolderQuery } from '../queries/updateFolderQuery'
import { useI18n, useScopedI18n } from '@/locales'
export const FolderButton = ({
folder,
@ -35,6 +36,8 @@ export const FolderButton = ({
onFolderDeleted: () => void
onFolderRenamed: (newName: string) => void
}) => {
const t = useI18n()
const scopedT = useScopedI18n('folders.folderButton')
const router = useRouter()
const { draggedTypebot, setMouseOverFolderId, mouseOverFolderId } =
useTypebotDnd()
@ -49,7 +52,6 @@ export const FolderButton = ({
const { error } = await deleteFolderQuery(folder.id)
return error
? showToast({
title: "Couldn't delete the folder",
description: error.message,
})
: onFolderDeleted()
@ -59,7 +61,7 @@ export const FolderButton = ({
if (newName === '' || newName === folder.name) return
const { error } = await updateFolderQuery(folder.id, { name: newName })
return error
? showToast({ title: 'An error occured', description: error.message })
? showToast({ title: t('errorMessage'), description: error.message })
: onFolderRenamed(newName)
}
@ -106,7 +108,7 @@ export const FolderButton = ({
onOpen()
}}
>
Delete
{t('delete')}
</MenuItem>
</MenuList>
</Menu>
@ -138,8 +140,9 @@ export const FolderButton = ({
confirmButtonLabel={'Delete'}
message={
<Text>
Are you sure you want to delete <strong>{folder.name}</strong>{' '}
folder? (Everything inside will be move to your dashboard)
{scopedT('deleteConfirmationMessage', {
folderName: <strong>{folder.name}</strong>,
})}
</Text>
}
title={`Delete ${folder.name}?`}

View File

@ -24,12 +24,14 @@ import { CreateFolderButton } from './CreateFolderButton'
import { ButtonSkeleton, FolderButton } from './FolderButton'
import { TypebotButton } from './TypebotButton'
import { TypebotCardOverlay } from './TypebotButtonOverlay'
import { useI18n } from '@/locales'
type Props = { folder: DashboardFolder | null }
const dragDistanceTolerance = 20
export const FolderContent = ({ folder }: Props) => {
const t = useI18n()
const { workspace, currentRole } = useWorkspace()
const [isCreatingFolder, setIsCreatingFolder] = useState(false)
const {
@ -57,7 +59,9 @@ export const FolderContent = ({ folder }: Props) => {
workspaceId: workspace?.id,
parentId: folder?.id,
onError: (error) => {
showToast({ title: "Couldn't fetch folders", description: error.message })
showToast({
description: error.message,
})
},
})
@ -70,7 +74,6 @@ export const FolderContent = ({ folder }: Props) => {
folderId: folder === null ? 'root' : folder.id,
onError: (error) => {
showToast({
title: "Couldn't fetch typebots",
description: error.message,
})
},
@ -94,7 +97,7 @@ export const FolderContent = ({ folder }: Props) => {
setIsCreatingFolder(false)
if (error)
return showToast({
title: 'An error occured',
title: t('errorMessage'),
description: error.message,
})
if (newFolder) mutateFolders({ folders: [...folders, newFolder] })

View File

@ -1,6 +1,7 @@
import { Seo } from '@/components/Seo'
import { DashboardHeader } from '@/features/dashboard'
import { useToast } from '@/hooks/useToast'
import { useI18n } from '@/locales'
import { Stack, Flex, Spinner } from '@chakra-ui/react'
import { useRouter } from 'next/router'
import { useFolder } from '../hooks/useFolder'
@ -8,6 +9,7 @@ import { TypebotDndProvider } from '../TypebotDndProvider'
import { FolderContent } from './FolderContent'
export const FolderPage = () => {
const t = useI18n()
const router = useRouter()
const { showToast } = useToast()
@ -16,7 +18,6 @@ export const FolderPage = () => {
folderId: router.query.id?.toString(),
onError: (error) => {
showToast({
title: "Couldn't fetch folder content",
description: error.message,
})
},
@ -24,7 +25,7 @@ export const FolderPage = () => {
return (
<Stack minH="100vh">
<Seo title="My typebots" />
<Seo title={t('dashboard.title')} />
<DashboardHeader />
<TypebotDndProvider>
{!folder ? (

View File

@ -1,9 +1,12 @@
import React from 'react'
import {
Alert,
AlertIcon,
Button,
Flex,
IconButton,
MenuItem,
Stack,
Tag,
Text,
useDisclosure,
@ -28,6 +31,7 @@ import {
import { MoreButton } from './MoreButton'
import { EmojiOrImageIcon } from '@/components/EmojiOrImageIcon'
import { deletePublishedTypebotQuery } from '@/features/publish/queries/deletePublishedTypebotQuery'
import { useScopedI18n } from '@/locales'
type Props = {
typebot: TypebotInDashboard
@ -42,6 +46,7 @@ export const TypebotButton = ({
onTypebotUpdated,
onMouseDown,
}: Props) => {
const scopedT = useScopedI18n('folders.typebotButton')
const router = useRouter()
const { workspace } = useWorkspace()
const { draggedTypebot } = useTypebotDnd()
@ -68,7 +73,6 @@ export const TypebotButton = ({
const { error } = await deleteTypebotQuery(typebot.id)
if (error)
return showToast({
title: "Couldn't delete typebot",
description: error.message,
})
onTypebotUpdated()
@ -78,14 +82,13 @@ export const TypebotButton = ({
e.stopPropagation()
const { data } = await getTypebotQuery(typebot.id)
const typebotToDuplicate = data?.typebot
if (!typebotToDuplicate) return { error: new Error('Typebot not found') }
if (!typebotToDuplicate) return
const { data: createdTypebot, error } = await importTypebotQuery(
data.typebot,
workspace?.plan ?? Plan.FREE
)
if (error)
return showToast({
title: "Couldn't duplicate typebot",
description: error.message,
})
if (createdTypebot) router.push(`/typebots/${createdTypebot?.id}/edit`)
@ -153,14 +156,18 @@ export const TypebotButton = ({
pos="absolute"
top="20px"
right="20px"
aria-label={`Show ${typebot.name} menu`}
aria-label={scopedT('showMoreOptions')}
>
{typebot.publishedTypebotId && (
<MenuItem onClick={handleUnpublishClick}>Unpublish</MenuItem>
<MenuItem onClick={handleUnpublishClick}>
{scopedT('unpublish')}
</MenuItem>
)}
<MenuItem onClick={handleDuplicateClick}>Duplicate</MenuItem>
<MenuItem onClick={handleDuplicateClick}>
{scopedT('duplicate')}
</MenuItem>
<MenuItem color="red.400" onClick={handleDeleteClick}>
Delete
{scopedT('delete')}
</MenuItem>
</MoreButton>
</>
@ -181,12 +188,17 @@ export const TypebotButton = ({
{!isReadOnly && (
<ConfirmModal
message={
<Text>
Are you sure you want to delete your Typebot &quot;{typebot.name}
&quot;.
<br />
All associated data will be lost.
</Text>
<Stack spacing="4">
<Text>
{scopedT('deleteConfirmationMessage', {
typebotName: <strong>{typebot.name}</strong>,
})}
</Text>
<Alert status="warning">
<AlertIcon />
{scopedT('deleteConfirmationMessageWarning')}
</Alert>
</Stack>
}
confirmButtonLabel="Delete"
onConfirm={handleDeleteTypebotClick}

View File

@ -169,6 +169,11 @@ const Alert = createMultiStyleConfigHelpers(
}
},
},
baseStyle: {
container: {
borderRadius: 'md',
},
},
})
const Switch = createMultiStyleConfigHelpers(

View File

@ -0,0 +1,22 @@
export default {
back: 'Back',
'confirmModal.defaultTitle': 'Are you sure?',
'dashboard.header.settingsButton.label': 'Settings & Members',
'dashboard.redirectionMessage': 'You are being redirected...',
'dashboard.title': 'My typebots',
delete: 'Delete',
errorMessage: 'An error occured',
'folders.createFolderButton.label': 'Create a folder',
'folders.createTypebotButton.label': 'Create a typebot',
'folders.folderButton.deleteConfirmationMessage':
'Are you sure you want to delete {folderName} folder? (Everything inside will be move to your dashboard)',
'folders.typebotButton.live': 'Live',
'folders.typebotButton.showMoreOptions': 'Show more options',
'folders.typebotButton.unpublish': 'Unpublish',
'folders.typebotButton.duplicate': 'Duplicate',
'folders.typebotButton.delete': 'Delete',
'folders.typebotButton.deleteConfirmationMessage':
'Are you sure you want to delete your typebot {typebotName}?',
'folders.typebotButton.deleteConfirmationMessageWarning':
"All its associated data will be deleted and won't be recoverable.",
} as const

View File

@ -0,0 +1,24 @@
import { defineLocale } from './index'
export default defineLocale({
back: 'Retour',
'confirmModal.defaultTitle': 'Êtes-vous sûr ?',
'dashboard.header.settingsButton.label': 'Paramètres & Membres',
'dashboard.redirectionMessage': "Vous êtes en train d'être redirigé...",
'dashboard.title': 'Mes typebots',
delete: 'Supprimer',
errorMessage: "Une erreur s'est produite",
'folders.createFolderButton.label': 'Créer un dossier',
'folders.createTypebotButton.label': 'Créer un typebot',
'folders.folderButton.deleteConfirmationMessage':
"Êtes-vous sûr de vouloir supprimer le dossier {folderName} ? (Tout ce qui est à l'intérieur sera déplacé dans le dossier parent ou sur votre tableau de bord)",
'folders.typebotButton.live': 'Live',
'folders.typebotButton.showMoreOptions': "Afficher plus d'options",
'folders.typebotButton.unpublish': 'Dépublier',
'folders.typebotButton.duplicate': 'Dupliquer',
'folders.typebotButton.delete': 'Supprimer',
'folders.typebotButton.deleteConfirmationMessage':
'Êtes-vous sûr de vouloir supprimer votre typebot {typebotName} ?',
'folders.typebotButton.deleteConfirmationMessageWarning':
'Toutes les données associées seront supprimées et ne pourront pas être récupérées.',
})

View File

@ -0,0 +1,14 @@
import { createI18n } from '@typebot.io/next-international'
import type Locale from './en'
export const {
defineLocale,
useI18n,
useScopedI18n,
I18nProvider,
getLocaleProps,
} = createI18n<typeof Locale>({
en: () => import('./en'),
fr: () => import('./fr'),
pt: () => import('./pt'),
})

View File

@ -0,0 +1,24 @@
import { defineLocale } from './index'
export default defineLocale({
back: 'Voltar',
'confirmModal.defaultTitle': 'Tem certeza?',
'dashboard.header.settingsButton.label': 'Configurações & Membros',
'dashboard.redirectionMessage': 'Você está sendo redirecionado...',
'dashboard.title': 'Meus typebots',
delete: 'Deletar',
errorMessage: 'Ocorreu um erro',
'folders.createFolderButton.label': 'Criar uma pasta',
'folders.createTypebotButton.label': 'Criar um typebot',
'folders.folderButton.deleteConfirmationMessage':
'Tem certeza de que deseja excluir a pasta {folderName}? (Tudo o que estiver dentro será movido para o seu painel)',
'folders.typebotButton.live': 'Live',
'folders.typebotButton.showMoreOptions': 'Mostrar mais opções',
'folders.typebotButton.unpublish': 'Despublicar',
'folders.typebotButton.duplicate': 'Duplicar',
'folders.typebotButton.delete': 'Deletar',
'folders.typebotButton.deleteConfirmationMessage':
'Tem certeza de que deseja excluir seu typebot {typebotName}?',
'folders.typebotButton.deleteConfirmationMessageWarning':
'Todos os dados associados serão excluídos e não poderão ser recuperados.',
})

View File

@ -14,17 +14,14 @@ import { useRouter } from 'next/router'
import { SupportBubble } from '@/components/SupportBubble'
import { WorkspaceProvider } from '@/features/workspace'
import { toTitleCase } from 'utils'
import { Session } from 'next-auth'
import { Plan } from 'db'
import { trpc } from '@/lib/trpc'
import { NewVersionPopup } from '@/components/NewVersionPopup'
import { I18nProvider } from '@/locales'
const { ToastContainer, toast } = createStandaloneToast(customTheme)
const App = ({
Component,
pageProps: { session, ...pageProps },
}: AppProps<{ session?: Session }>) => {
const App = ({ Component, pageProps }: AppProps) => {
useRouterProgressBar()
const { query, pathname } = useRouter()
@ -50,19 +47,21 @@ const App = ({
return (
<>
<ToastContainer />
<ChakraProvider theme={customTheme}>
<SessionProvider session={session}>
<UserProvider>
<TypebotProvider typebotId={typebotId}>
<WorkspaceProvider typebotId={typebotId}>
<Component {...pageProps} />
<SupportBubble />
<NewVersionPopup />
</WorkspaceProvider>
</TypebotProvider>
</UserProvider>
</SessionProvider>
</ChakraProvider>
<I18nProvider locale={pageProps.locale}>
<ChakraProvider theme={customTheme}>
<SessionProvider session={pageProps.session}>
<UserProvider>
<TypebotProvider typebotId={typebotId}>
<WorkspaceProvider typebotId={typebotId}>
<Component {...pageProps} />
<SupportBubble />
<NewVersionPopup />
</WorkspaceProvider>
</TypebotProvider>
</UserProvider>
</SessionProvider>
</ChakraProvider>
</I18nProvider>
</>
)
}

View File

@ -1,3 +1,4 @@
import { getLocaleProps } from '@/locales'
import { GetServerSidePropsContext } from 'next'
import { getSession } from 'next-auth/react'
@ -5,12 +6,28 @@ export default function Page() {
return null
}
export const getServerSideProps = async (
context: GetServerSidePropsContext
) => {
const session = await getSession(context)
if (!session?.user) {
return { redirect: { permanent: false, destination: '/signin' } }
export const getServerSideProps = getLocaleProps(
async (context: GetServerSidePropsContext) => {
const session = await getSession(context)
if (!session?.user) {
return {
redirect: {
permanent: false,
destination:
context.locale !== context.defaultLocale
? `/${context.locale}/signin`
: '/signin',
},
}
}
return {
redirect: {
permanent: false,
destination:
context.locale !== context.defaultLocale
? `/${context.locale}/typebots`
: '/typebots',
},
}
}
return { redirect: { permanent: false, destination: '/typebots' } }
}
)

View File

@ -1,18 +1,21 @@
import { NextPageContext } from 'next/types'
import { DashboardPage } from '@/features/dashboard'
import { getLocaleProps } from '@/locales'
import { GetServerSidePropsContext } from 'next'
export default function Page() {
return <DashboardPage />
}
export async function getServerSideProps(context: NextPageContext) {
const redirectPath = context.query.redirectPath?.toString()
return redirectPath
? {
redirect: {
permanent: false,
destination: redirectPath,
},
}
: { props: {} }
}
export const getServerSideProps = getLocaleProps(
async (context: GetServerSidePropsContext) => {
const redirectPath = context.query.redirectPath?.toString()
return redirectPath
? {
redirect: {
permanent: false,
destination: redirectPath,
},
}
: { props: {} }
}
)

17
pnpm-lock.yaml generated
View File

@ -44,6 +44,7 @@ importers:
'@trpc/react-query': 10.12.0
'@trpc/server': 10.12.0
'@typebot.io/js': workspace:*
'@typebot.io/next-international': ^0.3.8
'@typebot.io/react': workspace:*
'@types/canvas-confetti': 1.6.0
'@types/google-spreadsheet': 3.3.1
@ -148,6 +149,7 @@ importers:
'@trpc/react-query': 10.12.0_tij422wpfxfl3iixjhzsgwgs2e
'@trpc/server': 10.12.0
'@typebot.io/js': link:../../packages/js
'@typebot.io/next-international': 0.3.8_next@13.1.6+react@18.2.0
'@typebot.io/react': link:../../packages/react
'@udecode/plate-basic-marks': 19.2.0_opnyntabsv5vdkt3p6hmagoxbq
'@udecode/plate-common': 7.0.2_rjhyybmi5zd2n2kz5mt3q7icvy
@ -6669,6 +6671,17 @@ packages:
- webpack-cli
dev: false
/@typebot.io/next-international/0.3.8_next@13.1.6+react@18.2.0:
resolution: {integrity: sha512-X5Q/r7iNhCBlWGeQho2PESDFjYk5NUW72dUEI/ghG6kE1jactXJRC0yuPRef/eILwlbwF5k+YXSo5ueiJZ4H1Q==}
peerDependencies:
next: ^12.0.0 || ^11.0.0
react: ^18.0.0 || ^17.0.0
dependencies:
international-types: 0.3.8
next: 13.1.6_6m24vuloj5ihw4zc5lbsktc4fu
react: 18.2.0
dev: false
/@types/aos/3.0.4:
resolution: {integrity: sha512-mna6Jd6bdK1NpwarLopGvXOgUoCfj0470IwLxuVOFDElTGI0JTd7xSGQ0AjbAEnHErC/b3fA9t2uB3IXVKmckA==}
dev: true
@ -13026,6 +13039,10 @@ packages:
has: 1.0.3
side-channel: 1.0.4
/international-types/0.3.8:
resolution: {integrity: sha512-DeAFYOSA2wuUiSkFnAqrIM1UNVSAa8aP6hbXQOK4n74VuP/bCZPlZ7SEnCZ6+JBID/ekH1Ir5xTwsKA2Q++mKw==}
dev: false
/interpret/1.4.0:
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
engines: {node: '>= 0.10'}