2
0

feat(editor): Add file upload input

This commit is contained in:
Baptiste Arnaud
2022-06-12 17:34:33 +02:00
parent d4c52d47b3
commit 75365a0d82
48 changed files with 1022 additions and 587 deletions

View File

@@ -2,5 +2,12 @@ ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6 #256-bits secret (can be gene
NEXT_PUBLIC_VIEWER_URL=http://localhost:3001
DATABASE_URL=postgresql://postgres:typebot@localhost:5432/typebot
S3_ACCESS_KEY=minio
S3_SECRET_KEY=minio123
S3_BUCKET=typebot
S3_PORT=9000
S3_ENDPOINT=localhost
S3_SSL=false
# For more configuration options check out:
# https://docs.typebot.io/self-hosting/configuration
# https://docs.typebot.io/self-hosting/configuration

View File

@@ -13,6 +13,7 @@
},
"dependencies": {
"@sentry/nextjs": "^6.19.7",
"aws-sdk": "^2.1152.0",
"bot-engine": "*",
"cors": "^2.8.5",
"cuid": "^2.1.8",
@@ -31,6 +32,7 @@
},
"devDependencies": {
"@playwright/test": "^1.22.0",
"@types/aws-sdk": "^2.7.0",
"@types/cors": "^2.8.12",
"@types/google-spreadsheet": "^3.2.1",
"@types/node": "^17.0.33",

View File

@@ -0,0 +1,30 @@
import { withSentry } from '@sentry/nextjs'
import { NextApiRequest, NextApiResponse } from 'next'
import { badRequest, generatePresignedUrl, methodNotAllowed } from 'utils'
const handler = async (
req: NextApiRequest,
res: NextApiResponse
): Promise<void> => {
res.setHeader('Access-Control-Allow-Origin', '*')
if (req.method === 'GET') {
if (
!process.env.S3_ENDPOINT ||
!process.env.S3_ACCESS_KEY ||
!process.env.S3_SECRET_KEY
)
return badRequest(
res,
'S3 not properly configured. Missing one of those variables: S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY'
)
const filePath = req.query.filePath as string | undefined
const fileType = req.query.fileType as string | undefined
if (!filePath || !fileType) return badRequest(res)
const presignedUrl = generatePresignedUrl({ fileType, filePath })
return res.status(200).send({ presignedUrl })
}
return methodNotAllowed(res)
}
export default withSentry(handler)

View File

@@ -32,7 +32,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
})),
]
}, [])
console.log({ blocks: emptyWebhookBlocks })
return res.send({ blocks: emptyWebhookBlocks })
}
return methodNotAllowed(res)

View File

