diff --git a/apps/builder/public/templates/basic-chat-gpt.json b/apps/builder/public/templates/basic-chat-gpt.json
index 5a2653e54..c62b1bbb8 100644
--- a/apps/builder/public/templates/basic-chat-gpt.json
+++ b/apps/builder/public/templates/basic-chat-gpt.json
@@ -1,6 +1,6 @@
{
"id": "clf6ov7hg00001ao6q02sb8re",
- "version": "3",
+ "version": "4",
"createdAt": "2023-03-13T10:35:44.933Z",
"updatedAt": "2023-03-13T14:53:00.817Z",
"icon": "đ¤",
diff --git a/apps/builder/public/templates/chat-gpt-personas.json b/apps/builder/public/templates/chat-gpt-personas.json
index 1ae7dff4b..bff9befaf 100644
--- a/apps/builder/public/templates/chat-gpt-personas.json
+++ b/apps/builder/public/templates/chat-gpt-personas.json
@@ -1,6 +1,6 @@
{
"id": "qcueq3ttys1ddagic7jsimp4",
- "version": "3",
+ "version": "4",
"createdAt": "2023-03-30T15:45:12.464Z",
"updatedAt": "2023-03-30T15:45:12.464Z",
"icon": "đ",
diff --git a/apps/builder/public/templates/customer-support.json b/apps/builder/public/templates/customer-support.json
index 2609100c4..85229781b 100644
--- a/apps/builder/public/templates/customer-support.json
+++ b/apps/builder/public/templates/customer-support.json
@@ -1,5 +1,6 @@
{
"id": "cl16la7p900990b1a72qjqbb3",
+ "version": "4",
"createdAt": "2022-03-25T15:39:33.885Z",
"updatedAt": "2022-03-25T15:42:12.544Z",
"name": "Customer Support",
diff --git a/apps/builder/public/templates/digital-product-payment.json b/apps/builder/public/templates/digital-product-payment.json
index 410c9d8ed..96e5d0258 100644
--- a/apps/builder/public/templates/digital-product-payment.json
+++ b/apps/builder/public/templates/digital-product-payment.json
@@ -1,5 +1,6 @@
{
"id": "cl3u43chu40824z1acel3iw1c",
+ "version": "4",
"createdAt": "2022-05-31T12:04:12.930Z",
"updatedAt": "2022-05-31T12:31:12.867Z",
"icon": "đŧī¸",
diff --git a/apps/builder/public/templates/dog-insurance-offer.json b/apps/builder/public/templates/dog-insurance-offer.json
index 8e9a3aff8..46d547681 100644
--- a/apps/builder/public/templates/dog-insurance-offer.json
+++ b/apps/builder/public/templates/dog-insurance-offer.json
@@ -1,6 +1,6 @@
{
"id": "clh9fzu4b00031aotzr1ik4ba",
- "version": "3",
+ "version": "4",
"createdAt": "2023-05-04T18:10:07.548Z",
"updatedAt": "2023-05-04T19:13:45.356Z",
"icon": "đļ",
diff --git a/apps/builder/public/templates/faq.json b/apps/builder/public/templates/faq.json
index a4af3daba..219d4fca2 100644
--- a/apps/builder/public/templates/faq.json
+++ b/apps/builder/public/templates/faq.json
@@ -1,5 +1,6 @@
{
"id": "cl96ns7zc000dky099ku4bmav",
+ "version": "4",
"createdAt": "2022-10-13T06:07:11.976Z",
"updatedAt": "2022-10-13T06:27:31.951Z",
"icon": "đŦ",
diff --git a/apps/builder/public/templates/lead-gen-ai.json b/apps/builder/public/templates/lead-gen-ai.json
index 6d2f9c7c7..eab3ed48a 100644
--- a/apps/builder/public/templates/lead-gen-ai.json
+++ b/apps/builder/public/templates/lead-gen-ai.json
@@ -1,6 +1,6 @@
{
"id": "pjkxzxkujkbk2oml3d35w6zz",
- "version": "3",
+ "version": "4",
"createdAt": "2023-04-20T12:29:18.633Z",
"updatedAt": "2023-04-20T12:50:12.385Z",
"icon": "đĻž",
diff --git a/apps/builder/public/templates/lead-gen.json b/apps/builder/public/templates/lead-gen.json
index c7c42179e..a4d714400 100644
--- a/apps/builder/public/templates/lead-gen.json
+++ b/apps/builder/public/templates/lead-gen.json
@@ -1,5 +1,6 @@
{
"id": "qgMiLSr4W1ftXocFncin1G",
+ "version": "4",
"createdAt": "2022-02-05T06:21:16.522Z",
"updatedAt": "2022-02-05T06:21:16.522Z",
"name": "Lead Generation",
diff --git a/apps/builder/public/templates/lead-magnet.json b/apps/builder/public/templates/lead-magnet.json
index 018700bde..f842ba58a 100644
--- a/apps/builder/public/templates/lead-magnet.json
+++ b/apps/builder/public/templates/lead-magnet.json
@@ -1,6 +1,6 @@
{
"id": "clg69gafk00011ar14aryt8ej",
- "version": "3",
+ "version": "4",
"createdAt": "2023-04-07T08:03:57.008Z",
"updatedAt": "2023-04-07T08:22:10.285Z",
"icon": "đ§˛",
diff --git a/apps/builder/public/templates/lead-scoring.json b/apps/builder/public/templates/lead-scoring.json
index 5473808e2..da8e24235 100644
--- a/apps/builder/public/templates/lead-scoring.json
+++ b/apps/builder/public/templates/lead-scoring.json
@@ -1,5 +1,6 @@
{
"id": "cl1seoz582103xk1at12y4ucb",
+ "version": "4",
"createdAt": "2022-04-09T22:06:01.196Z",
"updatedAt": "2022-04-09T22:35:22.449Z",
"icon": "đ",
diff --git a/apps/builder/public/templates/movie-recommendation.json b/apps/builder/public/templates/movie-recommendation.json
index 0a6b86537..e40b70b4e 100644
--- a/apps/builder/public/templates/movie-recommendation.json
+++ b/apps/builder/public/templates/movie-recommendation.json
@@ -1,41 +1,41 @@
{
- "id": "clf17krhw00011am023muokc1",
- "version": "3",
- "createdAt": "2023-03-09T14:32:53.301Z",
- "updatedAt": "2023-03-09T16:26:36.916Z",
+ "id": "d8z3y5ats5r0lyptw938re79",
+ "version": "4",
+ "createdAt": "2023-08-06T05:56:35.727Z",
+ "updatedAt": "2023-08-06T06:39:16.019Z",
"icon": "đŋ",
"name": "Movie recommendation",
"folderId": null,
"groups": [
{
- "id": "kto4a8zwoqvagp14sjol0mqq",
+ "id": "u6lpjibfjhyoqij5wjf9kvnl",
"title": "Start",
"blocks": [
{
- "id": "dn2vorjxq275ulqwd97sgfjp",
+ "id": "rha69fygov33vym1hf6z837p",
"type": "start",
"label": "Start",
- "groupId": "kto4a8zwoqvagp14sjol0mqq",
- "outgoingEdgeId": "ozfvttdxg70dy931m762ssc0"
+ "groupId": "u6lpjibfjhyoqij5wjf9kvnl",
+ "outgoingEdgeId": "wfec8f4e1jtden2wqna6nrso"
}
],
"graphCoordinates": { "x": 0, "y": 0 }
},
{
- "id": "vpsi7kfd4hiu38fd0txt3ndn",
+ "id": "mjnkukpkpvf4ha2g4n5m804v",
"title": "Menu",
"blocks": [
{
- "id": "tmm2xkt5wqt5zmxzkyg2kx1q",
+ "id": "kjlf184vxf0uorniwje28iqb",
"type": "Set variable",
- "groupId": "vpsi7kfd4hiu38fd0txt3ndn",
+ "groupId": "mjnkukpkpvf4ha2g4n5m804v",
"options": {
"variableId": "vh5bxx07kl3016wr1undh2yb3",
"expressionToEvaluate": "2f584d1ffe2b7fb082dd4e05038e9bd7"
}
},
{
- "id": "pd22jxxptmw66xhwddj8toos",
+ "id": "f5rr5wi9zldun13tw79u9z2n",
"type": "text",
"content": {
"richText": [
@@ -47,18 +47,18 @@
}
]
},
- "groupId": "vpsi7kfd4hiu38fd0txt3ndn"
+ "groupId": "mjnkukpkpvf4ha2g4n5m804v"
},
{
- "id": "vwfnjnfnqnlvgraai2k4kl9e",
+ "id": "c7swi84rmdvrul0wz5kxtplm",
"type": "image",
"content": {
"url": "https://media3.giphy.com/media/BElb9DVpHezcZufOhl/giphy-downsized.gif?cid=fe3852a3uwhsp1sc3j6avr625v5e94h1v8o3wfb1ii3xwswk&rid=giphy-downsized.gif&ct=g"
},
- "groupId": "vpsi7kfd4hiu38fd0txt3ndn"
+ "groupId": "mjnkukpkpvf4ha2g4n5m804v"
},
{
- "id": "gr8qlv926y817febq85zl0y0",
+ "id": "nlihfc4ptxnxoktqblh6mcql",
"type": "text",
"content": {
"richText": [
@@ -68,28 +68,28 @@
}
]
},
- "groupId": "vpsi7kfd4hiu38fd0txt3ndn"
+ "groupId": "mjnkukpkpvf4ha2g4n5m804v"
},
{
- "id": "fm1894ok09bdyjpqhqth1p7q",
+ "id": "vr73urm54d9mq2oqg7ey1xh8",
"type": "image",
"content": {
"url": "https://www.themoviedb.org/assets/2/v4/logos/v2/blue_square_1-5bdc75aaebeb75dc7ae79426ddd9be3b2be1e342510f8202baf6bffa71d7f5c4.svg"
},
- "groupId": "vpsi7kfd4hiu38fd0txt3ndn"
+ "groupId": "mjnkukpkpvf4ha2g4n5m804v"
},
{
- "id": "qllnzuo1zxr5ef8bq9kjdjel",
+ "id": "g5yyuh9g75x7xa7fgqhhi1zz",
"type": "text",
"content": {
"richText": [
{ "type": "p", "children": [{ "text": "How can I we help?" }] }
]
},
- "groupId": "vpsi7kfd4hiu38fd0txt3ndn"
+ "groupId": "mjnkukpkpvf4ha2g4n5m804v"
},
{
- "id": "sodsq9mcigwvogmwx0t4jvil",
+ "id": "tzf45bvd8iquoxz7qgta8v94",
"type": "choice input",
"items": [
{
@@ -97,39 +97,52 @@
"type": 0,
"blockId": "sodsq9mcigwvogmwx0t4jvil",
"content": "Select a genre đ
",
- "outgoingEdgeId": "o8n3df42rx0og3vlkzhskp7r"
+ "outgoingEdgeId": "t8qyjpigrz7cdl8gxl1wxlwj"
},
{
"id": "i8ls2f8inq2ovuijj6l7rbcq",
"type": 0,
"blockId": "sodsq9mcigwvogmwx0t4jvil",
"content": "See what's trending đ",
- "outgoingEdgeId": "cfwctrcqoucw6djnn0j2zsv0"
+ "outgoingEdgeId": "tjn2ljosqyd4aj9dk8mnifsu"
}
],
- "groupId": "vpsi7kfd4hiu38fd0txt3ndn",
+ "groupId": "mjnkukpkpvf4ha2g4n5m804v",
"options": { "buttonLabel": "Send", "isMultipleChoice": false }
}
],
- "graphCoordinates": { "x": 255.24609375, "y": 173.00390625 }
+ "graphCoordinates": { "x": 255.25, "y": 172.89 }
},
{
- "id": "xjwiuplgl79ezx460xfuplgr",
+ "id": "kq1g5z6pz4buot7sawqdrr3s",
"title": "Genre",
"blocks": [
{
- "id": "vqrsszjis5qtbalo8d0fwb1j",
+ "id": "ecwz96cghzp4ji3lyx7taxt1",
"type": "text",
"content": {
"richText": [{ "type": "p", "children": [{ "text": "Sure!" }] }]
},
- "groupId": "xjwiuplgl79ezx460xfuplgr"
+ "groupId": "kq1g5z6pz4buot7sawqdrr3s"
},
{
- "id": "lmoqmlovcsg2l0uzkm9sohor",
+ "id": "gd4lt2pcljer6zaf7v9hkr1k",
"type": "Webhook",
- "groupId": "xjwiuplgl79ezx460xfuplgr",
+ "groupId": "kq1g5z6pz4buot7sawqdrr3s",
"options": {
+ "webhook": {
+ "id": "t4pht3ndfc8tu9geovi7czqm",
+ "url": "https://api.themoviedb.org/3/genre/movie/list",
+ "method": "GET",
+ "headers": [],
+ "queryParams": [
+ {
+ "id": "gq6m7x2k20qzrj752qi2zpmu",
+ "key": "api_key",
+ "value": "{{API Key}}"
+ }
+ ]
+ },
"isCustomBody": false,
"isAdvancedConfig": true,
"variablesForTest": [
@@ -151,11 +164,10 @@
"variableId": "vwc00rydyp035vtb0nlaqyzwr"
}
]
- },
- "webhookId": "kbd4kqs5lk58nyeggctha9hc"
+ }
},
{
- "id": "v2516de17eigf6mbccy2t6dr",
+ "id": "qeyvu7uq5tkvo7uo8iaj87z5",
"type": "text",
"content": {
"richText": [
@@ -165,10 +177,10 @@
}
]
},
- "groupId": "xjwiuplgl79ezx460xfuplgr"
+ "groupId": "kq1g5z6pz4buot7sawqdrr3s"
},
{
- "id": "eqm19tzeh7kullwld8auoqy4",
+ "id": "nwuk2clo78hmnh4d0g31u9xg",
"type": "choice input",
"items": [
{
@@ -178,7 +190,7 @@
"content": "Click to edit"
}
],
- "groupId": "xjwiuplgl79ezx460xfuplgr",
+ "groupId": "kq1g5z6pz4buot7sawqdrr3s",
"options": {
"variableId": "vkmbb3rb2hcfd2io1fhf7rz5x",
"buttonLabel": "Send",
@@ -187,36 +199,55 @@
}
},
{
- "id": "s27x4vixvakw62rwlsx61vw6",
+ "id": "j7pm34um4piuyabwlobjc356",
"type": "Set variable",
- "groupId": "xjwiuplgl79ezx460xfuplgr",
+ "groupId": "kq1g5z6pz4buot7sawqdrr3s",
"options": {
+ "type": "Map item with same index",
"isCode": true,
"variableId": "vwewa4yugqch2sswdpneszk3i",
+ "mapListItemParams": {
+ "baseItemVariableId": "vkmbb3rb2hcfd2io1fhf7rz5x",
+ "baseListVariableId": "vx0bbqzug4vk3zpc31ly8k7al",
+ "targetListVariableId": "vwc00rydyp035vtb0nlaqyzwr"
+ },
"expressionToEvaluate": "{{Genre IDs}}.at({{Genres}}.indexOf({{Selected genre}}))"
},
- "outgoingEdgeId": "vcuv47fbcqrtuelfy4i6ctqz"
+ "outgoingEdgeId": "tfuuwjnpn7mftd5s65mbhytd"
}
],
- "graphCoordinates": { "x": 630.84765625, "y": 333.68359375 }
+ "graphCoordinates": { "x": 630.85, "y": 333.68 }
},
{
- "id": "dewz832l9kse7xx1vhkihbk6",
+ "id": "d6v9lh83c7zuwrhf2mmo6nxo",
"title": "Trending",
"blocks": [
{
- "id": "yqci070mkxfjjdok5yztj1nx",
+ "id": "edokvbp15ubqeuydw9n7wf4w",
"type": "text",
"content": {
"richText": [{ "type": "p", "children": [{ "text": "Sure!" }] }]
},
- "groupId": "dewz832l9kse7xx1vhkihbk6"
+ "groupId": "d6v9lh83c7zuwrhf2mmo6nxo"
},
{
- "id": "xkd9bmjx1xf6uglewaeds8d8",
+ "id": "pwxb57b8nc2bp764vcdstois",
"type": "Webhook",
- "groupId": "dewz832l9kse7xx1vhkihbk6",
+ "groupId": "d6v9lh83c7zuwrhf2mmo6nxo",
"options": {
+ "webhook": {
+ "id": "a5oaijpqxo5a0mqrnjqg3tyt",
+ "url": "https://api.themoviedb.org/3/trending/movie/week",
+ "method": "GET",
+ "headers": [],
+ "queryParams": [
+ {
+ "id": "x9nxr63itm2lvbe8dmi53mi7",
+ "key": "api_key",
+ "value": "{{API Key}}"
+ }
+ ]
+ },
"isCustomBody": false,
"isAdvancedConfig": true,
"variablesForTest": [
@@ -238,11 +269,10 @@
"variableId": "vcmybxcoaytd2geo5sqx7v8hw"
}
]
- },
- "webhookId": "ohc5k58i0bs4i2vc5ymjxqbx"
+ }
},
{
- "id": "u6q1nvxowaszfdu4g42nqz38",
+ "id": "ruhgtbpv18cy2g5ujavljkku",
"type": "text",
"content": {
"richText": [
@@ -252,10 +282,10 @@
}
]
},
- "groupId": "dewz832l9kse7xx1vhkihbk6"
+ "groupId": "d6v9lh83c7zuwrhf2mmo6nxo"
},
{
- "id": "stlcncbupmsjxjipi86s45hy",
+ "id": "krcvvncnqtn99v0qe1dzudrk",
"type": "choice input",
"items": [
{
@@ -265,7 +295,7 @@
"content": "Click to edit"
}
],
- "groupId": "dewz832l9kse7xx1vhkihbk6",
+ "groupId": "d6v9lh83c7zuwrhf2mmo6nxo",
"options": {
"variableId": "vulnb1om2fk8mvkcesl8s15cr",
"buttonLabel": "Send",
@@ -274,25 +304,31 @@
}
},
{
- "id": "h0mbyxa1o6kyhpv2gwbweunu",
+ "id": "mgn6uuw2yebmengsukjramjx",
"type": "Set variable",
- "groupId": "dewz832l9kse7xx1vhkihbk6",
+ "groupId": "d6v9lh83c7zuwrhf2mmo6nxo",
"options": {
+ "type": "Map item with same index",
"isCode": true,
"variableId": "vzslfw8oyo1f08uo5rpkegn0x",
+ "mapListItemParams": {
+ "baseItemVariableId": "vulnb1om2fk8mvkcesl8s15cr",
+ "baseListVariableId": "vkzk96oh1pmdjv2bt5ps60qc0",
+ "targetListVariableId": "vcmybxcoaytd2geo5sqx7v8hw"
+ },
"expressionToEvaluate": "{{Trending IDs}}.at({{Trending Movies}}.indexOf({{Selected Trending Movie}}))"
},
- "outgoingEdgeId": "ctxcurho3eq3dkna3emyvpwr"
+ "outgoingEdgeId": "ual6xszx6tfcxqrnihc6zrvx"
}
],
- "graphCoordinates": { "x": 621.875, "y": 933.359375 }
+ "graphCoordinates": { "x": 628.03, "y": 965.53 }
},
{
- "id": "a1c99wep8eqambqjw8g8yeo8",
+ "id": "v35sky44jzz9fkwwul2qxufl",
"title": "Movies by genre",
"blocks": [
{
- "id": "dbp1cdzqy7txabinmm5osi1a",
+ "id": "g2pgwx5yr1ou9vkoy6gdwuor",
"type": "text",
"content": {
"richText": [
@@ -306,13 +342,31 @@
}
]
},
- "groupId": "a1c99wep8eqambqjw8g8yeo8"
+ "groupId": "v35sky44jzz9fkwwul2qxufl"
},
{
- "id": "ym35vqr6euzhmtj8i03yzilz",
+ "id": "a2datk3pv8o6xgitwjsq61m2",
"type": "Webhook",
- "groupId": "a1c99wep8eqambqjw8g8yeo8",
+ "groupId": "v35sky44jzz9fkwwul2qxufl",
"options": {
+ "webhook": {
+ "id": "em7huyvp98pd6hr25md0l6hb",
+ "url": "https://api.themoviedb.org/3/discover/movie",
+ "method": "GET",
+ "headers": [],
+ "queryParams": [
+ {
+ "id": "mgwlp399a056o9jo93tjqp02",
+ "key": "api_key",
+ "value": "{{API Key}}"
+ },
+ {
+ "id": "dv4wioynywqo57jq8lakq3yr",
+ "key": "with_genres",
+ "value": "{{Selected genre ID}}"
+ }
+ ]
+ },
"isCustomBody": false,
"isAdvancedConfig": true,
"variablesForTest": [
@@ -339,11 +393,10 @@
"variableId": "vhc2pc1sv4xc778r9od2ctooz"
}
]
- },
- "webhookId": "brjx15qipztmljhh31bsxj4u"
+ }
},
{
- "id": "dxp0gakw90f3ckahjuphx5ir",
+ "id": "tr5y76tx9ca336f8ob9odfa6",
"type": "choice input",
"items": [
{
@@ -353,7 +406,7 @@
"content": "Click to edit"
}
],
- "groupId": "a1c99wep8eqambqjw8g8yeo8",
+ "groupId": "v35sky44jzz9fkwwul2qxufl",
"options": {
"variableId": "vyyr3j2pu76uzvf88laai8snl",
"buttonLabel": "Send",
@@ -362,38 +415,57 @@
}
},
{
- "id": "hufag2k15sttgg2v2y1mipxz",
+ "id": "vudr8jrv2k3x0ubemt39tv7a",
"type": "Set variable",
- "groupId": "a1c99wep8eqambqjw8g8yeo8",
+ "groupId": "v35sky44jzz9fkwwul2qxufl",
"options": {
+ "type": "Map item with same index",
"isCode": true,
"variableId": "vzslfw8oyo1f08uo5rpkegn0x",
+ "mapListItemParams": {
+ "baseItemVariableId": "vyyr3j2pu76uzvf88laai8snl",
+ "baseListVariableId": "vad8vq3jfyybxo4la57hfd529",
+ "targetListVariableId": "vhc2pc1sv4xc778r9od2ctooz"
+ },
"expressionToEvaluate": "const movieIndex = {{Movies}}.indexOf({{Selected Movie}})\n\nreturn {{Movie IDs}}.at(movieIndex)"
},
- "outgoingEdgeId": "wv1qglc5fmexvpaei0xbhtzm"
+ "outgoingEdgeId": "r4wyd2185zhen98r5pmx53g9"
}
],
- "graphCoordinates": { "x": 977.484375, "y": 330.8515625 }
+ "graphCoordinates": { "x": 977.48, "y": 330.85 }
},
{
- "id": "xutzzlxo0f5hs9q8ga1e8tl9",
+ "id": "uozlg88loeb8xegu6y4le6k8",
"title": "Movie details",
"blocks": [
{
- "id": "tsferop3f80u2jq9p8ip964h",
+ "id": "ve9m7fromxw4tbm8558n8520",
"type": "text",
"content": {
"richText": [
{ "type": "p", "children": [{ "text": "Excellent choice đĨ" }] }
]
},
- "groupId": "xutzzlxo0f5hs9q8ga1e8tl9"
+ "groupId": "uozlg88loeb8xegu6y4le6k8"
},
{
- "id": "sssp7cez39ndix4owjl8x09t",
+ "id": "xag9d5i5td40kdt3poyq5g4b",
"type": "Webhook",
- "groupId": "xutzzlxo0f5hs9q8ga1e8tl9",
+ "groupId": "uozlg88loeb8xegu6y4le6k8",
"options": {
+ "webhook": {
+ "id": "ewrjdmbrm2yksvjrbp0z9duy",
+ "url": "https://api.themoviedb.org/3/movie/{{Selected Movie ID}}",
+ "method": "GET",
+ "headers": [],
+ "queryParams": [
+ {
+ "id": "f33u3unbezibfdv54kbkien3",
+ "key": "api_key",
+ "value": "{{API Key}}"
+ }
+ ]
+ },
"isCustomBody": false,
"isAdvancedConfig": true,
"variablesForTest": [
@@ -425,13 +497,12 @@
"variableId": "vzf5ryexokpr4dihiur2spm8z"
}
]
- },
- "webhookId": "d0x75v11voy19hnrrqnjlwpv"
+ }
},
{
- "id": "e4h6b2vb409zkwa0jrpyjpt0",
+ "id": "d0rsus9shxj8iowczbcaw53i",
"type": "Set variable",
- "groupId": "xutzzlxo0f5hs9q8ga1e8tl9",
+ "groupId": "uozlg88loeb8xegu6y4le6k8",
"options": {
"isCode": false,
"variableId": "vwitf3um5uweynypc0hxxwm14",
@@ -439,23 +510,23 @@
}
},
{
- "id": "ze726gr9fs7elsdqekbijckv",
+ "id": "nqnry4c1z3wwcni8rwpduuhe",
"type": "image",
"content": { "url": "{{Poster URL}}" },
- "groupId": "xutzzlxo0f5hs9q8ga1e8tl9"
+ "groupId": "uozlg88loeb8xegu6y4le6k8"
},
{
- "id": "t21xt4nr48hyu2qk2ij0d2oe",
+ "id": "lhljw54rdykyqtjiuh6jsl5c",
"type": "text",
"content": {
"richText": [
{ "type": "p", "children": [{ "text": "{{Movie Overview}}" }] }
]
},
- "groupId": "xutzzlxo0f5hs9q8ga1e8tl9"
+ "groupId": "uozlg88loeb8xegu6y4le6k8"
},
{
- "id": "nahhsfc6hsok5sufxcknopxr",
+ "id": "yifjhiamifo1y4ay7vols0mm",
"type": "choice input",
"items": [
{
@@ -463,7 +534,7 @@
"type": 0,
"blockId": "nahhsfc6hsok5sufxcknopxr",
"content": "Watch the movie",
- "outgoingEdgeId": "v2qrjwz43nej3nxbkbcq3kyd"
+ "outgoingEdgeId": "fteu5frsbj8wejfhhwzuv8t1"
},
{
"id": "j9d1pf2tndax0itezys7t73c",
@@ -472,44 +543,44 @@
"content": "Find something else"
}
],
- "groupId": "xutzzlxo0f5hs9q8ga1e8tl9",
+ "groupId": "uozlg88loeb8xegu6y4le6k8",
"options": { "buttonLabel": "Send", "isMultipleChoice": false },
- "outgoingEdgeId": "rddv20ku82g2yyagatt95t3r"
+ "outgoingEdgeId": "ntef5v9bt7vn4wg8s3dfm8yl"
}
],
- "graphCoordinates": { "x": 1198.4140625, "y": 846.53515625 }
+ "graphCoordinates": { "x": 1151.05, "y": 913.34 }
},
{
- "id": "vgulvp3bw1bi9em7yq6jfdrv",
+ "id": "x4d8cdsyzoqz6vzsurnb8twc",
"title": "Redirect to IMDB",
"blocks": [
{
- "id": "y7z3nkawu4dwh1otgcwbenyz",
+ "id": "mw0e0bzwiokhndkkncp9niu2",
"type": "Redirect",
- "groupId": "vgulvp3bw1bi9em7yq6jfdrv",
+ "groupId": "x4d8cdsyzoqz6vzsurnb8twc",
"options": {
"url": "https://m.imdb.com/title/{{IMDB ID}}",
"isNewTab": false
}
}
],
- "graphCoordinates": { "x": 1693.453125, "y": 1324.9921875 }
+ "graphCoordinates": { "x": 1616.55, "y": 1411.44 }
},
{
- "id": "trfx6m1qiu7awvwjfnhfd712",
+ "id": "x9610wtdv125hg56wicm2qmv",
"title": "Return to menu",
"blocks": [
{
- "id": "jumese0l44liwke0l5glv0fs",
+ "id": "efto9jivvcvomj3kltf57hbb",
"type": "Jump",
- "groupId": "trfx6m1qiu7awvwjfnhfd712",
+ "groupId": "x9610wtdv125hg56wicm2qmv",
"options": {
"blockId": "sodsq9mcigwvogmwx0t4jvil",
- "groupId": "vpsi7kfd4hiu38fd0txt3ndn"
+ "groupId": "mjnkukpkpvf4ha2g4n5m804v"
}
}
],
- "graphCoordinates": { "x": 1690.52734375, "y": 1501.76953125 }
+ "graphCoordinates": { "x": 1619.34, "y": 1586.5 }
}
],
"variables": [
@@ -532,70 +603,70 @@
],
"edges": [
{
- "id": "ozfvttdxg70dy931m762ssc0",
- "to": { "groupId": "vpsi7kfd4hiu38fd0txt3ndn" },
+ "id": "wfec8f4e1jtden2wqna6nrso",
+ "to": { "groupId": "mjnkukpkpvf4ha2g4n5m804v" },
"from": {
- "blockId": "dn2vorjxq275ulqwd97sgfjp",
- "groupId": "kto4a8zwoqvagp14sjol0mqq"
+ "blockId": "rha69fygov33vym1hf6z837p",
+ "groupId": "u6lpjibfjhyoqij5wjf9kvnl"
}
},
{
- "id": "o8n3df42rx0og3vlkzhskp7r",
- "to": { "groupId": "xjwiuplgl79ezx460xfuplgr" },
+ "id": "t8qyjpigrz7cdl8gxl1wxlwj",
+ "to": { "groupId": "kq1g5z6pz4buot7sawqdrr3s" },
"from": {
"itemId": "kaimvzg9igdtktgou5m3s1bw",
- "blockId": "sodsq9mcigwvogmwx0t4jvil",
- "groupId": "vpsi7kfd4hiu38fd0txt3ndn"
+ "blockId": "tzf45bvd8iquoxz7qgta8v94",
+ "groupId": "mjnkukpkpvf4ha2g4n5m804v"
}
},
{
- "id": "vcuv47fbcqrtuelfy4i6ctqz",
- "to": { "groupId": "a1c99wep8eqambqjw8g8yeo8" },
+ "id": "tfuuwjnpn7mftd5s65mbhytd",
+ "to": { "groupId": "v35sky44jzz9fkwwul2qxufl" },
"from": {
- "blockId": "s27x4vixvakw62rwlsx61vw6",
- "groupId": "xjwiuplgl79ezx460xfuplgr"
+ "blockId": "j7pm34um4piuyabwlobjc356",
+ "groupId": "kq1g5z6pz4buot7sawqdrr3s"
}
},
{
- "id": "wv1qglc5fmexvpaei0xbhtzm",
- "to": { "groupId": "xutzzlxo0f5hs9q8ga1e8tl9" },
+ "id": "r4wyd2185zhen98r5pmx53g9",
+ "to": { "groupId": "uozlg88loeb8xegu6y4le6k8" },
"from": {
- "blockId": "hufag2k15sttgg2v2y1mipxz",
- "groupId": "a1c99wep8eqambqjw8g8yeo8"
+ "blockId": "vudr8jrv2k3x0ubemt39tv7a",
+ "groupId": "v35sky44jzz9fkwwul2qxufl"
}
},
{
- "id": "v2qrjwz43nej3nxbkbcq3kyd",
- "to": { "groupId": "vgulvp3bw1bi9em7yq6jfdrv" },
+ "id": "fteu5frsbj8wejfhhwzuv8t1",
+ "to": { "groupId": "x4d8cdsyzoqz6vzsurnb8twc" },
"from": {
"itemId": "n4818dnrb4arw1xh5v0ot8vz",
- "blockId": "nahhsfc6hsok5sufxcknopxr",
- "groupId": "xutzzlxo0f5hs9q8ga1e8tl9"
+ "blockId": "yifjhiamifo1y4ay7vols0mm",
+ "groupId": "uozlg88loeb8xegu6y4le6k8"
}
},
{
- "id": "cfwctrcqoucw6djnn0j2zsv0",
- "to": { "groupId": "dewz832l9kse7xx1vhkihbk6" },
+ "id": "tjn2ljosqyd4aj9dk8mnifsu",
+ "to": { "groupId": "d6v9lh83c7zuwrhf2mmo6nxo" },
"from": {
"itemId": "i8ls2f8inq2ovuijj6l7rbcq",
- "blockId": "sodsq9mcigwvogmwx0t4jvil",
- "groupId": "vpsi7kfd4hiu38fd0txt3ndn"
+ "blockId": "tzf45bvd8iquoxz7qgta8v94",
+ "groupId": "mjnkukpkpvf4ha2g4n5m804v"
}
},
{
- "id": "rddv20ku82g2yyagatt95t3r",
- "to": { "groupId": "trfx6m1qiu7awvwjfnhfd712" },
+ "id": "ntef5v9bt7vn4wg8s3dfm8yl",
+ "to": { "groupId": "x9610wtdv125hg56wicm2qmv" },
"from": {
- "blockId": "nahhsfc6hsok5sufxcknopxr",
- "groupId": "xutzzlxo0f5hs9q8ga1e8tl9"
+ "blockId": "yifjhiamifo1y4ay7vols0mm",
+ "groupId": "uozlg88loeb8xegu6y4le6k8"
}
},
{
- "id": "ctxcurho3eq3dkna3emyvpwr",
- "to": { "groupId": "xutzzlxo0f5hs9q8ga1e8tl9" },
+ "id": "ual6xszx6tfcxqrnihc6zrvx",
+ "to": { "groupId": "uozlg88loeb8xegu6y4le6k8" },
"from": {
- "blockId": "h0mbyxa1o6kyhpv2gwbweunu",
- "groupId": "dewz832l9kse7xx1vhkihbk6"
+ "blockId": "mgn6uuw2yebmengsukjramjx",
+ "groupId": "d6v9lh83c7zuwrhf2mmo6nxo"
}
}
],
@@ -632,7 +703,6 @@
},
"publicId": null,
"customDomain": null,
- "workspaceId": "freeWorkspace",
"resultsTablePreferences": null,
"isArchived": false,
"isClosed": false
diff --git a/apps/builder/public/templates/nps.json b/apps/builder/public/templates/nps.json
index 87d375b95..38f0447ac 100644
--- a/apps/builder/public/templates/nps.json
+++ b/apps/builder/public/templates/nps.json
@@ -1,6 +1,6 @@
{
"id": "clesntjqu00011a4xkgffc3p0",
- "version": "3",
+ "version": "4",
"createdAt": "2023-03-03T14:57:41.430Z",
"updatedAt": "2023-03-03T16:14:29.268Z",
"icon": "â",
diff --git a/apps/builder/public/templates/onboarding.json b/apps/builder/public/templates/onboarding.json
index 897c62faf..cdc01f27e 100644
--- a/apps/builder/public/templates/onboarding.json
+++ b/apps/builder/public/templates/onboarding.json
@@ -1,6 +1,6 @@
{
"id": "qkvenb8ur2y0ahlbckmx7law",
- "version": "3",
+ "version": "4",
"createdAt": "2023-02-22T14:26:28.592Z",
"updatedAt": "2023-02-22T14:28:05.063Z",
"icon": null,
diff --git a/apps/builder/public/templates/product-recommendation.json b/apps/builder/public/templates/product-recommendation.json
index faaaaccc1..4a8690206 100644
--- a/apps/builder/public/templates/product-recommendation.json
+++ b/apps/builder/public/templates/product-recommendation.json
@@ -1,6 +1,6 @@
{
"id": "mdbjvgqhqypbsdd0airkzcwu",
- "version": "3",
+ "version": "4",
"createdAt": "2023-06-05T15:40:37.278Z",
"updatedAt": "2023-06-05T15:45:43.049Z",
"icon": "đĢ",
diff --git a/apps/builder/public/templates/quiz.json b/apps/builder/public/templates/quiz.json
index 4ab9e6a13..878c3066f 100644
--- a/apps/builder/public/templates/quiz.json
+++ b/apps/builder/public/templates/quiz.json
@@ -1,5 +1,6 @@
{
"id": "cl1qz4luj04007w1ai7rk1j5q",
+ "version": "4",
"createdAt": "2022-04-08T22:02:30.427Z",
"updatedAt": "2022-04-08T23:03:34.726Z",
"icon": "đšī¸",
diff --git a/apps/builder/src/features/blocks/integrations/makeCom/components/MakeComContent.tsx b/apps/builder/src/features/blocks/integrations/makeCom/components/MakeComContent.tsx
index 8a674a28d..d89f0528c 100644
--- a/apps/builder/src/features/blocks/integrations/makeCom/components/MakeComContent.tsx
+++ b/apps/builder/src/features/blocks/integrations/makeCom/components/MakeComContent.tsx
@@ -9,7 +9,7 @@ type Props = {
export const MakeComContent = ({ block }: Props) => {
const { webhooks } = useTypebot()
- const webhook = webhooks.find(byId(block.webhookId))
+ const webhook = block.options.webhook ?? webhooks.find(byId(block.webhookId))
if (isNotDefined(webhook?.body))
return Configure...
diff --git a/apps/builder/src/features/blocks/integrations/makeCom/components/MakeComSettings.tsx b/apps/builder/src/features/blocks/integrations/makeCom/components/MakeComSettings.tsx
index 4af1844f3..f3cd5bcc5 100644
--- a/apps/builder/src/features/blocks/integrations/makeCom/components/MakeComSettings.tsx
+++ b/apps/builder/src/features/blocks/integrations/makeCom/components/MakeComSettings.tsx
@@ -22,10 +22,17 @@ export const MakeComSettings = ({
const setLocalWebhook = useCallback(
async (newLocalWebhook: Webhook) => {
+ if (options.webhook) {
+ onOptionsChange({
+ ...options,
+ webhook: newLocalWebhook,
+ })
+ return
+ }
_setLocalWebhook(newLocalWebhook)
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
},
- [updateWebhook]
+ [onOptionsChange, options, updateWebhook]
)
useEffect(() => {
@@ -33,20 +40,23 @@ export const MakeComSettings = ({
!localWebhook ||
localWebhook.url ||
!webhook?.url ||
- webhook.url === localWebhook.url
+ webhook.url === localWebhook.url ||
+ options.webhook
)
return
setLocalWebhook({
...localWebhook,
url: webhook?.url,
})
- }, [webhook, localWebhook, setLocalWebhook])
+ }, [webhook, localWebhook, setLocalWebhook, options.webhook])
+
+ const url = options.webhook?.url ?? localWebhook?.url
return (
-
+
- {localWebhook?.url ? (
+ {url ? (
<>Your scenario is correctly configured đ>
) : (
@@ -62,10 +72,10 @@ export const MakeComSettings = ({
)}
- {localWebhook && (
+ {(localWebhook || options.webhook) && (
{
const { webhooks } = useTypebot()
- const webhook = webhooks.find(byId(block.webhookId))
+ const webhook = block.options.webhook ?? webhooks.find(byId(block.webhookId))
if (isNotDefined(webhook?.body))
return Configure...
diff --git a/apps/builder/src/features/blocks/integrations/pabbly/components/PabblyConnectSettings.tsx b/apps/builder/src/features/blocks/integrations/pabbly/components/PabblyConnectSettings.tsx
index c7f1601b3..a3cb1a529 100644
--- a/apps/builder/src/features/blocks/integrations/pabbly/components/PabblyConnectSettings.tsx
+++ b/apps/builder/src/features/blocks/integrations/pabbly/components/PabblyConnectSettings.tsx
@@ -27,6 +27,13 @@ export const PabblyConnectSettings = ({
)
const setLocalWebhook = async (newLocalWebhook: Webhook) => {
+ if (options.webhook) {
+ onOptionsChange({
+ ...options,
+ webhook: newLocalWebhook,
+ })
+ return
+ }
_setLocalWebhook(newLocalWebhook)
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
}
@@ -38,11 +45,13 @@ export const PabblyConnectSettings = ({
url,
})
+ const url = options.webhook?.url ?? localWebhook?.url
+
return (
-
+
- {localWebhook?.url ? (
+ {url ? (
<>Your scenario is correctly configured đ>
) : (
@@ -60,15 +69,15 @@ export const PabblyConnectSettings = ({
- {localWebhook && (
+ {(localWebhook || options.webhook) && (
& {
- webhooks: Webhook[]
- })
- | null
+ })) as Pick | null
if (!typebot)
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
const block = typebot.groups
- .flatMap((g) => g.blocks)
- .find((s) => s.id === blockId)
+ .flatMap((group) => group.blocks)
+ .find((block) => block.id === blockId)
if (!block)
throw new TRPCError({ code: 'NOT_FOUND', message: 'Block not found' })
diff --git a/apps/builder/src/features/blocks/integrations/webhook/api/listWebhookBlocks.ts b/apps/builder/src/features/blocks/integrations/webhook/api/listWebhookBlocks.ts
index 9d5ead8a2..ea63628d0 100644
--- a/apps/builder/src/features/blocks/integrations/webhook/api/listWebhookBlocks.ts
+++ b/apps/builder/src/features/blocks/integrations/webhook/api/listWebhookBlocks.ts
@@ -2,9 +2,10 @@ import prisma from '@/lib/prisma'
import { canReadTypebots } from '@/helpers/databaseRules'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server'
-import { Group, Typebot, Webhook, WebhookBlock } from '@typebot.io/schemas'
+import { Group, IntegrationBlockType, Typebot } from '@typebot.io/schemas'
import { byId, isWebhookBlock, parseGroupTitle } from '@typebot.io/lib'
import { z } from 'zod'
+import { Webhook } from '@typebot.io/prisma'
export const listWebhookBlocks = authenticatedProcedure
.meta({
@@ -28,6 +29,12 @@ export const listWebhookBlocks = authenticatedProcedure
webhookBlocks: z.array(
z.object({
id: z.string(),
+ type: z.enum([
+ IntegrationBlockType.WEBHOOK,
+ IntegrationBlockType.ZAPIER,
+ IntegrationBlockType.MAKE_COM,
+ IntegrationBlockType.PABBLY_CONNECT,
+ ]),
label: z.string(),
url: z.string().optional(),
})
@@ -46,17 +53,27 @@ export const listWebhookBlocks = authenticatedProcedure
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
const webhookBlocks = (typebot?.groups as Group[]).reduce<
- { id: string; label: string; url: string | undefined }[]
+ {
+ id: string
+ label: string
+ url: string | undefined
+ type:
+ | IntegrationBlockType.WEBHOOK
+ | IntegrationBlockType.ZAPIER
+ | IntegrationBlockType.MAKE_COM
+ | IntegrationBlockType.PABBLY_CONNECT
+ }[]
>((webhookBlocks, group) => {
- const blocks = group.blocks.filter((block) =>
- isWebhookBlock(block)
- ) as WebhookBlock[]
+ const blocks = group.blocks.filter(isWebhookBlock)
return [
...webhookBlocks,
- ...blocks.map((b) => ({
- id: b.id,
- label: `${parseGroupTitle(group.title)} > ${b.id}`,
- url: typebot?.webhooks.find(byId(b.webhookId))?.url ?? undefined,
+ ...blocks.map((block) => ({
+ id: block.id,
+ type: block.type,
+ label: `${parseGroupTitle(group.title)} > ${block.id}`,
+ url: block.options.webhook
+ ? block.options.webhook.url
+ : typebot?.webhooks.find(byId(block.webhookId))?.url ?? undefined,
})),
]
}, [])
diff --git a/apps/builder/src/features/blocks/integrations/webhook/api/subscribeWebhook.ts b/apps/builder/src/features/blocks/integrations/webhook/api/subscribeWebhook.ts
index 560c208f2..cdffab361 100644
--- a/apps/builder/src/features/blocks/integrations/webhook/api/subscribeWebhook.ts
+++ b/apps/builder/src/features/blocks/integrations/webhook/api/subscribeWebhook.ts
@@ -2,9 +2,10 @@ import prisma from '@/lib/prisma'
import { canWriteTypebots } from '@/helpers/databaseRules'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server'
-import { Typebot, Webhook, WebhookBlock } from '@typebot.io/schemas'
+import { Typebot, WebhookBlock } from '@typebot.io/schemas'
import { byId, isWebhookBlock } from '@typebot.io/lib'
import { z } from 'zod'
+import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/enums'
export const subscribeWebhook = authenticatedProcedure
.meta({
@@ -34,9 +35,8 @@ export const subscribeWebhook = authenticatedProcedure
where: canWriteTypebots(typebotId, user),
select: {
groups: true,
- webhooks: true,
},
- })) as (Pick & { webhooks: Webhook[] }) | null
+ })) as Pick | null
if (!typebot)
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
@@ -51,18 +51,50 @@ export const subscribeWebhook = authenticatedProcedure
message: 'Webhook block not found',
})
- await prisma.webhook.upsert({
- where: { id: webhookBlock.webhookId },
- update: { url, body: '{{state}}', method: 'POST' },
- create: {
- url,
- body: '{{state}}',
- method: 'POST',
- typebotId,
- headers: [],
- queryParams: [],
- },
- })
+ const newWebhook = {
+ id: webhookBlock.webhookId ?? webhookBlock.id,
+ url,
+ body: '{{state}}',
+ method: HttpMethod.POST,
+ headers: [],
+ queryParams: [],
+ }
+
+ if (webhookBlock.webhookId)
+ await prisma.webhook.upsert({
+ where: { id: webhookBlock.webhookId },
+ update: { url, body: newWebhook.body, method: newWebhook.method },
+ create: {
+ typebotId,
+ ...newWebhook,
+ },
+ })
+ else {
+ const updatedGroups = typebot.groups.map((group) =>
+ group.id !== webhookBlock.groupId
+ ? group
+ : {
+ ...group,
+ blocks: group.blocks.map((block) =>
+ block.id !== webhookBlock.id
+ ? block
+ : {
+ ...block,
+ options: {
+ ...webhookBlock.options,
+ webhook: newWebhook,
+ },
+ }
+ ),
+ }
+ )
+ await prisma.typebot.updateMany({
+ where: { id: typebotId },
+ data: {
+ groups: updatedGroups,
+ },
+ })
+ }
return {
id: blockId,
diff --git a/apps/builder/src/features/blocks/integrations/webhook/api/unsubscribeWebhook.ts b/apps/builder/src/features/blocks/integrations/webhook/api/unsubscribeWebhook.ts
index b4bf1b45c..2f4b4a208 100644
--- a/apps/builder/src/features/blocks/integrations/webhook/api/unsubscribeWebhook.ts
+++ b/apps/builder/src/features/blocks/integrations/webhook/api/unsubscribeWebhook.ts
@@ -50,10 +50,45 @@ export const unsubscribeWebhook = authenticatedProcedure
message: 'Webhook block not found',
})
- await prisma.webhook.update({
- where: { id: webhookBlock.webhookId },
- data: { url: null },
- })
+ if (webhookBlock.webhookId)
+ await prisma.webhook.update({
+ where: { id: webhookBlock.webhookId },
+ data: { url: null },
+ })
+ else {
+ if (!webhookBlock.options.webhook)
+ throw new TRPCError({
+ code: 'NOT_FOUND',
+ message: 'Webhook block not found',
+ })
+ const updatedGroups = typebot.groups.map((group) =>
+ group.id !== webhookBlock.groupId
+ ? group
+ : {
+ ...group,
+ blocks: group.blocks.map((block) =>
+ block.id !== webhookBlock.id
+ ? block
+ : {
+ ...block,
+ options: {
+ ...webhookBlock.options,
+ webhook: {
+ ...webhookBlock.options.webhook,
+ url: undefined,
+ },
+ },
+ }
+ ),
+ }
+ )
+ await prisma.typebot.updateMany({
+ where: { id: typebotId },
+ data: {
+ groups: updatedGroups,
+ },
+ })
+ }
return {
id: blockId,
diff --git a/apps/builder/src/features/blocks/integrations/webhook/components/WebhookAdvancedConfigForm.tsx b/apps/builder/src/features/blocks/integrations/webhook/components/WebhookAdvancedConfigForm.tsx
index e776d2b25..91c874d2c 100644
--- a/apps/builder/src/features/blocks/integrations/webhook/components/WebhookAdvancedConfigForm.tsx
+++ b/apps/builder/src/features/blocks/integrations/webhook/components/WebhookAdvancedConfigForm.tsx
@@ -16,7 +16,6 @@ import {
Text,
} from '@chakra-ui/react'
import {
- HttpMethod,
KeyValue,
VariableForTest,
ResponseVariableMapping,
@@ -31,6 +30,7 @@ import { QueryParamsInputs, HeadersInputs } from './KeyValueInputs'
import { DataVariableInputs } from './ResponseMappingInputs'
import { VariableForTestInputs } from './VariableForTestInputs'
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
+import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/enums'
type Props = {
blockId: string
@@ -78,9 +78,11 @@ export const WebhookAdvancedConfigForm = ({
onOptionsChange({ ...options, isCustomBody })
const executeTestRequest = async () => {
- if (!typebot || !webhook) return
+ if (!typebot) return
setIsTestResponseLoading(true)
- await Promise.all([updateWebhook(webhook.id, webhook), save()])
+ if (!options.webhook)
+ await Promise.all([updateWebhook(webhook.id, webhook), save()])
+ else await save()
const { data, error } = await executeWebhook(
typebot.id,
convertVariablesForTestToVariables(
diff --git a/apps/builder/src/features/blocks/integrations/webhook/components/WebhookContent.tsx b/apps/builder/src/features/blocks/integrations/webhook/components/WebhookContent.tsx
index 773c4d3ae..b6c76449a 100644
--- a/apps/builder/src/features/blocks/integrations/webhook/components/WebhookContent.tsx
+++ b/apps/builder/src/features/blocks/integrations/webhook/components/WebhookContent.tsx
@@ -8,10 +8,10 @@ type Props = {
block: WebhookBlock
}
-export const WebhookContent = ({ block: { webhookId, options } }: Props) => {
+export const WebhookContent = ({ block: { options, webhookId } }: Props) => {
const { typebot } = useTypebot()
const { webhooks } = useTypebot()
- const webhook = webhooks.find(byId(webhookId))
+ const webhook = options.webhook ?? webhooks.find(byId(webhookId))
if (!webhook?.url) return Configure...
return (
diff --git a/apps/builder/src/features/blocks/integrations/webhook/components/WebhookSettings.tsx b/apps/builder/src/features/blocks/integrations/webhook/components/WebhookSettings.tsx
index 4386de7eb..d95a3e391 100644
--- a/apps/builder/src/features/blocks/integrations/webhook/components/WebhookSettings.tsx
+++ b/apps/builder/src/features/blocks/integrations/webhook/components/WebhookSettings.tsx
@@ -21,25 +21,33 @@ export const WebhookSettings = ({
)
const setLocalWebhook = async (newLocalWebhook: Webhook) => {
+ if (options.webhook) {
+ onOptionsChange({ ...options, webhook: newLocalWebhook })
+ return
+ }
_setLocalWebhook(newLocalWebhook)
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
}
- const updateUrl = (url?: string) =>
- localWebhook && setLocalWebhook({ ...localWebhook, url: url ?? null })
+ const updateUrl = (url: string) => {
+ if (options.webhook)
+ onOptionsChange({ ...options, webhook: { ...options.webhook, url } })
+ else if (localWebhook)
+ setLocalWebhook({ ...localWebhook, url: url ?? undefined })
+ }
- if (!localWebhook) return
+ if (!localWebhook && !options.webhook) return
return (
{
)
await page.click('text=Test the request')
await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
- `"Group #1": "answer value", "Group #2": "20", "Group #2 (1)": "Yes"`, {timeout: 10000}
+ `"Group #1": "answer value", "Group #2": "20", "Group #2 (1)": "Yes"`,
+ { timeout: 10000 }
)
})
@@ -126,6 +127,7 @@ test.describe('API', () => {
expect(webhookBlocks[0]).toEqual({
id: 'webhookBlock',
label: 'Webhook > webhookBlock',
+ type: 'Webhook',
})
})
diff --git a/apps/builder/src/features/blocks/integrations/zapier/components/ZapierContent.tsx b/apps/builder/src/features/blocks/integrations/zapier/components/ZapierContent.tsx
index f13ad83e2..07daf7e01 100644
--- a/apps/builder/src/features/blocks/integrations/zapier/components/ZapierContent.tsx
+++ b/apps/builder/src/features/blocks/integrations/zapier/components/ZapierContent.tsx
@@ -9,7 +9,7 @@ type Props = {
export const ZapierContent = ({ block }: Props) => {
const { webhooks } = useTypebot()
- const webhook = webhooks.find(byId(block.webhookId))
+ const webhook = block.options.webhook ?? webhooks.find(byId(block.webhookId))
if (isNotDefined(webhook?.body))
return Configure...
diff --git a/apps/builder/src/features/blocks/integrations/zapier/components/ZapierSettings.tsx b/apps/builder/src/features/blocks/integrations/zapier/components/ZapierSettings.tsx
index e4a3b3757..8210b6f32 100644
--- a/apps/builder/src/features/blocks/integrations/zapier/components/ZapierSettings.tsx
+++ b/apps/builder/src/features/blocks/integrations/zapier/components/ZapierSettings.tsx
@@ -22,10 +22,17 @@ export const ZapierSettings = ({
const setLocalWebhook = useCallback(
async (newLocalWebhook: Webhook) => {
+ if (options.webhook) {
+ onOptionsChange({
+ ...options,
+ webhook: newLocalWebhook,
+ })
+ return
+ }
_setLocalWebhook(newLocalWebhook)
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
},
- [updateWebhook]
+ [onOptionsChange, options, updateWebhook]
)
useEffect(() => {
@@ -33,20 +40,23 @@ export const ZapierSettings = ({
!localWebhook ||
localWebhook.url ||
!webhook?.url ||
- webhook.url === localWebhook.url
+ webhook.url === localWebhook.url ||
+ options.webhook
)
return
setLocalWebhook({
...localWebhook,
url: webhook?.url,
})
- }, [webhook, localWebhook, setLocalWebhook])
+ }, [webhook, localWebhook, setLocalWebhook, options.webhook])
+
+ const url = options.webhook?.url ?? localWebhook?.url
return (
-
+
- {localWebhook?.url ? (
+ {url ? (
<>Your zap is correctly configured đ>
) : (
@@ -62,10 +72,10 @@ export const ZapierSettings = ({
)}
- {localWebhook && (
+ {(localWebhook || options.webhook) && (
{
await page.click('button:has-text("Age")')
await page.click('button:has-text("Select an operator")')
await page.click('button:has-text("Greater than")', { force: true })
- await page.fill('input[placeholder="Type a value..."]', '80')
+ await page.fill('input[placeholder="Type a number..."]', '80')
await page.click('button:has-text("Add a comparison")')
@@ -35,7 +35,7 @@ test.describe('Condition block', () => {
await page.click('button:has-text("Select an operator")')
await page.click('button:has-text("Less than")', { force: true })
await page.fill(
- ':nth-match(input[placeholder="Type a value..."], 2)',
+ ':nth-match(input[placeholder="Type a number..."], 2)',
'100'
)
@@ -47,7 +47,7 @@ test.describe('Condition block', () => {
await page.click('button:has-text("Age")')
await page.click('button:has-text("Select an operator")')
await page.click('button:has-text("Greater than")', { force: true })
- await page.fill('input[placeholder="Type a value..."]', '20')
+ await page.fill('input[placeholder="Type a number..."]', '20')
await page.click('text=Preview')
await page
diff --git a/apps/builder/src/features/dashboard/api/parseNewTypebot.ts b/apps/builder/src/features/dashboard/api/parseNewTypebot.ts
index fa3a21bc9..72a69bf6d 100644
--- a/apps/builder/src/features/dashboard/api/parseNewTypebot.ts
+++ b/apps/builder/src/features/dashboard/api/parseNewTypebot.ts
@@ -49,7 +49,7 @@ export const parseNewTypebot = ({
return {
folderId,
name,
- version: '3',
+ version: '4',
workspaceId,
groups: [startGroup],
edges: [],
diff --git a/apps/builder/src/features/dashboard/queries/importTypebotQuery.ts b/apps/builder/src/features/dashboard/queries/importTypebotQuery.ts
index b16640977..463d304f0 100644
--- a/apps/builder/src/features/dashboard/queries/importTypebotQuery.ts
+++ b/apps/builder/src/features/dashboard/queries/importTypebotQuery.ts
@@ -29,13 +29,19 @@ export const importTypebotQuery = async (typebot: Typebot, userPlan: Plan) => {
const webhookBlocks = typebot.groups
.flatMap((b) => b.blocks)
.filter(isWebhookBlock)
+ .filter((block) => block.webhookId)
await Promise.all(
- webhookBlocks.map((s) =>
+ webhookBlocks.map((webhookBlock) =>
duplicateWebhookQuery({
- existingIds: { typebotId: typebot.id, webhookId: s.webhookId },
+ existingIds: {
+ typebotId: typebot.id,
+ webhookId: webhookBlock.webhookId as string,
+ },
newIds: {
typebotId: newTypebot.id,
- webhookId: webhookIdsMapping.get(s.webhookId) as string,
+ webhookId: webhookIdsMapping.get(
+ webhookBlock.webhookId as string
+ ) as string,
},
})
)
@@ -51,84 +57,98 @@ const duplicateTypebot = (
webhookIdsMapping: Map
} => {
const groupIdsMapping = generateOldNewIdsMapping(typebot.groups)
+ const blockIdsMapping = generateOldNewIdsMapping(
+ typebot.groups.flatMap((group) => group.blocks)
+ )
const edgeIdsMapping = generateOldNewIdsMapping(typebot.edges)
const webhookIdsMapping = generateOldNewIdsMapping(
typebot.groups
- .flatMap((b) => b.blocks)
+ .flatMap((group) => group.blocks)
.filter(isWebhookBlock)
- .map((s) => ({ id: s.webhookId }))
+ .map((block) => ({
+ id: block.webhookId as string,
+ }))
)
const id = createId()
return {
typebot: {
...typebot,
- version: '3',
id,
name: `${typebot.name} copy`,
publicId: null,
customDomain: null,
- groups: typebot.groups.map((b) => ({
- ...b,
- id: groupIdsMapping.get(b.id) as string,
- blocks: b.blocks.map((s) => {
+ groups: typebot.groups.map((group) => ({
+ ...group,
+ id: groupIdsMapping.get(group.id) as string,
+ blocks: group.blocks.map((block) => {
const newIds = {
- groupId: groupIdsMapping.get(s.groupId) as string,
- outgoingEdgeId: s.outgoingEdgeId
- ? edgeIdsMapping.get(s.outgoingEdgeId)
+ id: blockIdsMapping.get(block.id) as string,
+ groupId: groupIdsMapping.get(block.groupId) as string,
+ outgoingEdgeId: block.outgoingEdgeId
+ ? edgeIdsMapping.get(block.outgoingEdgeId)
: undefined,
}
if (
- s.type === LogicBlockType.TYPEBOT_LINK &&
- s.options.typebotId === 'current' &&
- isDefined(s.options.groupId)
+ block.type === LogicBlockType.TYPEBOT_LINK &&
+ block.options.typebotId === 'current' &&
+ isDefined(block.options.groupId)
)
return {
- ...s,
+ ...block,
+ ...newIds,
options: {
- ...s.options,
- groupId: groupIdsMapping.get(s.options.groupId as string),
+ ...block.options,
+ groupId: groupIdsMapping.get(block.options.groupId as string),
},
}
- if (s.type === LogicBlockType.JUMP)
+ if (block.type === LogicBlockType.JUMP)
return {
- ...s,
+ ...block,
+ ...newIds,
options: {
- ...s.options,
- groupId: groupIdsMapping.get(s.options.groupId as string),
+ ...block.options,
+ groupId: groupIdsMapping.get(block.options.groupId as string),
} satisfies JumpBlock['options'],
}
- if (blockHasItems(s))
+ if (blockHasItems(block))
return {
- ...s,
- items: s.items.map((item) => ({
+ ...block,
+ ...newIds,
+ items: block.items.map((item) => ({
...item,
outgoingEdgeId: item.outgoingEdgeId
? (edgeIdsMapping.get(item.outgoingEdgeId) as string)
: undefined,
})),
- ...newIds,
} as ChoiceInputBlock | ConditionBlock
- if (isWebhookBlock(s)) {
+ if (isWebhookBlock(block) && block.webhookId) {
return {
- ...s,
- webhookId: webhookIdsMapping.get(s.webhookId) as string,
+ ...block,
...newIds,
+ webhookId: webhookIdsMapping.get(block.webhookId) as string,
}
}
return {
- ...s,
+ ...block,
...newIds,
}
}),
})),
- edges: typebot.edges.map((e) => ({
- ...e,
- id: edgeIdsMapping.get(e.id) as string,
+ edges: typebot.edges.map((edge) => ({
+ ...edge,
+ id: edgeIdsMapping.get(edge.id) as string,
from: {
- ...e.from,
- groupId: groupIdsMapping.get(e.from.groupId) as string,
+ ...edge.from,
+ blockId: blockIdsMapping.get(edge.from.blockId) as string,
+ groupId: groupIdsMapping.get(edge.from.groupId) as string,
+ },
+ to: {
+ ...edge.to,
+ blockId: edge.to.blockId
+ ? (blockIdsMapping.get(edge.to.blockId) as string)
+ : undefined,
+ groupId: groupIdsMapping.get(edge.to.groupId) as string,
},
- to: { ...e.to, groupId: groupIdsMapping.get(e.to.groupId) as string },
})),
settings:
typebot.settings.general.isBrandingEnabled === false &&
diff --git a/apps/builder/src/features/editor/providers/typebotActions/blocks.ts b/apps/builder/src/features/editor/providers/typebotActions/blocks.ts
index f9d991805..6a1907b0a 100644
--- a/apps/builder/src/features/editor/providers/typebotActions/blocks.ts
+++ b/apps/builder/src/features/editor/providers/typebotActions/blocks.ts
@@ -189,7 +189,8 @@ export const duplicateBlockDraft =
} as Block
if (isWebhookBlock(block)) {
const newWebhookId = createId()
- onWebhookBlockDuplicated(block.webhookId, newWebhookId)
+ if (block.webhookId)
+ onWebhookBlockDuplicated(block.webhookId, newWebhookId)
return {
...block,
groupId,
diff --git a/apps/builder/src/features/typebot/helpers/parseNewBlock.ts b/apps/builder/src/features/typebot/helpers/parseNewBlock.ts
index 58af86f85..a648f0bba 100644
--- a/apps/builder/src/features/typebot/helpers/parseNewBlock.ts
+++ b/apps/builder/src/features/typebot/helpers/parseNewBlock.ts
@@ -2,7 +2,6 @@ import { createId } from '@paralleldrive/cuid2'
import {
isBubbleBlockType,
blockTypeHasOption,
- blockTypeHasWebhook,
blockTypeHasItems,
} from '@typebot.io/lib'
import {
@@ -134,7 +133,7 @@ const parseDefaultBlockOptions = (type: BlockWithOptionsType): BlockOptions => {
case IntegrationBlockType.PABBLY_CONNECT:
case IntegrationBlockType.MAKE_COM:
case IntegrationBlockType.WEBHOOK:
- return defaultWebhookOptions
+ return defaultWebhookOptions(createId())
case IntegrationBlockType.EMAIL:
return defaultSendEmailOptions
case IntegrationBlockType.CHATWOOT:
@@ -159,7 +158,6 @@ export const parseNewBlock = (
options: blockTypeHasOption(type)
? parseDefaultBlockOptions(type)
: undefined,
- webhookId: blockTypeHasWebhook(type) ? createId() : undefined,
items: blockTypeHasItems(type) ? parseDefaultItems(type, id) : undefined,
} as DraggableBlock
}
diff --git a/apps/builder/src/pages/api/typebots/[typebotId].ts b/apps/builder/src/pages/api/typebots/[typebotId].ts
index d2ce038f0..f461c36e0 100644
--- a/apps/builder/src/pages/api/typebots/[typebotId].ts
+++ b/apps/builder/src/pages/api/typebots/[typebotId].ts
@@ -10,6 +10,7 @@ import { isReadTypebotForbidden } from '@/features/typebot/helpers/isReadTypebot
import { removeTypebotOldProperties } from '@/features/typebot/helpers/removeTypebotOldProperties'
import { roundGroupsCoordinate } from '@/features/typebot/helpers/roundGroupsCoordinate'
import { archiveResults } from '@typebot.io/lib/api/helpers/archiveResults'
+import { migrateTypebotFromV3ToV4 } from '@typebot.io/lib/migrations/migrateTypebotFromV3ToV4'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const user = await getAuthenticatedUser(req, res)
@@ -37,9 +38,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
collaborators.find((c) => c.userId === user.id)?.type ===
CollaborationType.READ
return res.send({
- typebot: roundGroupsCoordinate(
- removeTypebotOldProperties(typebot) as Typebot
- ),
+ typebot: await migrateTypebot(typebot as Typebot),
publishedTypebot,
isReadOnly,
webhooks,
@@ -95,7 +94,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const updates = {
...omit(data, 'id', 'createdAt', 'updatedAt'),
- version: '3',
theme: data.theme ?? undefined,
settings: data.settings ?? undefined,
resultsTablePreferences: data.resultsTablePreferences ?? undefined,
@@ -142,4 +140,12 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
return methodNotAllowed(res)
}
+const migrateTypebot = async (typebot: Typebot): Promise => {
+ if (typebot.version === '4') return typebot
+ const updatedTypebot = roundGroupsCoordinate(
+ removeTypebotOldProperties(typebot) as Typebot
+ )
+ return migrateTypebotFromV3ToV4(prisma)(updatedTypebot)
+}
+
export default handler
diff --git a/apps/docs/openapi/builder/_spec_.json b/apps/docs/openapi/builder/_spec_.json
index d524cd6cd..40963394c 100644
--- a/apps/docs/openapi/builder/_spec_.json
+++ b/apps/docs/openapi/builder/_spec_.json
@@ -2797,6 +2797,83 @@
},
"isExecutedOnClient": {
"type": "boolean"
+ },
+ "webhook": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "queryParams": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "headers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "method": {
+ "type": "string",
+ "enum": [
+ "POST",
+ "GET",
+ "PUT",
+ "DELETE",
+ "PATCH",
+ "HEAD",
+ "CONNECT",
+ "OPTIONS",
+ "TRACE"
+ ]
+ },
+ "url": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "queryParams",
+ "headers",
+ "method"
+ ],
+ "additionalProperties": false
}
},
"required": [
@@ -2806,15 +2883,15 @@
"additionalProperties": false
},
"webhookId": {
- "type": "string"
+ "type": "string",
+ "description": "Deprecated, use webhook.id instead"
}
},
"required": [
"id",
"groupId",
"type",
- "options",
- "webhookId"
+ "options"
],
"additionalProperties": false
},
@@ -3135,6 +3212,83 @@
},
"isExecutedOnClient": {
"type": "boolean"
+ },
+ "webhook": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "queryParams": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "headers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "method": {
+ "type": "string",
+ "enum": [
+ "POST",
+ "GET",
+ "PUT",
+ "DELETE",
+ "PATCH",
+ "HEAD",
+ "CONNECT",
+ "OPTIONS",
+ "TRACE"
+ ]
+ },
+ "url": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "queryParams",
+ "headers",
+ "method"
+ ],
+ "additionalProperties": false
}
},
"required": [
@@ -3144,15 +3298,15 @@
"additionalProperties": false
},
"webhookId": {
- "type": "string"
+ "type": "string",
+ "description": "Deprecated, use webhook.id instead"
}
},
"required": [
"id",
"groupId",
"type",
- "options",
- "webhookId"
+ "options"
],
"additionalProperties": false
},
@@ -3303,6 +3457,83 @@
},
"isExecutedOnClient": {
"type": "boolean"
+ },
+ "webhook": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "queryParams": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "headers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "method": {
+ "type": "string",
+ "enum": [
+ "POST",
+ "GET",
+ "PUT",
+ "DELETE",
+ "PATCH",
+ "HEAD",
+ "CONNECT",
+ "OPTIONS",
+ "TRACE"
+ ]
+ },
+ "url": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "queryParams",
+ "headers",
+ "method"
+ ],
+ "additionalProperties": false
}
},
"required": [
@@ -3312,15 +3543,15 @@
"additionalProperties": false
},
"webhookId": {
- "type": "string"
+ "type": "string",
+ "description": "Deprecated, now integrated in webhook block options"
}
},
"required": [
"id",
"groupId",
"type",
- "options",
- "webhookId"
+ "options"
],
"additionalProperties": false
},
@@ -3395,6 +3626,83 @@
},
"isExecutedOnClient": {
"type": "boolean"
+ },
+ "webhook": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "queryParams": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "headers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "method": {
+ "type": "string",
+ "enum": [
+ "POST",
+ "GET",
+ "PUT",
+ "DELETE",
+ "PATCH",
+ "HEAD",
+ "CONNECT",
+ "OPTIONS",
+ "TRACE"
+ ]
+ },
+ "url": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "queryParams",
+ "headers",
+ "method"
+ ],
+ "additionalProperties": false
}
},
"required": [
@@ -3404,15 +3712,15 @@
"additionalProperties": false
},
"webhookId": {
- "type": "string"
+ "type": "string",
+ "description": "Deprecated, use webhook.id instead"
}
},
"required": [
"id",
"groupId",
"type",
- "options",
- "webhookId"
+ "options"
],
"additionalProperties": false
},
@@ -4528,6 +4836,15 @@
"id": {
"type": "string"
},
+ "type": {
+ "type": "string",
+ "enum": [
+ "Webhook",
+ "Zapier",
+ "Make.com",
+ "Pabbly"
+ ]
+ },
"label": {
"type": "string"
},
@@ -4537,6 +4854,7 @@
},
"required": [
"id",
+ "type",
"label"
],
"additionalProperties": false
diff --git a/apps/docs/openapi/chat/_spec_.json b/apps/docs/openapi/chat/_spec_.json
index 8f40410d6..996529bb2 100644
--- a/apps/docs/openapi/chat/_spec_.json
+++ b/apps/docs/openapi/chat/_spec_.json
@@ -2365,6 +2365,83 @@
},
"isExecutedOnClient": {
"type": "boolean"
+ },
+ "webhook": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "queryParams": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "headers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "method": {
+ "type": "string",
+ "enum": [
+ "POST",
+ "GET",
+ "PUT",
+ "DELETE",
+ "PATCH",
+ "HEAD",
+ "CONNECT",
+ "OPTIONS",
+ "TRACE"
+ ]
+ },
+ "url": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "queryParams",
+ "headers",
+ "method"
+ ],
+ "additionalProperties": false
}
},
"required": [
@@ -2374,15 +2451,15 @@
"additionalProperties": false
},
"webhookId": {
- "type": "string"
+ "type": "string",
+ "description": "Deprecated, use webhook.id instead"
}
},
"required": [
"id",
"groupId",
"type",
- "options",
- "webhookId"
+ "options"
],
"additionalProperties": false
},
@@ -2703,6 +2780,83 @@
},
"isExecutedOnClient": {
"type": "boolean"
+ },
+ "webhook": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "queryParams": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "headers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "method": {
+ "type": "string",
+ "enum": [
+ "POST",
+ "GET",
+ "PUT",
+ "DELETE",
+ "PATCH",
+ "HEAD",
+ "CONNECT",
+ "OPTIONS",
+ "TRACE"
+ ]
+ },
+ "url": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "queryParams",
+ "headers",
+ "method"
+ ],
+ "additionalProperties": false
}
},
"required": [
@@ -2712,15 +2866,15 @@
"additionalProperties": false
},
"webhookId": {
- "type": "string"
+ "type": "string",
+ "description": "Deprecated, use webhook.id instead"
}
},
"required": [
"id",
"groupId",
"type",
- "options",
- "webhookId"
+ "options"
],
"additionalProperties": false
},
@@ -2871,6 +3025,83 @@
},
"isExecutedOnClient": {
"type": "boolean"
+ },
+ "webhook": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "queryParams": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "headers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "method": {
+ "type": "string",
+ "enum": [
+ "POST",
+ "GET",
+ "PUT",
+ "DELETE",
+ "PATCH",
+ "HEAD",
+ "CONNECT",
+ "OPTIONS",
+ "TRACE"
+ ]
+ },
+ "url": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "queryParams",
+ "headers",
+ "method"
+ ],
+ "additionalProperties": false
}
},
"required": [
@@ -2880,15 +3111,15 @@
"additionalProperties": false
},
"webhookId": {
- "type": "string"
+ "type": "string",
+ "description": "Deprecated, now integrated in webhook block options"
}
},
"required": [
"id",
"groupId",
"type",
- "options",
- "webhookId"
+ "options"
],
"additionalProperties": false
},
@@ -2963,6 +3194,83 @@
},
"isExecutedOnClient": {
"type": "boolean"
+ },
+ "webhook": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "queryParams": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "headers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "method": {
+ "type": "string",
+ "enum": [
+ "POST",
+ "GET",
+ "PUT",
+ "DELETE",
+ "PATCH",
+ "HEAD",
+ "CONNECT",
+ "OPTIONS",
+ "TRACE"
+ ]
+ },
+ "url": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "queryParams",
+ "headers",
+ "method"
+ ],
+ "additionalProperties": false
}
},
"required": [
@@ -2972,15 +3280,15 @@
"additionalProperties": false
},
"webhookId": {
- "type": "string"
+ "type": "string",
+ "description": "Deprecated, use webhook.id instead"
}
},
"required": [
"id",
"groupId",
"type",
- "options",
- "webhookId"
+ "options"
],
"additionalProperties": false
},
@@ -3524,7 +3832,7 @@
},
"isPreview": {
"type": "boolean",
- "description": "If set to `true`, it will start a Preview session with the unpublished bot and it won't be saved in the Results tab. You need to be authenticated for this to work."
+ "description": "If set to `true`, it will start a Preview session with the unpublished bot and it won't be saved in the Results tab. You need to be authenticated with a bearer token for this to work."
},
"resultId": {
"type": "string",
@@ -5808,7 +6116,7 @@
}
}
},
- "/session/{sessionId}/updateTypebot": {
+ "/sessions/{sessionId}/updateTypebot": {
"post": {
"operationId": "updateTypebotInSession",
"summary": "Update typebot in session",
diff --git a/apps/viewer/next.config.js b/apps/viewer/next.config.js
index 1d63c5dd5..5518859ce 100644
--- a/apps/viewer/next.config.js
+++ b/apps/viewer/next.config.js
@@ -76,11 +76,40 @@ const nextConfig = {
}))
)
: []
- ).concat({
- source: '/api/typebots/:typebotId/blocks/:blockId/storage/upload-url',
- destination:
- '/api/v1/typebots/:typebotId/blocks/:blockId/storage/upload-url',
- }),
+ ).concat([
+ {
+ source: '/api/typebots/:typebotId/blocks/:blockId/storage/upload-url',
+ destination:
+ '/api/v1/typebots/:typebotId/blocks/:blockId/storage/upload-url',
+ },
+ {
+ source:
+ '/api/typebots/:typebotId/blocks/:blockId/steps/:stepId/sampleResult',
+ destination: `${process.env.NEXTAUTH_URL}/api/v1/typebots/:typebotId/webhookBlocks/:blockId/getResultExample`,
+ },
+ {
+ source: '/api/typebots/:typebotId/blocks/:blockId/sampleResult',
+ destination: `${process.env.NEXTAUTH_URL}/api/v1/typebots/:typebotId/webhookBlocks/:blockId/getResultExample`,
+ },
+ {
+ source:
+ '/api/typebots/:typebotId/blocks/:blockId/steps/:stepId/unsubscribeWebhook',
+ destination: `${process.env.NEXTAUTH_URL}/api/v1/typebots/:typebotId/webhookBlocks/:blockId/unsubscribe`,
+ },
+ {
+ source: '/api/typebots/:typebotId/blocks/:blockId/unsubscribeWebhook',
+ destination: `${process.env.NEXTAUTH_URL}/api/v1/typebots/:typebotId/webhookBlocks/:blockId/unsubscribe`,
+ },
+ {
+ source:
+ '/api/typebots/:typebotId/blocks/:blockId/steps/:stepId/subscribeWebhook',
+ destination: `${process.env.NEXTAUTH_URL}/api/v1/typebots/:typebotId/webhookBlocks/:blockId/subscribe`,
+ },
+ {
+ source: '/api/typebots/:typebotId/blocks/:blockId/subscribeWebhook',
+ destination: `${process.env.NEXTAUTH_URL}/api/v1/typebots/:typebotId/webhookBlocks/:blockId/subscribe`,
+ },
+ ]),
}
},
}
diff --git a/apps/viewer/src/features/blocks/integrations/webhook/executeWebhookBlock.ts b/apps/viewer/src/features/blocks/integrations/webhook/executeWebhookBlock.ts
index 99fa60519..5f852f891 100644
--- a/apps/viewer/src/features/blocks/integrations/webhook/executeWebhookBlock.ts
+++ b/apps/viewer/src/features/blocks/integrations/webhook/executeWebhookBlock.ts
@@ -11,7 +11,6 @@ import {
WebhookResponse,
WebhookOptions,
defaultWebhookAttributes,
- HttpMethod,
PublicTypebot,
KeyValue,
ReplyLog,
@@ -26,6 +25,7 @@ import { parseSampleResult } from './parseSampleResult'
import { ExecuteIntegrationResponse } from '@/features/chat/types'
import { parseVariables } from '@/features/variables/parseVariables'
import { resumeWebhookExecution } from './resumeWebhookExecution'
+import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/enums'
type ParsedWebhook = ExecutableWebhook & {
basicAuth: { username?: string; password?: string }
@@ -38,9 +38,11 @@ export const executeWebhookBlock = async (
): Promise => {
const { typebot, result } = state
const logs: ReplyLog[] = []
- const webhook = (await prisma.webhook.findUnique({
- where: { id: block.webhookId },
- })) as Webhook | null
+ const webhook =
+ block.options.webhook ??
+ ((await prisma.webhook.findUnique({
+ where: { id: block.webhookId },
+ })) as Webhook | null)
if (!webhook) {
logs.push({
status: 'error',
diff --git a/apps/viewer/src/features/blocks/integrations/webhook/webhook.spec.ts b/apps/viewer/src/features/blocks/integrations/webhook/webhook.spec.ts
index ebe6c3972..fc34488b6 100644
--- a/apps/viewer/src/features/blocks/integrations/webhook/webhook.spec.ts
+++ b/apps/viewer/src/features/blocks/integrations/webhook/webhook.spec.ts
@@ -1,6 +1,6 @@
import test, { expect } from '@playwright/test'
import { createId } from '@paralleldrive/cuid2'
-import { HttpMethod } from '@typebot.io/schemas'
+import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/enums'
import {
createWebhook,
importTypebotInDatabase,
diff --git a/apps/viewer/src/features/chat/chat.spec.ts b/apps/viewer/src/features/chat/chat.spec.ts
index 7a0d26062..99f034c7e 100644
--- a/apps/viewer/src/features/chat/chat.spec.ts
+++ b/apps/viewer/src/features/chat/chat.spec.ts
@@ -2,13 +2,14 @@ import { getTestAsset } from '@/test/utils/playwright'
import test, { expect } from '@playwright/test'
import { createId } from '@paralleldrive/cuid2'
import prisma from '@/lib/prisma'
-import { HttpMethod, SendMessageInput } from '@typebot.io/schemas'
+import { SendMessageInput } from '@typebot.io/schemas'
import {
createWebhook,
deleteTypebots,
deleteWebhooks,
importTypebotInDatabase,
} from '@typebot.io/lib/playwright/databaseActions'
+import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/enums'
test.afterEach(async () => {
await deleteWebhooks(['chat-webhook-id'])
diff --git a/apps/viewer/src/pages/[[...publicId]].tsx b/apps/viewer/src/pages/[[...publicId]].tsx
index bb46f7afe..54a442610 100644
--- a/apps/viewer/src/pages/[[...publicId]].tsx
+++ b/apps/viewer/src/pages/[[...publicId]].tsx
@@ -162,7 +162,7 @@ const App = ({
return
if (publishedTypebot.typebot.isClosed)
return
- return publishedTypebot.version === '3' ? (
+ return publishedTypebot.version ? (
{
const block = typebot.groups
.flatMap((g) => g.blocks)
.find(byId(blockId)) as WebhookBlock
- const webhook = typebot.webhooks.find(byId(block.webhookId))
+ const webhook =
+ block.options.webhook ?? typebot.webhooks.find(byId(block.webhookId))
if (!webhook)
return res
.status(404)
@@ -84,116 +85,131 @@ const checkIfBodyIsAVariable = (body: string) => /^{{.+}}$/.test(body)
export const executeWebhook =
(typebot: Typebot) =>
- async ({
- webhook,
- variables,
- groupId,
- resultValues,
- resultId,
- parentTypebotIds = [],
- }: {
- webhook: Webhook
- variables: Variable[]
- groupId: string
- resultValues?: ResultValues
- resultId?: string
- parentTypebotIds: string[]
- }): Promise => {
- if (!webhook.url || !webhook.method)
- return {
- statusCode: 400,
- data: { message: `Webhook doesn't have url or method` },
- }
- const basicAuth: { username?: string; password?: string } = {}
- const basicAuthHeaderIdx = webhook.headers.findIndex(
- (h) =>
- h.key?.toLowerCase() === 'authorization' &&
- h.value?.toLowerCase()?.includes('basic')
- )
- const isUsernamePasswordBasicAuth =
- basicAuthHeaderIdx !== -1 &&
- webhook.headers[basicAuthHeaderIdx].value?.includes(':')
- if (isUsernamePasswordBasicAuth) {
- const [username, password] =
- webhook.headers[basicAuthHeaderIdx].value?.slice(6).split(':') ?? []
- basicAuth.username = username
- basicAuth.password = password
- webhook.headers.splice(basicAuthHeaderIdx, 1)
- }
- const headers = convertKeyValueTableToObject(webhook.headers, variables) as
- | Headers
- | undefined
- const queryParams = stringify(
- convertKeyValueTableToObject(webhook.queryParams, variables)
- )
- const contentType = headers ? headers['Content-Type'] : undefined
- const linkedTypebotsParents = await fetchLinkedTypebots({
- isPreview: !('typebotId' in typebot),
- typebotIds: parentTypebotIds,
- })
- const linkedTypebotsChildren = await getPreviouslyLinkedTypebots({
- isPreview: !('typebotId' in typebot),
- typebots: [typebot],
- })([])
- const bodyContent = await getBodyContent(typebot, [
- ...linkedTypebotsParents,
- ...linkedTypebotsChildren,
- ])({
- body: webhook.body,
- resultValues,
- groupId,
+ async ({
+ webhook,
variables,
- })
- const { data: body, isJson } =
- bodyContent && webhook.method !== HttpMethod.GET
- ? safeJsonParse(
+ groupId,
+ resultValues,
+ resultId,
+ parentTypebotIds = [],
+ }: {
+ webhook: Webhook
+ variables: Variable[]
+ groupId: string
+ resultValues?: ResultValues
+ resultId?: string
+ parentTypebotIds: string[]
+ }): Promise => {
+ if (!webhook.url || !webhook.method)
+ return {
+ statusCode: 400,
+ data: { message: `Webhook doesn't have url or method` },
+ }
+ const basicAuth: { username?: string; password?: string } = {}
+ const basicAuthHeaderIdx = webhook.headers.findIndex(
+ (h) =>
+ h.key?.toLowerCase() === 'authorization' &&
+ h.value?.toLowerCase()?.includes('basic')
+ )
+ const isUsernamePasswordBasicAuth =
+ basicAuthHeaderIdx !== -1 &&
+ webhook.headers[basicAuthHeaderIdx].value?.includes(':')
+ if (isUsernamePasswordBasicAuth) {
+ const [username, password] =
+ webhook.headers[basicAuthHeaderIdx].value?.slice(6).split(':') ?? []
+ basicAuth.username = username
+ basicAuth.password = password
+ webhook.headers.splice(basicAuthHeaderIdx, 1)
+ }
+ const headers = convertKeyValueTableToObject(webhook.headers, variables) as
+ | Headers
+ | undefined
+ const queryParams = stringify(
+ convertKeyValueTableToObject(webhook.queryParams, variables)
+ )
+ const contentType = headers ? headers['Content-Type'] : undefined
+ const linkedTypebotsParents = await fetchLinkedTypebots({
+ isPreview: !('typebotId' in typebot),
+ typebotIds: parentTypebotIds,
+ })
+ const linkedTypebotsChildren = await getPreviouslyLinkedTypebots({
+ isPreview: !('typebotId' in typebot),
+ typebots: [typebot],
+ })([])
+ const bodyContent = await getBodyContent(typebot, [
+ ...linkedTypebotsParents,
+ ...linkedTypebotsChildren,
+ ])({
+ body: webhook.body,
+ resultValues,
+ groupId,
+ variables,
+ })
+ const { data: body, isJson } =
+ bodyContent && webhook.method !== HttpMethod.GET
+ ? safeJsonParse(
parseVariables(variables, {
escapeForJson: !checkIfBodyIsAVariable(bodyContent),
})(bodyContent)
)
- : { data: undefined, isJson: false }
+ : { data: undefined, isJson: false }
- const request = {
- url: parseVariables(variables)(
- webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
- ),
- method: webhook.method as Method,
- headers,
- ...basicAuth,
- json:
- !contentType?.includes('x-www-form-urlencoded') && body && isJson
- ? body
- : undefined,
- form:
- contentType?.includes('x-www-form-urlencoded') && body
- ? body
- : undefined,
- body: body && !isJson ? body : undefined,
- }
- try {
- const response = await got(request.url, omit(request, 'url'))
- await saveSuccessLog({
- resultId,
- message: 'Webhook successfuly executed.',
- details: {
- statusCode: response.statusCode,
- request,
- response: safeJsonParse(response.body).data,
- },
- })
- return {
- statusCode: response.statusCode,
- data: safeJsonParse(response.body).data,
+ const request = {
+ url: parseVariables(variables)(
+ webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
+ ),
+ method: webhook.method as Method,
+ headers,
+ ...basicAuth,
+ json:
+ !contentType?.includes('x-www-form-urlencoded') && body && isJson
+ ? body
+ : undefined,
+ form:
+ contentType?.includes('x-www-form-urlencoded') && body
+ ? body
+ : undefined,
+ body: body && !isJson ? body : undefined,
}
- } catch (error) {
- if (error instanceof HTTPError) {
- const response = {
- statusCode: error.response.statusCode,
- data: safeJsonParse(error.response.body as string).data,
+ try {
+ const response = await got(request.url, omit(request, 'url'))
+ await saveSuccessLog({
+ resultId,
+ message: 'Webhook successfuly executed.',
+ details: {
+ statusCode: response.statusCode,
+ request,
+ response: safeJsonParse(response.body).data,
+ },
+ })
+ return {
+ statusCode: response.statusCode,
+ data: safeJsonParse(response.body).data,
}
+ } catch (error) {
+ if (error instanceof HTTPError) {
+ const response = {
+ statusCode: error.response.statusCode,
+ data: safeJsonParse(error.response.body as string).data,
+ }
+ await saveErrorLog({
+ resultId,
+ message: 'Webhook returned an error',
+ details: {
+ request,
+ response,
+ },
+ })
+ return response
+ }
+ const response = {
+ statusCode: 500,
+ data: { message: `Error from Typebot server: ${error}` },
+ }
+ console.error(error)
await saveErrorLog({
resultId,
- message: 'Webhook returned an error',
+ message: 'Webhook failed to execute',
details: {
request,
response,
@@ -201,51 +217,36 @@ export const executeWebhook =
})
return response
}
- const response = {
- statusCode: 500,
- data: { message: `Error from Typebot server: ${error}` },
- }
- console.error(error)
- await saveErrorLog({
- resultId,
- message: 'Webhook failed to execute',
- details: {
- request,
- response,
- },
- })
- return response
}
- }
const getBodyContent =
(
typebot: Pick,
linkedTypebots: (Typebot | PublicTypebot)[]
) =>
- async ({
- body,
- resultValues,
- groupId,
- variables,
- }: {
- body?: string | null
- resultValues?: ResultValues
- groupId: string
- variables: Variable[]
- }): Promise => {
- if (!body) return
- return body === '{{state}}'
- ? JSON.stringify(
+ async ({
+ body,
+ resultValues,
+ groupId,
+ variables,
+ }: {
+ body?: string | null
+ resultValues?: ResultValues
+ groupId: string
+ variables: Variable[]
+ }): Promise => {
+ if (!body) return
+ return body === '{{state}}'
+ ? JSON.stringify(
resultValues
? parseAnswers(typebot, linkedTypebots)(resultValues)
: await parseSampleResult(typebot, linkedTypebots)(
- groupId,
- variables
- )
+ groupId,
+ variables
+ )
)
- : body
- }
+ : body
+ }
const convertKeyValueTableToObject = (
keyValues: KeyValue[] | undefined,
diff --git a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/sampleResult.ts b/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/sampleResult.ts
deleted file mode 100644
index c09c2682e..000000000
--- a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/sampleResult.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { authenticateUser } from '@/helpers/authenticateUser'
-import prisma from '@/lib/prisma'
-import { Typebot } from '@typebot.io/schemas'
-import { NextApiRequest, NextApiResponse } from 'next'
-import { methodNotAllowed } from '@typebot.io/lib/api'
-import { getPreviouslyLinkedTypebots } from '@/features/blocks/logic/typebotLink/getPreviouslyLinkedTypebots'
-import { parseSampleResult } from '@/features/blocks/integrations/webhook/parseSampleResult'
-
-const handler = async (req: NextApiRequest, res: NextApiResponse) => {
- const user = await authenticateUser(req)
- if (!user) return res.status(401).json({ message: 'Not authenticated' })
- if (req.method === 'GET') {
- const typebotId = req.query.typebotId as string
- const blockId = req.query.blockId as string
- const typebot = (await prisma.typebot.findFirst({
- where: {
- id: typebotId,
- workspace: { members: { some: { userId: user.id } } },
- },
- })) as unknown as Typebot | undefined
- if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
- const block = typebot.groups
- .flatMap((g) => g.blocks)
- .find((s) => s.id === blockId)
- if (!block) return res.status(404).send({ message: 'Group not found' })
- const linkedTypebots = await getPreviouslyLinkedTypebots({
- isPreview: true,
- typebots: [typebot],
- user,
- })([])
- return res.send(
- await parseSampleResult(typebot, linkedTypebots)(block.groupId, [])
- )
- }
- methodNotAllowed(res)
-}
-
-export default handler
diff --git a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/executeWebhook.ts b/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/executeWebhook.ts
deleted file mode 100644
index c84d65408..000000000
--- a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/executeWebhook.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import prisma from '@/lib/prisma'
-import {
- defaultWebhookAttributes,
- ResultValues,
- Typebot,
- Variable,
- Webhook,
- WebhookOptions,
- WebhookBlock,
-} from '@typebot.io/schemas'
-import { NextApiRequest, NextApiResponse } from 'next'
-import { initMiddleware, methodNotAllowed, notFound } from '@typebot.io/lib/api'
-import { byId } from '@typebot.io/lib'
-import Cors from 'cors'
-import { executeWebhook } from '../../executeWebhook'
-
-const cors = initMiddleware(Cors())
-
-const handler = async (req: NextApiRequest, res: NextApiResponse) => {
- await cors(req, res)
- if (req.method === 'POST') {
- const typebotId = req.query.typebotId as string
- const groupId = req.query.groupId as string
- const blockId = req.query.blockId as string
- const resultId = req.query.resultId as string | undefined
- const { resultValues, variables, parentTypebotIds } = (
- typeof req.body === 'string' ? JSON.parse(req.body) : req.body
- ) as {
- resultValues: ResultValues
- variables: Variable[]
- parentTypebotIds: string[]
- }
- const typebot = (await prisma.typebot.findUnique({
- where: { id: typebotId },
- include: { webhooks: true },
- })) as unknown as (Typebot & { webhooks: Webhook[] }) | null
- if (!typebot) return notFound(res)
- const block = typebot.groups
- .find(byId(groupId))
- ?.blocks.find(byId(blockId)) as WebhookBlock
- const webhook = typebot.webhooks.find(byId(block.webhookId))
- if (!webhook)
- return res
- .status(404)
- .send({ statusCode: 404, data: { message: `Couldn't find webhook` } })
- const preparedWebhook = prepareWebhookAttributes(webhook, block.options)
- const result = await executeWebhook(typebot)({
- webhook: preparedWebhook,
- variables,
- groupId,
- resultValues,
- resultId,
- parentTypebotIds,
- })
- return res.status(200).send(result)
- }
- return methodNotAllowed(res)
-}
-
-const prepareWebhookAttributes = (
- webhook: Webhook,
- options: WebhookOptions
-): Webhook => {
- if (options.isAdvancedConfig === false) {
- return { ...webhook, body: '{{state}}', ...defaultWebhookAttributes }
- } else if (options.isCustomBody === false) {
- return { ...webhook, body: '{{state}}' }
- }
- return webhook
-}
-
-export default handler
diff --git a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/sampleResult.ts b/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/sampleResult.ts
deleted file mode 100644
index bd7f2af96..000000000
--- a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/sampleResult.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import prisma from '@/lib/prisma'
-import { Typebot } from '@typebot.io/schemas'
-import { NextApiRequest, NextApiResponse } from 'next'
-import { methodNotAllowed } from '@typebot.io/lib/api'
-import { parseSampleResult } from '@/features/blocks/integrations/webhook/parseSampleResult'
-import { getPreviouslyLinkedTypebots } from '@/features/blocks/logic/typebotLink/getPreviouslyLinkedTypebots'
-import { authenticateUser } from '@/helpers/authenticateUser'
-
-const handler = async (req: NextApiRequest, res: NextApiResponse) => {
- const user = await authenticateUser(req)
- if (!user) return res.status(401).json({ message: 'Not authenticated' })
- if (req.method === 'GET') {
- const typebotId = req.query.typebotId as string
- const groupId = req.query.groupId as string
- const typebot = (await prisma.typebot.findFirst({
- where: {
- id: typebotId,
- workspace: { members: { some: { userId: user.id } } },
- },
- })) as unknown as Typebot | undefined
- if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
- const linkedTypebots = await getPreviouslyLinkedTypebots({
- isPreview: true,
- typebots: [typebot],
- user,
- })([])
- return res.send(
- await parseSampleResult(typebot, linkedTypebots)(groupId, [])
- )
- }
- methodNotAllowed(res)
-}
-
-export default handler
diff --git a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/subscribeWebhook.ts b/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/subscribeWebhook.ts
deleted file mode 100644
index ccf709096..000000000
--- a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/subscribeWebhook.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { Typebot, WebhookBlock } from '@typebot.io/schemas'
-import { NextApiRequest, NextApiResponse } from 'next'
-import { methodNotAllowed } from '@typebot.io/lib/api'
-import { byId } from '@typebot.io/lib'
-import prisma from '@/lib/prisma'
-import { authenticateUser } from '@/helpers/authenticateUser'
-
-const handler = async (req: NextApiRequest, res: NextApiResponse) => {
- const user = await authenticateUser(req)
- if (!user) return res.status(401).json({ message: 'Not authenticated' })
- if (req.method === 'POST') {
- const body = req.body as Record
- if (!('url' in body))
- return res.status(403).send({ message: 'url is missing in body' })
- const { url } = body
- const typebotId = req.query.typebotId as string
- const groupId = req.query.groupId as string
- const blockId = req.query.blockId as string
- const typebot = (await prisma.typebot.findFirst({
- where: {
- id: typebotId,
- workspace: { members: { some: { userId: user.id } } },
- },
- })) as unknown as Typebot | undefined
- if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
- try {
- const { webhookId } = typebot.groups
- .find(byId(groupId))
- ?.blocks.find(byId(blockId)) as WebhookBlock
- await prisma.webhook.upsert({
- where: { id: webhookId },
- update: { url, body: '{{state}}', method: 'POST' },
- create: {
- url,
- body: '{{state}}',
- method: 'POST',
- typebotId,
- headers: [],
- queryParams: [],
- },
- })
-
- return res.send({ message: 'success' })
- } catch (err) {
- return res
- .status(400)
- .send({ message: "blockId doesn't point to a Webhook block" })
- }
- }
- return methodNotAllowed(res)
-}
-
-export default handler
diff --git a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/unsubscribeWebhook.ts b/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/unsubscribeWebhook.ts
deleted file mode 100644
index 0e7dff4cc..000000000
--- a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/steps/[stepId]/unsubscribeWebhook.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { Typebot, WebhookBlock } from '@typebot.io/schemas'
-import { NextApiRequest, NextApiResponse } from 'next'
-import { methodNotAllowed } from '@typebot.io/lib/api'
-import { byId } from '@typebot.io/lib'
-import { authenticateUser } from '@/helpers/authenticateUser'
-import prisma from '@/lib/prisma'
-
-const handler = async (req: NextApiRequest, res: NextApiResponse) => {
- const user = await authenticateUser(req)
- if (!user) return res.status(401).json({ message: 'Not authenticated' })
- if (req.method === 'POST') {
- const typebotId = req.query.typebotId as string
- const groupId = req.query.groupId as string
- const blockId = req.query.blockId as string
- const typebot = (await prisma.typebot.findFirst({
- where: {
- id: typebotId,
- workspace: { members: { some: { userId: user.id } } },
- },
- })) as unknown as Typebot | undefined
- if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
- try {
- const { webhookId } = typebot.groups
- .find(byId(groupId))
- ?.blocks.find(byId(blockId)) as WebhookBlock
- await prisma.webhook.updateMany({
- where: { id: webhookId },
- data: { url: null },
- })
-
- return res.send({ message: 'success' })
- } catch (err) {
- return res
- .status(400)
- .send({ message: "blockId doesn't point to a Webhook block" })
- }
- }
- return methodNotAllowed(res)
-}
-
-export default handler
diff --git a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/subscribeWebhook.ts b/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/subscribeWebhook.ts
deleted file mode 100644
index 30e0c207f..000000000
--- a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/subscribeWebhook.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { authenticateUser } from '@/helpers/authenticateUser'
-import prisma from '@/lib/prisma'
-import { Typebot, WebhookBlock } from '@typebot.io/schemas'
-import { NextApiRequest, NextApiResponse } from 'next'
-import { byId } from '@typebot.io/lib'
-import { methodNotAllowed } from '@typebot.io/lib/api'
-
-const handler = async (req: NextApiRequest, res: NextApiResponse) => {
- const user = await authenticateUser(req)
- if (!user) return res.status(401).json({ message: 'Not authenticated' })
- if (req.method === 'POST') {
- const body = req.body as Record
- if (!('url' in body))
- return res.status(403).send({ message: 'url is missing in body' })
- const { url } = body
- const typebotId = req.query.typebotId as string
- const blockId = req.query.blockId as string
- const typebot = (await prisma.typebot.findFirst({
- where: {
- id: typebotId,
- workspace: { members: { some: { userId: user.id } } },
- },
- })) as unknown as Typebot | undefined
- if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
- try {
- const { webhookId } = typebot.groups
- .flatMap((g) => g.blocks)
- .find(byId(blockId)) as WebhookBlock
- await prisma.webhook.upsert({
- where: { id: webhookId },
- update: { url, body: '{{state}}', method: 'POST' },
- create: {
- url,
- body: '{{state}}',
- method: 'POST',
- typebotId,
- headers: [],
- queryParams: [],
- },
- })
-
- return res.send({ message: 'success' })
- } catch (err) {
- return res
- .status(400)
- .send({ message: "groupId doesn't point to a Webhook block" })
- }
- }
- return methodNotAllowed(res)
-}
-
-export default handler
diff --git a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/unsubscribeWebhook.ts b/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/unsubscribeWebhook.ts
deleted file mode 100644
index 3cc12035b..000000000
--- a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/unsubscribeWebhook.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { authenticateUser } from '@/helpers/authenticateUser'
-import prisma from '@/lib/prisma'
-import { Typebot, WebhookBlock } from '@typebot.io/schemas'
-import { NextApiRequest, NextApiResponse } from 'next'
-import { byId } from '@typebot.io/lib'
-import { methodNotAllowed } from '@typebot.io/lib/api'
-
-const handler = async (req: NextApiRequest, res: NextApiResponse) => {
- const user = await authenticateUser(req)
- if (!user) return res.status(401).json({ message: 'Not authenticated' })
- if (req.method === 'POST') {
- const typebotId = req.query.typebotId as string
- const blockId = req.query.blockId as string
- const typebot = (await prisma.typebot.findFirst({
- where: {
- id: typebotId,
- workspace: { members: { some: { userId: user.id } } },
- },
- })) as unknown as Typebot | undefined
- if (!typebot) return res.status(400).send({ message: 'Typebot not found' })
- try {
- const { webhookId } = typebot.groups
- .flatMap((g) => g.blocks)
- .find(byId(blockId)) as WebhookBlock
- await prisma.webhook.updateMany({
- where: { id: webhookId },
- data: { url: null },
- })
-
- return res.send({ message: 'success' })
- } catch (err) {
- return res
- .status(400)
- .send({ message: "groupId doesn't point to a Webhook block" })
- }
- }
- return methodNotAllowed(res)
-}
-
-export default handler
diff --git a/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx b/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx
index 3e13c6e13..abe640c66 100644
--- a/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx
+++ b/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx
@@ -105,7 +105,6 @@ export const ConversationContainer = (props: Props) => {
})
const streamMessage = (content: string) => {
- console.log('STREAM', content)
setIsSending(false)
const lastChunk = [...chatChunks()].pop()
if (!lastChunk) return
diff --git a/packages/lib/migrations/migrateTypebotFromV3ToV4.ts b/packages/lib/migrations/migrateTypebotFromV3ToV4.ts
new file mode 100644
index 000000000..9e9bc3644
--- /dev/null
+++ b/packages/lib/migrations/migrateTypebotFromV3ToV4.ts
@@ -0,0 +1,63 @@
+import { PrismaClient, Webhook as WebhookFromDb } from '@typebot.io/prisma'
+import {
+ Block,
+ Typebot,
+ Webhook,
+ defaultWebhookAttributes,
+} from '@typebot.io/schemas'
+import { isWebhookBlock } from '../utils'
+import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/enums'
+
+export const migrateTypebotFromV3ToV4 =
+ (prisma: PrismaClient) =>
+ async (
+ typebot: Typebot
+ ): Promise & { version: '4' }> => {
+ if (typebot.version === '4')
+ return typebot as Omit & { version: '4' }
+ const webhookBlocks = typebot.groups
+ .flatMap((group) => group.blocks)
+ .filter(isWebhookBlock)
+ const webhooks = await prisma.webhook.findMany({
+ where: {
+ id: {
+ in: webhookBlocks.map((block) => block.webhookId as string),
+ },
+ },
+ })
+ return {
+ ...typebot,
+ version: '4',
+ groups: typebot.groups.map((group) => ({
+ ...group,
+ blocks: group.blocks.map(migrateWebhookBlock(webhooks)),
+ })),
+ }
+ }
+
+const migrateWebhookBlock =
+ (webhooks: WebhookFromDb[]) =>
+ (block: Block): Block => {
+ if (!isWebhookBlock(block)) return block
+ const webhook = webhooks.find((webhook) => webhook.id === block.webhookId)
+ return {
+ ...block,
+ webhookId: undefined,
+ options: {
+ ...block.options,
+ webhook: webhook
+ ? {
+ id: webhook.id,
+ url: webhook.url ?? undefined,
+ method: (webhook.method as Webhook['method']) ?? HttpMethod.POST,
+ headers: (webhook.headers as Webhook['headers']) ?? [],
+ queryParams: (webhook.queryParams as Webhook['headers']) ?? [],
+ body: webhook.body ?? undefined,
+ }
+ : {
+ ...defaultWebhookAttributes,
+ id: block.webhookId ?? '',
+ },
+ },
+ }
+ }
diff --git a/packages/lib/utils.ts b/packages/lib/utils.ts
index fec1e9ee5..adade3949 100644
--- a/packages/lib/utils.ts
+++ b/packages/lib/utils.ts
@@ -125,16 +125,6 @@ export const blockTypeHasOption = (
.concat(Object.values(IntegrationBlockType))
.includes(type)
-export const blockTypeHasWebhook = (
- type: BlockType
-): type is IntegrationBlockType.WEBHOOK =>
- Object.values([
- IntegrationBlockType.WEBHOOK,
- IntegrationBlockType.ZAPIER,
- IntegrationBlockType.MAKE_COM,
- IntegrationBlockType.PABBLY_CONNECT,
- ] as string[]).includes(type)
-
export const blockTypeHasItems = (
type: BlockType
): type is
diff --git a/packages/schemas/features/blocks/integrations/index.ts b/packages/schemas/features/blocks/integrations/index.ts
index 8fee9da55..04a5b113f 100644
--- a/packages/schemas/features/blocks/integrations/index.ts
+++ b/packages/schemas/features/blocks/integrations/index.ts
@@ -5,7 +5,7 @@ export * from './googleSheets'
export * from './makeCom'
export * from './pabblyConnect'
export * from './sendEmail'
-export * from './webhook'
+export * from './webhook/schemas'
export * from './zapier'
export * from './pixel/schemas'
export * from './pixel/constants'
diff --git a/packages/schemas/features/blocks/integrations/makeCom.ts b/packages/schemas/features/blocks/integrations/makeCom.ts
index 4650f21cc..b092a4164 100644
--- a/packages/schemas/features/blocks/integrations/makeCom.ts
+++ b/packages/schemas/features/blocks/integrations/makeCom.ts
@@ -1,13 +1,16 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
-import { webhookOptionsSchema } from './webhook'
+import { webhookOptionsSchema } from './webhook/schemas'
export const makeComBlockSchema = blockBaseSchema.merge(
z.object({
type: z.enum([IntegrationBlockType.MAKE_COM]),
options: webhookOptionsSchema,
- webhookId: z.string(),
+ webhookId: z
+ .string()
+ .describe('Deprecated, use webhook.id instead')
+ .optional(),
})
)
diff --git a/packages/schemas/features/blocks/integrations/pabblyConnect.ts b/packages/schemas/features/blocks/integrations/pabblyConnect.ts
index 45fc3105c..726f2cb70 100644
--- a/packages/schemas/features/blocks/integrations/pabblyConnect.ts
+++ b/packages/schemas/features/blocks/integrations/pabblyConnect.ts
@@ -1,13 +1,16 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
-import { webhookOptionsSchema } from './webhook'
+import { webhookOptionsSchema } from './webhook/schemas'
export const pabblyConnectBlockSchema = blockBaseSchema.merge(
z.object({
type: z.enum([IntegrationBlockType.PABBLY_CONNECT]),
options: webhookOptionsSchema,
- webhookId: z.string(),
+ webhookId: z
+ .string()
+ .describe('Deprecated, use webhook.id instead')
+ .optional(),
})
)
diff --git a/packages/schemas/features/blocks/integrations/webhook.ts b/packages/schemas/features/blocks/integrations/webhook.ts
deleted file mode 100644
index e7df30162..000000000
--- a/packages/schemas/features/blocks/integrations/webhook.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { z } from 'zod'
-import { blockBaseSchema } from '../baseSchemas'
-import { IntegrationBlockType } from './enums'
-
-const variableForTestSchema = z.object({
- id: z.string(),
- variableId: z.string().optional(),
- value: z.string().optional(),
-})
-
-const responseVariableMappingSchema = z.object({
- id: z.string(),
- variableId: z.string().optional(),
- bodyPath: z.string().optional(),
-})
-
-export const webhookOptionsSchema = z.object({
- variablesForTest: z.array(variableForTestSchema),
- responseVariableMapping: z.array(responseVariableMappingSchema),
- isAdvancedConfig: z.boolean().optional(),
- isCustomBody: z.boolean().optional(),
- isExecutedOnClient: z.boolean().optional(),
-})
-
-export const webhookBlockSchema = blockBaseSchema.merge(
- z.object({
- type: z.enum([IntegrationBlockType.WEBHOOK]),
- options: webhookOptionsSchema,
- webhookId: z.string(),
- })
-)
-
-export const defaultWebhookOptions: Omit = {
- responseVariableMapping: [],
- variablesForTest: [],
- isAdvancedConfig: false,
- isCustomBody: false,
-}
-
-export type WebhookBlock = z.infer
-export type WebhookOptions = z.infer
-export type ResponseVariableMapping = z.infer<
- typeof responseVariableMappingSchema
->
-export type VariableForTest = z.infer
diff --git a/packages/schemas/features/blocks/integrations/webhook/enums.ts b/packages/schemas/features/blocks/integrations/webhook/enums.ts
new file mode 100644
index 000000000..c1a618095
--- /dev/null
+++ b/packages/schemas/features/blocks/integrations/webhook/enums.ts
@@ -0,0 +1,11 @@
+export enum HttpMethod {
+ POST = 'POST',
+ GET = 'GET',
+ PUT = 'PUT',
+ DELETE = 'DELETE',
+ PATCH = 'PATCH',
+ HEAD = 'HEAD',
+ CONNECT = 'CONNECT',
+ OPTIONS = 'OPTIONS',
+ TRACE = 'TRACE',
+}
diff --git a/packages/schemas/features/blocks/integrations/webhook/index.ts b/packages/schemas/features/blocks/integrations/webhook/index.ts
new file mode 100644
index 000000000..47da4e4b5
--- /dev/null
+++ b/packages/schemas/features/blocks/integrations/webhook/index.ts
@@ -0,0 +1,2 @@
+export * from './enums'
+export * from './schemas'
diff --git a/packages/schemas/features/blocks/integrations/webhook/schemas.ts b/packages/schemas/features/blocks/integrations/webhook/schemas.ts
new file mode 100644
index 000000000..522bea48c
--- /dev/null
+++ b/packages/schemas/features/blocks/integrations/webhook/schemas.ts
@@ -0,0 +1,95 @@
+import { z } from 'zod'
+import { blockBaseSchema } from '../../baseSchemas'
+import { IntegrationBlockType } from '../enums'
+import { HttpMethod } from './enums'
+
+const variableForTestSchema = z.object({
+ id: z.string(),
+ variableId: z.string().optional(),
+ value: z.string().optional(),
+})
+
+const responseVariableMappingSchema = z.object({
+ id: z.string(),
+ variableId: z.string().optional(),
+ bodyPath: z.string().optional(),
+})
+
+const keyValueSchema = z.object({
+ id: z.string(),
+ key: z.string().optional(),
+ value: z.string().optional(),
+})
+
+export const webhookSchema = z.object({
+ id: z.string(),
+ queryParams: keyValueSchema.array(),
+ headers: keyValueSchema.array(),
+ method: z.nativeEnum(HttpMethod),
+ url: z.string().optional(),
+ body: z.string().optional(),
+})
+
+export const webhookOptionsSchema = z.object({
+ variablesForTest: z.array(variableForTestSchema),
+ responseVariableMapping: z.array(responseVariableMappingSchema),
+ isAdvancedConfig: z.boolean().optional(),
+ isCustomBody: z.boolean().optional(),
+ isExecutedOnClient: z.boolean().optional(),
+ webhook: webhookSchema.optional(),
+})
+
+export const webhookBlockSchema = blockBaseSchema.merge(
+ z.object({
+ type: z.enum([IntegrationBlockType.WEBHOOK]),
+ options: webhookOptionsSchema,
+ webhookId: z
+ .string()
+ .describe('Deprecated, now integrated in webhook block options')
+ .optional(),
+ })
+)
+
+export const defaultWebhookAttributes: Omit<
+ Webhook,
+ 'id' | 'body' | 'url' | 'typebotId' | 'createdAt' | 'updatedAt'
+> = {
+ method: HttpMethod.POST,
+ headers: [],
+ queryParams: [],
+}
+
+export const defaultWebhookOptions = (webhookId: string): WebhookOptions => ({
+ responseVariableMapping: [],
+ variablesForTest: [],
+ isAdvancedConfig: false,
+ isCustomBody: false,
+ webhook: {
+ id: webhookId,
+ ...defaultWebhookAttributes,
+ },
+})
+
+export const executableWebhookSchema = z.object({
+ url: z.string(),
+ headers: z.record(z.string()).optional(),
+ body: z.unknown().optional(),
+ method: z.nativeEnum(HttpMethod).optional(),
+})
+
+export type KeyValue = { id: string; key?: string; value?: string }
+
+export type WebhookResponse = {
+ statusCode: number
+ data?: unknown
+}
+
+export type ExecutableWebhook = z.infer
+
+export type Webhook = z.infer
+export type WebhookBlock = z.infer
+export type WebhookOptions = z.infer
+export type ResponseVariableMapping = z.infer<
+ typeof responseVariableMappingSchema
+>
+export type VariableForTest = z.infer
diff --git a/packages/schemas/features/blocks/integrations/zapier.ts b/packages/schemas/features/blocks/integrations/zapier.ts
index 470ab9e50..b75489e24 100644
--- a/packages/schemas/features/blocks/integrations/zapier.ts
+++ b/packages/schemas/features/blocks/integrations/zapier.ts
@@ -1,13 +1,16 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
-import { webhookOptionsSchema } from './webhook'
+import { webhookOptionsSchema } from './webhook/schemas'
export const zapierBlockSchema = blockBaseSchema.merge(
z.object({
type: z.enum([IntegrationBlockType.ZAPIER]),
options: webhookOptionsSchema,
- webhookId: z.string(),
+ webhookId: z
+ .string()
+ .describe('Deprecated, use webhook.id instead')
+ .optional(),
})
)
diff --git a/packages/schemas/features/chat.ts b/packages/schemas/features/chat.ts
index fcb11145c..07f3f326f 100644
--- a/packages/schemas/features/chat.ts
+++ b/packages/schemas/features/chat.ts
@@ -1,5 +1,6 @@
import { z } from 'zod'
import {
+ executableWebhookSchema,
googleAnalyticsOptionsSchema,
paymentInputRuntimeOptionsSchema,
pixelOptionsSchema,
@@ -19,7 +20,6 @@ import { answerSchema } from './answer'
import { BubbleBlockType } from './blocks/bubbles/enums'
import { inputBlockSchemas } from './blocks/schemas'
import { chatCompletionMessageSchema } from './blocks/integrations/openai'
-import { executableWebhookSchema } from './webhooks'
const typebotInSessionStateSchema = publicTypebotSchema.pick({
id: true,
diff --git a/packages/schemas/features/publicTypebot.ts b/packages/schemas/features/publicTypebot.ts
index 6574c8d8c..478a96ac8 100644
--- a/packages/schemas/features/publicTypebot.ts
+++ b/packages/schemas/features/publicTypebot.ts
@@ -11,7 +11,7 @@ import { z } from 'zod'
export const publicTypebotSchema = z.object({
id: z.string(),
- version: z.enum(['3']).nullable(),
+ version: z.enum(['3', '4']).nullable(),
createdAt: z.date(),
updatedAt: z.date(),
typebotId: z.string(),
diff --git a/packages/schemas/features/typebot/typebot.ts b/packages/schemas/features/typebot/typebot.ts
index 3a58b098a..5038ba2d6 100644
--- a/packages/schemas/features/typebot/typebot.ts
+++ b/packages/schemas/features/typebot/typebot.ts
@@ -39,7 +39,7 @@ const resultsTablePreferencesSchema = z.object({
})
export const typebotSchema = z.object({
- version: z.enum(['3']).nullable(),
+ version: z.enum(['3', '4']).nullable(),
id: z.string(),
name: z.string(),
groups: z.array(groupSchema),
diff --git a/packages/schemas/features/webhooks.ts b/packages/schemas/features/webhooks.ts
deleted file mode 100644
index 8685767d5..000000000
--- a/packages/schemas/features/webhooks.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Webhook as WebhookFromPrisma } from '@typebot.io/prisma'
-import { z } from 'zod'
-
-export enum HttpMethod {
- POST = 'POST',
- GET = 'GET',
- PUT = 'PUT',
- DELETE = 'DELETE',
- PATCH = 'PATCH',
- HEAD = 'HEAD',
- CONNECT = 'CONNECT',
- OPTIONS = 'OPTIONS',
- TRACE = 'TRACE',
-}
-
-export type KeyValue = { id: string; key?: string; value?: string }
-
-export type Webhook = Omit<
- WebhookFromPrisma,
- 'queryParams' | 'headers' | 'method' | 'createdAt' | 'updatedAt'
-> & {
- queryParams: KeyValue[]
- headers: KeyValue[]
- method: HttpMethod
-}
-
-export type WebhookResponse = {
- statusCode: number
- data?: unknown
-}
-
-export const defaultWebhookAttributes: Omit<
- Webhook,
- 'id' | 'body' | 'url' | 'typebotId' | 'createdAt' | 'updatedAt'
-> = {
- method: HttpMethod.POST,
- headers: [],
- queryParams: [],
-}
-
-export const executableWebhookSchema = z.object({
- url: z.string(),
- headers: z.record(z.string()).optional(),
- body: z.unknown().optional(),
- method: z.nativeEnum(HttpMethod).optional(),
-})
-
-export type ExecutableWebhook = z.infer
diff --git a/packages/schemas/index.ts b/packages/schemas/index.ts
index bcd884df2..4c298d0df 100644
--- a/packages/schemas/index.ts
+++ b/packages/schemas/index.ts
@@ -5,7 +5,6 @@ export * from './features/result'
export * from './features/answer'
export * from './features/utils'
export * from './features/credentials'
-export * from './features/webhooks'
export * from './features/chat'
export * from './features/workspace'
export * from './features/items'