2
0

📦 Upgrade packages

This commit is contained in:
Baptiste Arnaud
2022-11-02 19:45:46 +01:00
committed by GitHub
parent 48285479cc
commit 2c66c59dfc
64 changed files with 2518 additions and 3158 deletions

View File

@ -1,6 +1,6 @@
import React from 'react'
import { NextChakraLink } from '../nextChakra/NextChakraLink'
import { Text } from '@chakra-ui/react'
import { TextLink } from 'components/shared/TextLink'
type Props = {
type: 'register' | 'signin'
@ -10,16 +10,11 @@ export const AuthSwitcher = ({ type }: Props) => (
{type === 'signin' ? (
<Text>
Don't have an account?{' '}
<NextChakraLink href="/register" color="blue.500" textDecor="underline">
Sign up for free
</NextChakraLink>
<TextLink href="/register">Sign up for free</TextLink>
</Text>
) : (
<Text>
Already have an account?{' '}
<NextChakraLink href="/signin" color="blue.500" textDecor="underline">
Sign in
</NextChakraLink>
Already have an account? <TextLink href="/signin">Sign in</TextLink>
</Text>
)}
</>

View File

@ -19,9 +19,9 @@ import {
import { DividerWithText } from './DividerWithText'
import { SocialLoginButtons } from './SocialLoginButtons'
import { useRouter } from 'next/router'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { BuiltInProviderType } from 'next-auth/providers'
import { useToast } from 'components/shared/hooks/useToast'
import { TextLink } from 'components/shared/TextLink'
type Props = {
defaultEmail?: string
@ -76,14 +76,12 @@ export const SignInForm = ({
return (
<Text>
You need to{' '}
<NextChakraLink
<TextLink
href="https://docs.typebot.io/self-hosting/configuration"
isExternal
color="blue.400"
textDecor="underline"
>
configure at least one auth provider
</NextChakraLink>{' '}
</TextLink>{' '}
(Email, Google, GitHub, Facebook or Azure AD).
</Text>
)

View File

@ -12,7 +12,6 @@ import {
useDisclosure,
} from '@chakra-ui/react'
import { TypebotLogo } from 'assets/logos'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import {
ChevronLeftIcon,
HardDriveIcon,
@ -27,6 +26,7 @@ import { EmojiOrImageIcon } from 'components/shared/EmojiOrImageIcon'
import { WorkspaceSettingsModal } from './WorkspaceSettingsModal'
import { isNotDefined } from 'utils'
import { PlanTag } from 'components/shared/PlanTag'
import Link from 'next/link'
export const DashboardHeader = () => {
const { user } = useUser()
@ -52,13 +52,9 @@ export const DashboardHeader = () => {
maxW="1000px"
flex="1"
>
<NextChakraLink
className="w-24"
href="/typebots"
data-testid="typebot-logo"
>
<Link href="/typebots" data-testid="typebot-logo">
<TypebotLogo w="30px" />
</NextChakraLink>
</Link>
<HStack>
{user && workspace && (
<WorkspaceSettingsModal

View File

@ -1,7 +1,7 @@
import { Button } from '@chakra-ui/react'
import { ChevronLeftIcon } from 'assets/icons'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { useTypebotDnd } from 'contexts/TypebotDndContext'
import Link from 'next/link'
import React, { useMemo } from 'react'
export const BackButton = ({ id }: { id: string | null }) => {
@ -17,7 +17,7 @@ export const BackButton = ({ id }: { id: string | null }) => {
const handleMouseLeave = () => setMouseOverFolderId(undefined)
return (
<Button
as={NextChakraLink}
as={Link}
href={id ? `/typebots/folders/${id}` : '/typebots'}
leftIcon={<ChevronLeftIcon />}
variant={'outline'}

View File

@ -1,7 +1,7 @@
import { HStack, Stack, Text } from '@chakra-ui/react'
import { StripeClimateLogo } from 'assets/logos/StripeClimateLogo'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { ChangePlanForm } from 'components/shared/ChangePlanForm'
import { TextLink } from 'components/shared/TextLink'
import { useWorkspace } from 'contexts/WorkspaceContext'
import { Plan } from 'db'
import React from 'react'
@ -33,13 +33,9 @@ export const BillingContent = () => {
<Text fontSize="xs" color="gray.500">
Typebot is contributing 1% of your subscription to remove CO from
the atmosphere.{' '}
<NextChakraLink
href="https://climate.stripe.com/5VCRAq"
isExternal
textDecor="underline"
>
<TextLink href="https://climate.stripe.com/5VCRAq" isExternal>
More info.
</NextChakraLink>
</TextLink>
</Text>
</HStack>
{workspace.plan !== Plan.CUSTOM &&

View File

@ -14,8 +14,8 @@ import {
Text,
} from '@chakra-ui/react'
import { DownloadIcon, FileIcon } from 'assets/icons'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { Workspace } from 'db'
import Link from 'next/link'
import React from 'react'
import { useInvoicesQuery } from './queries/useInvoicesQuery'
@ -54,12 +54,12 @@ export const InvoicesList = ({ workspace }: Props) => {
<Td>{getFormattedPrice(invoice.amount, invoice.currency)}</Td>
<Td>
<IconButton
as={NextChakraLink}
as={Link}
size="xs"
icon={<DownloadIcon />}
variant="outline"
href={invoice.url}
isExternal
target="_blank"
aria-label={'Download invoice'}
/>
</Td>

View File

@ -1,47 +0,0 @@
import { PropsWithChildren } from 'react'
import NextLink from 'next/link'
import { LinkProps as NextLinkProps } from 'next/dist/client/link'
import {
Link as ChakraLink,
LinkProps as ChakraLinkProps,
} from '@chakra-ui/react'
import React from 'react'
export type NextChakraLinkProps = PropsWithChildren<
NextLinkProps & Omit<ChakraLinkProps, 'href'>
>
export const NextChakraLink = React.forwardRef<
HTMLAnchorElement,
NextChakraLinkProps
>(
(
{
href,
replace,
scroll,
shallow,
prefetch,
children,
locale,
...chakraProps
},
ref
) => {
return (
<NextLink
passHref={true}
href={href}
replace={replace}
scroll={scroll}
shallow={shallow}
prefetch={prefetch}
locale={locale}
>
<ChakraLink ref={ref} {...chakraProps}>
{children}
</ChakraLink>
</NextLink>
)
}
)

View File

@ -248,10 +248,9 @@ const IndeterminateCheckbox = React.forwardRef(
const resolvedRef: any = ref || defaultRef
return (
<Flex justify="center" data-testid="checkbox">
<Flex justify="center" data-testid="checkbox" {...rest}>
<Checkbox
ref={resolvedRef}
{...rest}
isIndeterminate={indeterminate}
isChecked={checked}
/>

View File

@ -1,9 +1,9 @@
import { Stack, HStack, Text } from '@chakra-ui/react'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { useUser } from 'contexts/UserContext'
import { useWorkspace } from 'contexts/WorkspaceContext'
import { Plan } from 'db'
import { useToast } from '../hooks/useToast'
import { TextLink } from '../TextLink'
import { ProPlanContent } from './ProPlanContent'
import { pay } from './queries/updatePlan'
import { useCurrentSubscriptionInfo } from './queries/useCurrentSubscriptionInfo'
@ -89,13 +89,9 @@ export const ChangePlanForm = () => {
</HStack>
<Text color="gray.500">
Need custom limits? Specific features?{' '}
<NextChakraLink
href={'https://typebot.io/enterprise-lead-form'}
isExternal
textDecor="underline"
>
<TextLink href={'https://typebot.io/enterprise-lead-form'} isExternal>
Let's chat!
</NextChakraLink>
</TextLink>
</Text>
</Stack>
)

View File

@ -13,9 +13,9 @@ import {
Flex,
} from '@chakra-ui/react'
import { GoogleLogo } from 'assets/logos'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { Info } from 'components/shared/Info'
import { useWorkspace } from 'contexts/WorkspaceContext'
import Link from 'next/link'
import React from 'react'
import { getGoogleSheetsConsentScreenUrl } from 'services/integrations'
@ -53,7 +53,7 @@ export const GoogleSheetConnectModal = ({
/>
<Flex>
<Button
as={NextChakraLink}
as={Link}
leftIcon={<GoogleLogo />}
data-testid="google"
isLoading={['loading', 'authenticated'].includes(status)}

View File

@ -18,12 +18,11 @@ import { CredentialsType, StripeCredentialsData } from 'models'
import React, { useState } from 'react'
import { useWorkspace } from 'contexts/WorkspaceContext'
import { Input } from 'components/shared/Textbox'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { MoreInfoTooltip } from 'components/shared/MoreInfoTooltip'
import { ExternalLinkIcon } from 'assets/icons'
import { createCredentials } from 'services/credentials'
import { omit } from 'utils'
import { useToast } from 'components/shared/hooks/useToast'
import { TextLink } from 'components/shared/TextLink'
type Props = {
isOpen: boolean
@ -157,13 +156,9 @@ export const StripeConfigModal = ({
<Text>
(You can find your keys{' '}
<NextChakraLink
href="https://dashboard.stripe.com/apikeys"
isExternal
textDecor="underline"
>
here <ExternalLinkIcon />
</NextChakraLink>
<TextLink href="https://dashboard.stripe.com/apikeys" isExternal>
here
</TextLink>
)
</Text>
</Stack>

View File

@ -1,8 +1,8 @@
import { HStack, IconButton, Input } from '@chakra-ui/react'
import { ExternalLinkIcon } from 'assets/icons'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { useToast } from 'components/shared/hooks/useToast'
import { SearchableDropdown } from 'components/shared/SearchableDropdown'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import { useTypebots } from 'services/typebots'
@ -54,7 +54,7 @@ export const TypebotsDropdown = ({
<IconButton
aria-label="Navigate to typebot"
icon={<ExternalLinkIcon />}
as={NextChakraLink}
as={Link}
href={`/typebots/${currentTypebot?.id}/edit?parentId=${query.typebotId}`}
/>
)}

View File

@ -0,0 +1,37 @@
import Link, { LinkProps } from 'next/link'
import React from 'react'
import { chakra, HStack, TextProps } from '@chakra-ui/react'
import { ExternalLinkIcon } from 'assets/icons'
type TextLinkProps = LinkProps & TextProps & { isExternal?: boolean }
export const TextLink = ({
children,
href,
shallow,
replace,
scroll,
prefetch,
isExternal,
...textProps
}: TextLinkProps) => (
<Link
href={href}
shallow={shallow}
replace={replace}
scroll={scroll}
prefetch={prefetch}
target={isExternal ? '_blank' : undefined}
>
<chakra.span textDecor="underline" display="inline-block" {...textProps}>
{isExternal ? (
<HStack spacing={1}>
<chakra.span>{children}</chakra.span>
<ExternalLinkIcon />
</HStack>
) : (
children
)}
</chakra.span>
</Link>
)

View File

@ -8,7 +8,6 @@ import {
Text,
} from '@chakra-ui/react'
import { BuoyIcon, ChevronLeftIcon, RedoIcon, UndoIcon } from 'assets/icons'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { RightPanel, useEditor } from 'contexts/EditorContext'
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
import { useRouter } from 'next/router'
@ -20,6 +19,7 @@ import { CollaborationMenuButton } from './CollaborationMenuButton'
import { EditableTypebotName } from './EditableTypebotName'
import { getBubbleActions } from 'typebot-js'
import { isCloudProdInstance } from 'services/utils'
import Link from 'next/link'
export const headerHeight = 56
@ -71,7 +71,7 @@ export const TypebotHeader = () => {
right={{ base: 280, xl: 0 }}
>
<Button
as={NextChakraLink}
as={Link}
href={`/typebots/${typebot?.id}/edit`}
colorScheme={router.pathname.includes('/edit') ? 'blue' : 'gray'}
variant={router.pathname.includes('/edit') ? 'outline' : 'ghost'}
@ -80,7 +80,7 @@ export const TypebotHeader = () => {
Flow
</Button>
<Button
as={NextChakraLink}
as={Link}
href={`/typebots/${typebot?.id}/theme`}
colorScheme={router.pathname.endsWith('theme') ? 'blue' : 'gray'}
variant={router.pathname.endsWith('theme') ? 'outline' : 'ghost'}
@ -89,7 +89,7 @@ export const TypebotHeader = () => {
Theme
</Button>
<Button
as={NextChakraLink}
as={Link}
href={`/typebots/${typebot?.id}/settings`}
colorScheme={router.pathname.endsWith('settings') ? 'blue' : 'gray'}
variant={router.pathname.endsWith('settings') ? 'outline' : 'ghost'}
@ -98,7 +98,7 @@ export const TypebotHeader = () => {
Settings
</Button>
<Button
as={NextChakraLink}
as={Link}
href={`/typebots/${typebot?.id}/share`}
colorScheme={router.pathname.endsWith('share') ? 'blue' : 'gray'}
variant={router.pathname.endsWith('share') ? 'outline' : 'ghost'}
@ -108,7 +108,7 @@ export const TypebotHeader = () => {
</Button>
{typebot?.publishedTypebotId && (
<Button
as={NextChakraLink}
as={Link}
href={`/typebots/${typebot?.id}/results`}
colorScheme={router.pathname.includes('results') ? 'blue' : 'gray'}
variant={router.pathname.includes('results') ? 'outline' : 'ghost'}
@ -127,7 +127,7 @@ export const TypebotHeader = () => {
>
<HStack alignItems="center" spacing={3}>
<IconButton
as={NextChakraLink}
as={Link}
aria-label="Navigate back"
icon={<ChevronLeftIcon fontSize={25} />}
href={

View File

@ -8,59 +8,59 @@
"start": "next start",
"lint": "next lint",
"test": "pnpm playwright test",
"test:open": "NO_RETRIES=1 pnpm playwright test --debug"
"test:report": "pnpm playwright show-report"
},
"dependencies": {
"@chakra-ui/css-reset": "2.0.8",
"@chakra-ui/react": "2.3.5",
"@codemirror/lang-css": "6.0.0",
"@codemirror/lang-html": "6.1.2",
"@codemirror/lang-javascript": "6.1.0",
"@codemirror/lang-json": "6.0.0",
"@chakra-ui/react": "2.3.6",
"@codemirror/lang-css": "6.0.1",
"@codemirror/lang-html": "6.1.3",
"@codemirror/lang-javascript": "6.1.1",
"@codemirror/lang-json": "6.0.1",
"@codemirror/lint": "6.0.0",
"@codemirror/state": "6.1.2",
"@dnd-kit/core": "6.0.5",
"@dnd-kit/sortable": "7.0.1",
"@dnd-kit/utilities": "3.2.0",
"@emotion/react": "11.10.4",
"@emotion/styled": "11.10.4",
"@emotion/react": "11.10.5",
"@emotion/styled": "11.10.5",
"@giphy/js-fetch-api": "4.4.0",
"@giphy/js-types": "4.2.1",
"@giphy/react-components": "6.2.0",
"@googleapis/drive": "3.0.1",
"@sentry/nextjs": "7.15.0",
"@stripe/stripe-js": "1.41.0",
"@tanstack/react-table": "8.5.15",
"@udecode/plate-basic-marks": "18.2.0",
"@googleapis/drive": "4.0.0",
"@sentry/nextjs": "7.17.3",
"@stripe/stripe-js": "1.42.1",
"@tanstack/react-table": "8.5.22",
"@udecode/plate-basic-marks": "18.7.0",
"@udecode/plate-common": "^7.0.2",
"@udecode/plate-core": "18.2.0",
"@udecode/plate-link": "18.2.0",
"@udecode/plate-serializer-html": "18.2.0",
"@udecode/plate-ui-link": "18.2.0",
"@udecode/plate-ui-toolbar": "18.2.0",
"aws-sdk": "2.1233.0",
"@udecode/plate-core": "18.7.0",
"@udecode/plate-link": "18.7.0",
"@udecode/plate-serializer-html": "18.7.0",
"@udecode/plate-ui-link": "18.7.0",
"@udecode/plate-ui-toolbar": "18.7.0",
"aws-sdk": "2.1245.0",
"bot-engine": "workspace:*",
"browser-image-compression": "2.0.0",
"canvas-confetti": "1.5.1",
"canvas-confetti": "1.6.0",
"codemirror": "6.0.1",
"cuid": "2.1.8",
"deep-object-diff": "1.1.7",
"dequal": "2.0.3",
"emojilib": "3.0.7",
"focus-visible": "5.2.0",
"framer-motion": "7.5.3",
"google-auth-library": "8.5.2",
"framer-motion": "7.6.4",
"google-auth-library": "8.6.0",
"google-spreadsheet": "3.3.0",
"got": "12.5.2",
"htmlparser2": "8.0.1",
"immer": "9.0.15",
"immer": "9.0.16",
"js-video-url-parser": "0.5.1",
"jsonwebtoken": "8.5.1",
"kbar": "0.1.0-beta.36",
"micro": "9.4.1",
"micro-cors": "0.1.1",
"minio": "7.0.32",
"next": "12.3.1",
"next": "13.0.1",
"next-auth": "4.12.3",
"nodemailer": "6.8.0",
"nprogress": "0.2.0",
@ -74,7 +74,7 @@
"slate-history": "0.66.0",
"slate-hyperscript": "0.77.0",
"slate-react": "0.83.2",
"stripe": "10.14.0",
"stripe": "10.15.0",
"styled-components": "5.3.6",
"svg-round-corners": "0.4.1",
"swr": "1.3.0",
@ -85,7 +85,7 @@
"@faire/mjml-react": "2.1.4"
},
"devDependencies": {
"@babel/core": "7.19.3",
"@babel/core": "7.19.6",
"@chakra-ui/styled-system": "2.3.4",
"@playwright/test": "1.27.1",
"@types/canvas-confetti": "1.4.3",
@ -93,24 +93,25 @@
"@types/jsonwebtoken": "8.5.9",
"@types/micro-cors": "0.1.2",
"@types/minio": "7.0.14",
"@types/node": "18.11.0",
"@types/node": "18.11.9",
"@types/nodemailer": "6.4.6",
"@types/nprogress": "0.2.0",
"@types/papaparse": "5.3.5",
"@types/prettier": "2.7.1",
"@types/qs": "6.9.7",
"@types/react": "18.0.21",
"@types/react": "18.0.24",
"@types/tinycolor2": "1.4.3",
"@typescript-eslint/eslint-plugin": "5.40.0",
"@typescript-eslint/parser": "5.40.0",
"@typescript-eslint/eslint-plugin": "5.42.0",
"@typescript-eslint/parser": "5.42.0",
"db": "workspace:*",
"dotenv": "16.0.3",
"eslint": "8.25.0",
"eslint-config-next": "12.3.1",
"eslint": "8.26.0",
"eslint-config-next": "13.0.1",
"eslint-plugin-react": "7.31.10",
"models": "workspace:*",
"next-transpile-modules": "9.1.0",
"next-transpile-modules": "10.0.0",
"typescript": "4.8.4",
"utils": "workspace:*"
"utils": "workspace:*",
"configs": "workspace:*"
}
}

View File

@ -2,7 +2,6 @@ import { Seo } from 'components/Seo'
import { TypebotHeader } from 'components/shared/TypebotHeader'
import React, { useMemo } from 'react'
import { HStack, Button, Tag, Flex, Text } from '@chakra-ui/react'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { ResultsContent } from 'components/results/ResultsContent'
import { useTypebot } from 'contexts/TypebotContext'
import { useWorkspace } from 'contexts/WorkspaceContext'
@ -14,6 +13,7 @@ import { ResultsProvider } from 'contexts/ResultsProvider'
import { UnlockPlanInfo } from 'components/shared/Info'
import { getChatsLimit, getStorageLimit } from 'utils'
import { useUsage } from 'components/dashboard/WorkspaceSettingsModal/BillingContent/UsageContent/useUsage'
import Link from 'next/link'
const ALERT_CHATS_PERCENT_THRESHOLD = 80
const ALERT_STORAGE_PERCENT_THRESHOLD = 80
@ -130,7 +130,7 @@ const ResultsPage = () => {
>
<HStack maxW="1600px" w="full" px="4">
<Button
as={NextChakraLink}
as={Link}
colorScheme={!isAnalytics ? 'blue' : 'gray'}
variant={!isAnalytics ? 'outline' : 'ghost'}
size="sm"
@ -144,7 +144,7 @@ const ResultsPage = () => {
)}
</Button>
<Button
as={NextChakraLink}
as={Link}
colorScheme={isAnalytics ? 'blue' : 'gray'}
variant={isAnalytics ? 'outline' : 'ghost'}
href={`/typebots/${typebot?.id}/results/analytics`}

View File

@ -1,35 +1,22 @@
import { devices, PlaywrightTestConfig } from '@playwright/test'
import { PlaywrightTestConfig } from '@playwright/test'
import path from 'path'
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('dotenv').config({
path: path.join(__dirname, 'playwright/.env'),
})
import { playwrightBaseConfig } from 'configs/playwright'
const config: PlaywrightTestConfig = {
globalSetup: require.resolve(path.join(__dirname, 'playwright/global-setup')),
...playwrightBaseConfig,
testDir: path.join(__dirname, 'playwright/tests'),
retries: process.env.NO_RETRIES ? 0 : 2,
workers: process.env.CI ? 1 : 3,
reporter: 'html',
maxFailures: process.env.CI ? 10 : undefined,
webServer: process.env.CI
? {
...(playwrightBaseConfig.webServer as { command: string }),
port: 3000,
}
: undefined,
use: {
actionTimeout: 0,
baseURL: process.env.PLAYWRIGHT_BUILDER_TEST_BASE_URL,
trace: 'on-first-retry',
...playwrightBaseConfig.use,
baseURL: process.env.NEXTAUTH_URL,
storageState: path.join(__dirname, 'playwright/firstUser.json'),
video: 'retain-on-failure',
locale: 'en-US',
},
outputDir: path.join(__dirname, 'playwright/test-results/'),
projects: [
{
name: 'Chrome',
use: {
...devices['Desktop Chrome'],
viewport: { width: 1400, height: 1000 },
},
},
],
}
export default config

View File

@ -119,12 +119,12 @@
"graphCoordinates": { "x": 639, "y": 142 }
},
{
"id": "q5dAhqSTCaNdiGSJm9B9Rw",
"id": "group1",
"blocks": [
{
"id": "sgtE2Sy7cKykac9B223Kq9R",
"type": "text",
"groupId": "q5dAhqSTCaNdiGSJm9B9Rw",
"groupId": "group1",
"content": {
"html": "<div>What&#x27;s your name?</div>",
"richText": [
@ -134,9 +134,9 @@
}
},
{
"id": "sqEsMo747LTDnY9FjQcEwUv",
"id": "block1",
"type": "text input",
"groupId": "q5dAhqSTCaNdiGSJm9B9Rw",
"groupId": "group1",
"options": {
"isLong": false,
"labels": {

View File

@ -1,14 +0,0 @@
import { FullConfig } from '@playwright/test'
import { setupDatabase, teardownDatabase } from 'utils/playwright/databaseSetup'
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('dotenv').config({ path: '.env' })
async function globalSetup(config: FullConfig) {
const { baseURL } = config.projects[0].use
if (!baseURL) throw new Error('baseURL is missing')
await teardownDatabase()
await setupDatabase()
}
export default globalSetup

View File

@ -10,7 +10,7 @@ import { proWorkspaceId } from 'utils/playwright/databaseSetup'
const prisma = new PrismaClient()
const stripe = new Stripe(process.env.STRIPE_TEST_SECRET_KEY ?? '', {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY ?? '', {
apiVersion: '2022-08-01',
})

View File

@ -21,7 +21,7 @@ test('should display user info properly', async ({ page }) => {
await expect(page.locator('img >> nth=1')).toHaveAttribute(
'src',
new RegExp(
`http://localhost:9000/typebot/public/users/${userId}/avatar`,
`${process.env.S3_ENDPOINT}/${process.env.S3_BUCKET}/public/users/${userId}/avatar`,
'gm'
)
)

View File

@ -1,7 +1,10 @@
import test, { expect } from '@playwright/test'
import cuid from 'cuid'
import path from 'path'
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import {
importTypebotInDatabase,
injectFakeResults,
} from 'utils/playwright/databaseActions'
import { starterWorkspaceId } from 'utils/playwright/databaseSetup'
test('analytics are not available for non-pro workspaces', async ({ page }) => {
@ -13,6 +16,7 @@ test('analytics are not available for non-pro workspaces', async ({ page }) => {
workspaceId: starterWorkspaceId,
}
)
await injectFakeResults({ typebotId, count: 10 })
await page.goto(`/typebots/${typebotId}/results/analytics`)
const firstDropoffBox = page.locator('text="%" >> nth=0')
await firstDropoffBox.hover()

View File

@ -227,7 +227,7 @@ test('should display invoices', async ({ page }) => {
await page.click('text=Settings & Members')
await page.click('text=Billing & Usage')
await expect(page.locator('text="Invoices"')).toBeVisible()
await expect(page.locator('tr')).toHaveCount(2)
await expect(page.locator('tr')).toHaveCount(3)
await expect(page.locator('text="€39.00"')).toBeVisible()
})

View File

@ -33,7 +33,7 @@ test.describe.parallel('Image bubble block', () => {
await expect(page.locator('img')).toHaveAttribute(
'src',
new RegExp(
`http://localhost:9000/typebot/public/typebots/${typebotId}/avatar.jpg`,
`${process.env.S3_ENDPOINT}/${process.env.S3_BUCKET}/public/typebots/${typebotId}/avatar.jpg`,
'gm'
)
)

View File

@ -26,19 +26,19 @@ test.describe('Payment input block', () => {
await page.fill('[placeholder="Typebot"]', 'My Stripe Account')
await page.fill(
'[placeholder="sk_test_..."]',
process.env.STRIPE_TEST_SECRET_KEY ?? ''
process.env.STRIPE_SECRET_KEY ?? ''
)
await page.fill(
'[placeholder="sk_live_..."]',
process.env.STRIPE_TEST_SECRET_KEY ?? ''
process.env.STRIPE_SECRET_KEY ?? ''
)
await page.fill(
'[placeholder="pk_test_..."]',
process.env.STRIPE_TEST_PUBLIC_KEY ?? ''
process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY ?? ''
)
await page.fill(
'[placeholder="pk_live_..."]',
process.env.STRIPE_TEST_PUBLIC_KEY ?? ''
process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY ?? ''
)
await expect(page.locator('button >> text="Connect"')).toBeEnabled()
await page.click('button >> text="Connect"')
@ -66,6 +66,9 @@ test.describe('Payment input block', () => {
await stripePaymentForm(page)
.locator(`[placeholder="1234 1234 1234 1234"]`)
.fill('4242424242424242')
const zipInput = stripePaymentForm(page).getByPlaceholder('90210')
const isZipInputVisible = await zipInput.isVisible()
if (isZipInputVisible) await zipInput.fill('12345')
await typebotViewer(page).locator(`text="Pay 30€"`).click()
await expect(typebotViewer(page).locator(`text="Success"`)).toBeVisible()
})

View File

@ -11,10 +11,9 @@ test.describe('Send email block', () => {
if (
!process.env.SMTP_USERNAME ||
!process.env.SMTP_PORT ||
!process.env.SMTP_SECURE ||
!process.env.SMTP_HOST ||
!process.env.SMTP_PASSWORD ||
!process.env.SMTP_FROM
!process.env.NEXT_PUBLIC_SMTP_FROM
)
throw new Error('SMTP_ env vars are missing')
await importTypebotInDatabase(
@ -29,7 +28,7 @@ test.describe('Send email block', () => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Configure...')
await page.click(`text=${process.env.SMTP_FROM}`)
await page.click(`text=notifications@typebot.io`)
await page.click('text=Connect new')
const createButton = page.locator('button >> text=Create')
await expect(createButton).toBeDisabled()
@ -44,7 +43,6 @@ test.describe('Send email block', () => {
process.env.SMTP_USERNAME
)
await page.fill('[type="password"]', process.env.SMTP_PASSWORD)
if (process.env.SMTP_SECURE === 'true') await page.click('text=Use TLS?')
await page.fill('input[role="spinbutton"]', process.env.SMTP_PORT)
await expect(createButton).toBeEnabled()
await createButton.click()

View File

@ -24,7 +24,7 @@ test.describe('Webhook block', () => {
await page.click('text=Configure...')
await page.fill(
'input[placeholder="Paste webhook URL..."]',
`${process.env.PLAYWRIGHT_BUILDER_TEST_BASE_URL}/api/mock/webhook-easy-config`
`${process.env.NEXTAUTH_URL}/api/mock/webhook-easy-config`
)
await page.click('text=Test the request')
await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
@ -46,7 +46,7 @@ test.describe('Webhook block', () => {
await page.click('text=Configure...')
await page.fill(
'input[placeholder="Paste webhook URL..."]',
`${process.env.PLAYWRIGHT_BUILDER_TEST_BASE_URL}/api/mock/webhook`
`${process.env.NEXTAUTH_URL}/api/mock/webhook`
)
await page.click('text=Advanced configuration')
await page.click('text=GET')

View File

@ -1,184 +1,174 @@
import test, { expect, Page } from '@playwright/test'
import cuid from 'cuid'
import { readFileSync } from 'fs'
import { defaultTextInputOptions, InputBlockType } from 'models'
import { parse } from 'papaparse'
import path from 'path'
import {
importTypebotInDatabase,
injectFakeResults,
createTypebots,
} from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
const typebotId = cuid()
test('Submission table header should be parsed correctly', async ({ page }) => {
const typebotId = cuid()
test.beforeEach(async () => {
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/results/submissionHeader.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/results`)
await expect(page.locator('text=Submitted at')).toBeVisible()
await expect(page.locator('text=Welcome')).toBeVisible()
await expect(page.locator('text=Email')).toBeVisible()
await expect(page.locator('text=Name')).toBeVisible()
await expect(page.locator('text=Services')).toBeVisible()
await expect(page.locator('text=Additional information')).toBeVisible()
await expect(page.locator('text=utm_source')).toBeVisible()
await expect(page.locator('text=utm_userid')).toBeVisible()
})
test('results should be deletable', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultGroupWithBlock({
type: InputBlockType.TEXT,
options: defaultTextInputOptions,
}),
},
])
await injectFakeResults({ typebotId, count: 200, isChronological: true })
await page.goto(`/typebots/${typebotId}/results`)
await expect(page.locator('text=content199')).toBeVisible()
await page.click('[data-testid="checkbox"] >> nth=1')
await page.click('[data-testid="checkbox"] >> nth=2')
await page.click('text="Delete"')
await deleteButtonInConfirmDialog(page).click()
await expect(page.locator('text=content199')).toBeHidden()
await expect(page.locator('text=content198')).toBeHidden()
await page.waitForTimeout(1000)
await page.click('[data-testid="checkbox"] >> nth=0')
await page.click('text="Delete"')
await deleteButtonInConfirmDialog(page).click()
await page.waitForTimeout(1000)
expect(await page.locator('tr').count()).toBe(1)
await expect(page.locator('text="Delete"')).toBeHidden()
})
test('submissions table should have infinite scroll', async ({ page }) => {
const scrollToBottom = () =>
page.evaluate(() => {
const tableWrapper = document.querySelector('.table-wrapper')
if (!tableWrapper) return
tableWrapper.scrollTo(0, tableWrapper.scrollHeight)
})
await injectFakeResults({ typebotId, count: 200, isChronological: true })
await page.goto(`/typebots/${typebotId}/results`)
await expect(page.locator('text=content199')).toBeVisible()
await expect(page.locator('text=content149')).toBeHidden()
await scrollToBottom()
await expect(page.locator('text=content149')).toBeVisible()
await expect(page.locator('text=content99')).toBeHidden()
await scrollToBottom()
await expect(page.locator('text=content99')).toBeVisible()
await expect(page.locator('text=content49')).toBeHidden()
await scrollToBottom()
await expect(page.locator('text=content49')).toBeVisible()
await expect(page.locator('text=content0')).toBeVisible()
})
test('should correctly export selection in CSV', async ({ page }) => {
await page.goto(`/typebots/${typebotId}/results`)
await expect(page.locator('text=content199')).toBeVisible()
await page.click('[data-testid="checkbox"] >> nth=1')
await page.click('[data-testid="checkbox"] >> nth=2')
const [download] = await Promise.all([
page.waitForEvent('download'),
page.locator('text="Export"').click(),
])
const path = await download.path()
expect(path).toBeDefined()
const file = readFileSync(path as string).toString()
const { data } = parse(file)
validateExportSelection(data)
await page.click('[data-testid="checkbox"] >> nth=0')
const [downloadAll] = await Promise.all([
page.waitForEvent('download'),
page.locator('text="Export"').click(),
])
const pathAll = await downloadAll.path()
expect(pathAll).toBeDefined()
const fileAll = readFileSync(pathAll as string).toString()
const { data: dataAll } = parse(fileAll)
validateExportAll(dataAll)
})
test('Can resize, hide and reorder columns', async ({ page }) => {
const typebotId = cuid()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/results/submissionHeader.json'),
{
id: typebotId,
}
)
test('Results', async ({ page }) => {
await page.goto(`/typebots/${typebotId}/results`)
// Resize
expect((await page.locator('th >> nth=4').boundingBox())?.width).toBe(200)
await page.waitForTimeout(500)
await page.dragAndDrop(
'[data-testid="resize-handle"] >> nth=3',
'[data-testid="resize-handle"] >> nth=3',
{ targetPosition: { x: 150, y: 0 }, force: true }
)
await page.waitForTimeout(500)
expect((await page.locator('th >> nth=4').boundingBox())?.width).toBe(345)
await test.step('Check header format', async () => {
await expect(page.locator('text=Submitted at')).toBeVisible()
await expect(page.locator('text=Welcome')).toBeVisible()
await expect(page.locator('text=Email')).toBeVisible()
await expect(page.locator('text=Name')).toBeVisible()
await expect(page.locator('text=Services')).toBeVisible()
await expect(page.locator('text=Additional information')).toBeVisible()
await expect(page.locator('text=utm_source')).toBeVisible()
await expect(page.locator('text=utm_userid')).toBeVisible()
})
// Hide
await expect(
page.locator('[data-testid="Submitted at header"]')
).toBeVisible()
await expect(page.locator('[data-testid="Email header"]')).toBeVisible()
await page.click('button >> text="Columns"')
await page.click('[aria-label="Hide column"] >> nth=0')
await page.click('[aria-label="Hide column"] >> nth=1')
await expect(page.locator('[data-testid="Submitted at header"]')).toBeHidden()
await expect(page.locator('[data-testid="Email header"]')).toBeHidden()
await test.step('Resize columns', async () => {
expect((await page.locator('th >> nth=4').boundingBox())?.width).toBe(200)
await page.waitForTimeout(500)
await page.dragAndDrop(
'[data-testid="resize-handle"] >> nth=3',
'[data-testid="resize-handle"] >> nth=3',
{ targetPosition: { x: 150, y: 0 }, force: true }
)
await page.waitForTimeout(500)
expect((await page.locator('th >> nth=4').boundingBox())?.width).toBe(345)
})
// Reorder
await expect(page.locator('th >> nth=1')).toHaveText('Welcome')
await expect(page.locator('th >> nth=2')).toHaveText('Name')
await page.dragAndDrop(
'[aria-label="Drag"] >> nth=0',
'[aria-label="Drag"] >> nth=0',
{ targetPosition: { x: 0, y: 80 }, force: true }
)
await expect(page.locator('th >> nth=1')).toHaveText('Name')
await expect(page.locator('th >> nth=2')).toHaveText('Welcome')
await test.step('Hide columns', async () => {
await expect(
page.locator('[data-testid="Submitted at header"]')
).toBeVisible()
await expect(page.locator('[data-testid="Email header"]')).toBeVisible()
await page.click('button >> text="Columns"')
await page.click('[aria-label="Hide column"] >> nth=0')
await page.click('[aria-label="Hide column"] >> nth=1')
await expect(
page.locator('[data-testid="Submitted at header"]')
).toBeHidden()
await expect(page.locator('[data-testid="Email header"]')).toBeHidden()
})
// Preferences should be persisted
const saveAndReload = async (page: Page) => {
await page.click('text="Theme"')
await page.waitForTimeout(2000)
await page.goto(`/typebots/${typebotId}/results`)
}
await saveAndReload(page)
expect((await page.locator('th >> nth=1').boundingBox())?.width).toBe(345)
await expect(page.locator('[data-testid="Submitted at header"]')).toBeHidden()
await expect(page.locator('[data-testid="Email header"]')).toBeHidden()
await expect(page.locator('th >> nth=1')).toHaveText('Name')
await expect(page.locator('th >> nth=2')).toHaveText('Welcome')
await test.step('Reorder columns', async () => {
await expect(page.locator('th >> nth=1')).toHaveText('Welcome')
await expect(page.locator('th >> nth=2')).toHaveText('Name')
await page.dragAndDrop(
'[aria-label="Drag"] >> nth=0',
'[aria-label="Drag"] >> nth=0',
{ targetPosition: { x: 0, y: 80 }, force: true }
)
await expect(page.locator('th >> nth=1')).toHaveText('Name')
await expect(page.locator('th >> nth=2')).toHaveText('Welcome')
})
await test.step('Preferences should be persisted', async () => {
await saveAndReload(page)
expect((await page.locator('th >> nth=1').boundingBox())?.width).toBe(345)
await expect(
page.locator('[data-testid="Submitted at header"]')
).toBeHidden()
await expect(page.locator('[data-testid="Email header"]')).toBeHidden()
await expect(page.locator('th >> nth=1')).toHaveText('Name')
await expect(page.locator('th >> nth=2')).toHaveText('Welcome')
})
await test.step('Infinite scroll', async () => {
await expect(page.locator('text=content199')).toBeVisible()
await expect(page.locator('text=content149')).toBeHidden()
await scrollToBottom(page)
await expect(page.locator('text=content149')).toBeVisible()
await expect(page.locator('text=content99')).toBeHidden()
await scrollToBottom(page)
await expect(page.locator('text=content99')).toBeVisible()
await expect(page.locator('text=content49')).toBeHidden()
await scrollToBottom(page)
await expect(page.locator('text=content49')).toBeVisible()
await expect(page.locator('text=content0')).toBeVisible()
})
await test.step('Export', async () => {
// For some reason, we need to double click on checkboxes to check them
await getNthCheckbox(page, 1).dblclick()
await getNthCheckbox(page, 2).dblclick()
const [download] = await Promise.all([
page.waitForEvent('download'),
page.getByRole('button', { name: 'Export 2' }).click(),
])
const path = await download.path()
expect(path).toBeDefined()
const file = readFileSync(path as string).toString()
const { data } = parse(file)
validateExportSelection(data)
await getNthCheckbox(page, 0).click()
const [downloadAll] = await Promise.all([
page.waitForEvent('download'),
page.getByRole('button', { name: 'Export 200' }).click(),
])
const pathAll = await downloadAll.path()
expect(pathAll).toBeDefined()
const fileAll = readFileSync(pathAll as string).toString()
const { data: dataAll } = parse(fileAll)
validateExportAll(dataAll)
await getNthCheckbox(page, 0).click()
})
await test.step('Delete', async () => {
await getNthCheckbox(page, 1).click()
await getNthCheckbox(page, 2).click()
await page.click('text="Delete"')
await deleteButtonInConfirmDialog(page).click()
await expect(page.locator('text=content199')).toBeHidden()
await expect(page.locator('text=content198')).toBeHidden()
await page.waitForTimeout(1000)
await page.click('[data-testid="checkbox"] >> nth=0')
await page.click('text="Delete"')
await deleteButtonInConfirmDialog(page).click()
await page.waitForTimeout(1000)
expect(await page.locator('tr').count()).toBe(1)
await expect(page.locator('text="Delete"')).toBeHidden()
})
})
const validateExportSelection = (data: unknown[]) => {
expect(data).toHaveLength(3)
expect((data[1] as unknown[])[1]).toBe('content199')
expect((data[2] as unknown[])[1]).toBe('content198')
expect((data[1] as unknown[])[3]).toBe('content199')
expect((data[2] as unknown[])[3]).toBe('content198')
}
const validateExportAll = (data: unknown[]) => {
expect(data).toHaveLength(201)
expect((data[1] as unknown[])[1]).toBe('content199')
expect((data[200] as unknown[])[1]).toBe('content0')
expect((data[1] as unknown[])[3]).toBe('content199')
expect((data[200] as unknown[])[3]).toBe('content0')
}
const scrollToBottom = (page: Page) =>
page.evaluate(() => {
const tableWrapper = document.querySelector('.table-wrapper')
if (!tableWrapper) return
tableWrapper.scrollTo(0, tableWrapper.scrollHeight)
})
const saveAndReload = async (page: Page) => {
await page.click('text="Theme"')
await page.waitForTimeout(2000)
await page.goto(`/typebots/${typebotId}/results`)
}
const getNthCheckbox = (page: Page, n: number) =>
page.getByTestId('checkbox').nth(n)

View File

@ -6,17 +6,19 @@ import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import { freeWorkspaceId } from 'utils/playwright/databaseSetup'
import { typebotViewer } from 'utils/playwright/testHelpers'
const typebotId = cuid()
test.describe.parallel('Settings page', () => {
test.beforeAll(async () => {
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/settings.json'),
{
id: typebotId,
}
)
})
test.describe('General', () => {
test('should reflect change in real-time', async ({ page }) => {
const typebotId = cuid()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/settings.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/settings`)
await expect(
typebotViewer(page).locator('a:has-text("Made with Typebot")')
@ -134,7 +136,6 @@ test.describe.parallel('Settings page', () => {
await expect(
typebotViewer(page).locator('text="What\'s your name?"')
).toBeVisible()
await page.click('button:has-text("General")')
await expect(
page.locator('[data-testid="starter-lock-tag"]')
).toBeVisible()

View File

@ -91,7 +91,7 @@ test('can manage members', async ({ page }) => {
await page.goto('/typebots')
await page.click('text=Settings & Members')
await page.click('text="Members"')
await expect(page.locator('text="user@email.com"')).toBeVisible()
await expect(page.locator('text="user@email.com"').nth(1)).toBeVisible()
await expect(page.locator('button >> text="Invite"')).toBeEnabled()
await page.fill(
'input[placeholder="colleague@company.com"]',