@@ -0,0 +1,131 @@
{
"id": "cl45ojo7z01383q1av699t0qj",
"createdAt": "2022-06-08T14:22:14.879Z",
"updatedAt": "2022-06-08T16:19:32.893Z",
"icon": null,
"name": "My typebot",
"publishedTypebotId": "cl45ol3j8000f2e6gcifqf21t",
"folderId": null,
"groups": [
{
"id": "cl45ojo7y00013q1aaysi2o6i",
"blocks": [
{
"id": "cl45ojo7y00023q1aavrwd411",
"type": "start",
"label": "Start",
"groupId": "cl45ojo7y00013q1aaysi2o6i",
"outgoingEdgeId": "cl45ojxvc00082e6gw1xqnxpp"
}
],
"title": "Start",
"graphCoordinates": { "x": 0, "y": 0 }
},
{
"id": "cl45ojrrd00062e6g17tuu9t0",
"blocks": [
{
"id": "cl45ojrre00072e6gk91592pj",
"type": "text",
"groupId": "cl45ojrrd00062e6g17tuu9t0",
"content": {
"html": "<div>Hey there, upload please</div>",
"richText": [
{
"type": "p",
"children": [{ "text": "Hey there, upload please" }]
}
],
"plainText": "Hey there, upload please"
}
},
{
"id": "cl45ojzs300092e6gkno525c4",
"type": "file input",
"groupId": "cl45ojrrd00062e6g17tuu9t0",
"options": {
"labels": {
"button": "Upload",
"placeholder": "<strong>\n Click to upload\n </strong> or drag and drop<br>\n (size limit: 10MB)"
},
"variableId": "vcl45ok77i000a2e6g79ye53a2",
"isMultipleAllowed": true
},
"outgoingEdgeId": "cl45okfgz000d2e6g7z3wnqgq"
}
],
"title": "Group #1",
"graphCoordinates": { "x": 416, "y": 98 }
},
{
"id": "cl45ok963000b2e6g2ky0wkvx",
"blocks": [
{
"id": "cl45ok963000c2e6g9snvbhw4",
"type": "text",
"groupId": "cl45ok963000b2e6g2ky0wkvx",
"content": {
"html": "<div>Thank you!</div>",
"richText": [
{ "type": "p", "children": [{ "text": "Thank you!" }] }
],
"plainText": "Thank you!"
}
}
],
"title": "Group #2",
"graphCoordinates": { "x": 863, "y": 249 }
}
],
"variables": [{ "id": "vcl45ok77i000a2e6g79ye53a2", "name": "Files" }],
"edges": [
{
"id": "cl45ojxvc00082e6gw1xqnxpp",
"to": { "groupId": "cl45ojrrd00062e6g17tuu9t0" },
"from": {
"blockId": "cl45ojo7y00023q1aavrwd411",
"groupId": "cl45ojo7y00013q1aaysi2o6i"
}
},
{
"id": "cl45okfgz000d2e6g7z3wnqgq",
"to": { "groupId": "cl45ok963000b2e6g2ky0wkvx" },
"from": {
"blockId": "cl45ojzs300092e6gkno525c4",
"groupId": "cl45ojrrd00062e6g17tuu9t0"
}
}
],
"theme": {
"chat": {
"inputs": {
"color": "#303235",
"backgroundColor": "#FFFFFF",
"placeholderColor": "#9095A0"
},
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
"hostAvatar": {
"url": "https://avatars.githubusercontent.com/u/16015833?v=4",
"isEnabled": true
},
"hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
},
"general": { "font": "Open Sans", "background": { "type": "None" } }
},
"settings": {
"general": {
"isBrandingEnabled": true,
"isInputPrefillEnabled": true,
"isHideQueryParamsEnabled": true,
"isNewResultOnRefreshEnabled": false
},
"metadata": {
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
},
"typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
},
"publicId": "my-typebot-699t0qj",
"customDomain": null,
"workspaceId": "proWorkspace"
}

View File

@@ -8,7 +8,7 @@ import {
Typebot,
Webhook,
} from 'models'
import { PrismaClient, WorkspaceRole } from 'db'
import { Plan, PrismaClient, WorkspaceRole } from 'db'
import { readFileSync } from 'fs'
import { encrypt } from 'utils'
@@ -46,6 +46,7 @@ export const createUser = () =>
create: {
id: proWorkspaceId,
name: 'Pro workspace',
plan: Plan.PRO,
},
},
},

View File

@@ -0,0 +1,39 @@
import test, { expect } from '@playwright/test'
import cuid from 'cuid'
import path from 'path'
import { typebotViewer } from '../services/selectorUtils'
import { importTypebotInDatabase } from '../services/database'
test('should work as expected', async ({ page, context }) => {
const typebotId = cuid()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/fileUpload.json'),
{ id: typebotId, publicId: `${typebotId}-public` }
)
await page.goto(`/${typebotId}-public`)
await typebotViewer(page)
.locator(`input[type="file"]`)
.setInputFiles([
path.join(__dirname, '../fixtures/typebots/api.json'),
path.join(__dirname, '../fixtures/typebots/fileUpload.json'),
path.join(__dirname, '../fixtures/typebots/hugeGroup.json'),
])
await expect(typebotViewer(page).locator(`text="3"`)).toBeVisible()
await typebotViewer(page).locator('text="Upload 3 files"').click()
await expect(
typebotViewer(page).locator(`text="3 files uploaded"`)
).toBeVisible()
await page.goto(`http://localhost:3000/typebots/${typebotId}/results`)
await expect(page.locator('text="api.json"')).toHaveAttribute(
'href',
/.+\/api\.json/
)
await expect(page.locator('text="fileUpload.json"')).toHaveAttribute(
'href',
/.+\/fileUpload\.json/
)
await expect(page.locator('text="api.json"')).toHaveAttribute(
'href',
/.+\/api\.json/
)
})