@nyaruka/temba-components 0.129.3 → 0.129.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.github/workflows/build.yml +135 -3
  3. package/CHANGELOG.md +18 -0
  4. package/demo/data/flows/sample-flow.json +110 -87
  5. package/demo/field-config-demo.html +135 -0
  6. package/dist/temba-components.js +1257 -675
  7. package/dist/temba-components.js.map +1 -1
  8. package/docs/ActionEditor-Migration.md +118 -0
  9. package/out-tsc/src/events.js.map +1 -1
  10. package/out-tsc/src/flow/{EditorNode.js → CanvasNode.js} +345 -42
  11. package/out-tsc/src/flow/CanvasNode.js.map +1 -0
  12. package/out-tsc/src/flow/Editor.js +107 -3
  13. package/out-tsc/src/flow/Editor.js.map +1 -1
  14. package/out-tsc/src/flow/NodeEditor.js +1200 -0
  15. package/out-tsc/src/flow/NodeEditor.js.map +1 -0
  16. package/out-tsc/src/flow/Plumber.js +0 -6
  17. package/out-tsc/src/flow/Plumber.js.map +1 -1
  18. package/out-tsc/src/flow/actions/add_contact_groups.js +40 -0
  19. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -0
  20. package/out-tsc/src/flow/actions/add_contact_urn.js +16 -0
  21. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -0
  22. package/out-tsc/src/flow/actions/add_input_labels.js +11 -0
  23. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -0
  24. package/out-tsc/src/flow/actions/call_classifier.js +11 -0
  25. package/out-tsc/src/flow/actions/call_classifier.js.map +1 -0
  26. package/out-tsc/src/flow/actions/call_llm.js +11 -0
  27. package/out-tsc/src/flow/actions/call_llm.js.map +1 -0
  28. package/out-tsc/src/flow/actions/call_resthook.js +11 -0
  29. package/out-tsc/src/flow/actions/call_resthook.js.map +1 -0
  30. package/out-tsc/src/flow/actions/call_webhook.js +122 -0
  31. package/out-tsc/src/flow/actions/call_webhook.js.map +1 -0
  32. package/out-tsc/src/flow/actions/enter_flow.js +14 -0
  33. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
  34. package/out-tsc/src/flow/actions/open_ticket.js +11 -0
  35. package/out-tsc/src/flow/actions/open_ticket.js.map +1 -0
  36. package/out-tsc/src/flow/actions/play_audio.js +11 -0
  37. package/out-tsc/src/flow/actions/play_audio.js.map +1 -0
  38. package/out-tsc/src/flow/actions/remove_contact_groups.js +62 -0
  39. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -0
  40. package/out-tsc/src/flow/actions/request_optin.js +11 -0
  41. package/out-tsc/src/flow/actions/request_optin.js.map +1 -0
  42. package/out-tsc/src/flow/actions/say_msg.js +11 -0
  43. package/out-tsc/src/flow/actions/say_msg.js.map +1 -0
  44. package/out-tsc/src/flow/actions/send_broadcast.js +33 -0
  45. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -0
  46. package/out-tsc/src/flow/actions/send_email.js +56 -0
  47. package/out-tsc/src/flow/actions/send_email.js.map +1 -0
  48. package/out-tsc/src/flow/actions/send_msg.js +55 -0
  49. package/out-tsc/src/flow/actions/send_msg.js.map +1 -0
  50. package/out-tsc/src/flow/actions/set_contact_channel.js +12 -0
  51. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -0
  52. package/out-tsc/src/flow/actions/set_contact_field.js +12 -0
  53. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -0
  54. package/out-tsc/src/flow/actions/set_contact_language.js +10 -0
  55. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -0
  56. package/out-tsc/src/flow/actions/set_contact_name.js +10 -0
  57. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -0
  58. package/out-tsc/src/flow/actions/set_contact_status.js +10 -0
  59. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -0
  60. package/out-tsc/src/flow/actions/set_run_result.js +10 -0
  61. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -0
  62. package/out-tsc/src/flow/actions/split_by_expression_example.js +77 -0
  63. package/out-tsc/src/flow/actions/split_by_expression_example.js.map +1 -0
  64. package/out-tsc/src/flow/actions/start_session.js +11 -0
  65. package/out-tsc/src/flow/actions/start_session.js.map +1 -0
  66. package/out-tsc/src/flow/actions/transfer_airtime.js +11 -0
  67. package/out-tsc/src/flow/actions/transfer_airtime.js.map +1 -0
  68. package/out-tsc/src/flow/config.js +88 -193
  69. package/out-tsc/src/flow/config.js.map +1 -1
  70. package/out-tsc/src/flow/nodes/execute_actions.js +4 -0
  71. package/out-tsc/src/flow/nodes/execute_actions.js.map +1 -0
  72. package/out-tsc/src/flow/nodes/split_by_airtime.js +9 -0
  73. package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -0
  74. package/out-tsc/src/flow/nodes/split_by_contact_field.js +7 -0
  75. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -0
  76. package/out-tsc/src/flow/nodes/split_by_expression.js +7 -0
  77. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -0
  78. package/out-tsc/src/flow/nodes/split_by_groups.js +7 -0
  79. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -0
  80. package/out-tsc/src/flow/nodes/split_by_random.js +10 -0
  81. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -0
  82. package/out-tsc/src/flow/nodes/split_by_run_result.js +7 -0
  83. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -0
  84. package/out-tsc/src/flow/nodes/split_by_scheme.js +7 -0
  85. package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -0
  86. package/out-tsc/src/flow/nodes/split_by_subflow.js +9 -0
  87. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -0
  88. package/out-tsc/src/flow/nodes/split_by_webhook.js +18 -0
  89. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -0
  90. package/out-tsc/src/flow/nodes/wait_for_audio.js +7 -0
  91. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
  92. package/out-tsc/src/flow/nodes/wait_for_digits.js +7 -0
  93. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -0
  94. package/out-tsc/src/flow/nodes/wait_for_image.js +7 -0
  95. package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -0
  96. package/out-tsc/src/flow/nodes/wait_for_location.js +7 -0
  97. package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -0
  98. package/out-tsc/src/flow/nodes/wait_for_menu.js +7 -0
  99. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -0
  100. package/out-tsc/src/flow/nodes/wait_for_response.js +7 -0
  101. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -0
  102. package/out-tsc/src/flow/nodes/wait_for_video.js +7 -0
  103. package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -0
  104. package/out-tsc/src/flow/types.js +79 -0
  105. package/out-tsc/src/flow/types.js.map +1 -0
  106. package/out-tsc/src/flow/utils.js +65 -0
  107. package/out-tsc/src/flow/utils.js.map +1 -0
  108. package/out-tsc/src/form/ArrayEditor.js +199 -0
  109. package/out-tsc/src/form/ArrayEditor.js.map +1 -0
  110. package/out-tsc/src/form/BaseListEditor.js +128 -0
  111. package/out-tsc/src/form/BaseListEditor.js.map +1 -0
  112. package/out-tsc/src/form/Checkbox.js +17 -2
  113. package/out-tsc/src/form/Checkbox.js.map +1 -1
  114. package/out-tsc/src/form/Completion.js +6 -0
  115. package/out-tsc/src/form/Completion.js.map +1 -1
  116. package/out-tsc/src/form/FormField.js +110 -11
  117. package/out-tsc/src/form/FormField.js.map +1 -1
  118. package/out-tsc/src/form/KeyValueEditor.js +223 -0
  119. package/out-tsc/src/form/KeyValueEditor.js.map +1 -0
  120. package/out-tsc/src/form/select/Select.js +77 -32
  121. package/out-tsc/src/form/select/Select.js.map +1 -1
  122. package/out-tsc/src/interfaces.js +6 -0
  123. package/out-tsc/src/interfaces.js.map +1 -1
  124. package/out-tsc/src/live/ContactChat.js +2 -76
  125. package/out-tsc/src/live/ContactChat.js.map +1 -1
  126. package/out-tsc/temba-modules.js +9 -2
  127. package/out-tsc/temba-modules.js.map +1 -1
  128. package/out-tsc/test/ActionHelper.js +116 -0
  129. package/out-tsc/test/ActionHelper.js.map +1 -0
  130. package/out-tsc/test/actions/add_contact_groups.test.js +66 -0
  131. package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -0
  132. package/out-tsc/test/actions/remove_contact_groups.test.js +226 -0
  133. package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -0
  134. package/out-tsc/test/actions/send_email.test.js +160 -0
  135. package/out-tsc/test/actions/send_email.test.js.map +1 -0
  136. package/out-tsc/test/actions/send_msg.test.js +95 -0
  137. package/out-tsc/test/actions/send_msg.test.js.map +1 -0
  138. package/out-tsc/test/temba-action-editing-integration.test.js +183 -0
  139. package/out-tsc/test/temba-action-editing-integration.test.js.map +1 -0
  140. package/out-tsc/test/temba-checkbox.test.js +1 -1
  141. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  142. package/out-tsc/test/temba-field-config.test.js +133 -0
  143. package/out-tsc/test/temba-field-config.test.js.map +1 -0
  144. package/out-tsc/test/temba-flow-editor-node.test.js +14 -14
  145. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  146. package/out-tsc/test/temba-node-editor.test.js +283 -0
  147. package/out-tsc/test/temba-node-editor.test.js.map +1 -0
  148. package/out-tsc/test/temba-select.test.js +85 -0
  149. package/out-tsc/test/temba-select.test.js.map +1 -1
  150. package/package.json +1 -1
  151. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  152. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  153. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  154. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  155. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  156. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  157. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  158. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  159. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  160. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  161. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  162. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  163. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  164. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  165. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  166. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  167. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  168. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  169. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  170. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  171. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  172. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  173. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  174. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  175. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  176. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  177. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  178. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  179. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  180. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  181. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  182. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  183. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  184. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  185. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  186. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  187. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  188. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  189. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  190. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  191. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  192. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  193. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  194. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  195. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  196. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  197. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  198. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  199. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  200. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  201. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  202. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  203. package/screenshots/truth/editor/router.png +0 -0
  204. package/screenshots/truth/editor/send_msg.png +0 -0
  205. package/screenshots/truth/editor/set_contact_language.png +0 -0
  206. package/screenshots/truth/editor/set_contact_name.png +0 -0
  207. package/screenshots/truth/editor/set_run_result.png +0 -0
  208. package/screenshots/truth/editor/wait.png +0 -0
  209. package/screenshots/truth/formfield/markdown-errors.png +0 -0
  210. package/screenshots/truth/formfield/plain-text-errors.png +0 -0
  211. package/screenshots/truth/formfield/widget-only-markdown-errors.png +0 -0
  212. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
  213. package/src/events.ts +1 -40
  214. package/src/flow/{EditorNode.ts → CanvasNode.ts} +424 -48
  215. package/src/flow/Editor.ts +140 -4
  216. package/src/flow/NodeEditor.ts +1443 -0
  217. package/src/flow/Plumber.ts +0 -9
  218. package/src/flow/actions/add_contact_groups.ts +42 -0
  219. package/src/flow/actions/add_contact_urn.ts +17 -0
  220. package/src/flow/actions/add_input_labels.ts +12 -0
  221. package/src/flow/actions/call_classifier.ts +12 -0
  222. package/src/flow/actions/call_llm.ts +12 -0
  223. package/src/flow/actions/call_resthook.ts +12 -0
  224. package/src/flow/actions/call_webhook.ts +133 -0
  225. package/src/flow/actions/enter_flow.ts +15 -0
  226. package/src/flow/actions/open_ticket.ts +12 -0
  227. package/src/flow/actions/play_audio.ts +12 -0
  228. package/src/flow/actions/remove_contact_groups.ts +66 -0
  229. package/src/flow/actions/request_optin.ts +12 -0
  230. package/src/flow/actions/say_msg.ts +12 -0
  231. package/src/flow/actions/send_broadcast.ts +35 -0
  232. package/src/flow/actions/send_email.ts +60 -0
  233. package/src/flow/actions/send_msg.ts +58 -0
  234. package/src/flow/actions/set_contact_channel.ts +13 -0
  235. package/src/flow/actions/set_contact_field.ts +13 -0
  236. package/src/flow/actions/set_contact_language.ts +11 -0
  237. package/src/flow/actions/set_contact_name.ts +11 -0
  238. package/src/flow/actions/set_contact_status.ts +11 -0
  239. package/src/flow/actions/set_run_result.ts +11 -0
  240. package/src/flow/actions/split_by_expression_example.ts +88 -0
  241. package/src/flow/actions/start_session.ts +12 -0
  242. package/src/flow/actions/transfer_airtime.ts +12 -0
  243. package/src/flow/config.ts +93 -232
  244. package/src/flow/nodes/execute_actions.ts +5 -0
  245. package/src/flow/nodes/split_by_airtime.ts +9 -0
  246. package/src/flow/nodes/split_by_contact_field.ts +7 -0
  247. package/src/flow/nodes/split_by_expression.ts +7 -0
  248. package/src/flow/nodes/split_by_groups.ts +7 -0
  249. package/src/flow/nodes/split_by_random.ts +10 -0
  250. package/src/flow/nodes/split_by_run_result.ts +7 -0
  251. package/src/flow/nodes/split_by_scheme.ts +7 -0
  252. package/src/flow/nodes/split_by_subflow.ts +9 -0
  253. package/src/flow/nodes/split_by_webhook.ts +19 -0
  254. package/src/flow/nodes/wait_for_audio.ts +7 -0
  255. package/src/flow/nodes/wait_for_digits.ts +7 -0
  256. package/src/flow/nodes/wait_for_image.ts +7 -0
  257. package/src/flow/nodes/wait_for_location.ts +7 -0
  258. package/src/flow/nodes/wait_for_menu.ts +7 -0
  259. package/src/flow/nodes/wait_for_response.ts +7 -0
  260. package/src/flow/nodes/wait_for_video.ts +7 -0
  261. package/src/flow/types.ts +352 -0
  262. package/src/flow/utils.ts +76 -0
  263. package/src/form/ArrayEditor.ts +240 -0
  264. package/src/form/BaseListEditor.ts +177 -0
  265. package/src/form/Checkbox.ts +22 -3
  266. package/src/form/Completion.ts +6 -0
  267. package/src/form/FormField.ts +115 -11
  268. package/src/form/KeyValueEditor.ts +251 -0
  269. package/src/form/select/Select.ts +89 -32
  270. package/src/interfaces.ts +7 -2
  271. package/src/live/ContactChat.ts +3 -97
  272. package/src/store/flow-definition.d.ts +6 -1
  273. package/static/api/contacts.json +30 -0
  274. package/static/api/groups.json +4 -426
  275. package/static/api/locations.json +24 -0
  276. package/static/api/media.json +5 -0
  277. package/static/api/optins.json +16 -0
  278. package/static/api/orgs.json +13 -0
  279. package/static/api/topics.json +21 -0
  280. package/static/api/users.json +26 -0
  281. package/static/css/temba-components.css +3 -6
  282. package/temba-modules.ts +9 -2
  283. package/test/ActionHelper.ts +142 -0
  284. package/test/actions/add_contact_groups.test.ts +89 -0
  285. package/test/actions/remove_contact_groups.test.ts +265 -0
  286. package/test/actions/send_email.test.ts +214 -0
  287. package/test/actions/send_msg.test.ts +130 -0
  288. package/test/temba-action-editing-integration.test.ts +240 -0
  289. package/test/temba-checkbox.test.ts +1 -1
  290. package/test/temba-field-config.test.ts +152 -0
  291. package/test/temba-flow-editor-node.test.ts +18 -18
  292. package/test/temba-node-editor.test.ts +353 -0
  293. package/test/temba-select.test.ts +127 -0
  294. package/test-assets/contacts/history.json +11 -33
  295. package/web-dev-server.config.mjs +34 -0
  296. package/.github/workflows/coverage.yml +0 -80
  297. package/demo/sticky-note-demo.html +0 -155
  298. package/out-tsc/src/flow/EditorNode.js.map +0 -1
  299. package/out-tsc/src/flow/render.js +0 -358
  300. package/out-tsc/src/flow/render.js.map +0 -1
  301. package/out-tsc/test/temba-flow-render.test.js +0 -794
  302. package/out-tsc/test/temba-flow-render.test.js.map +0 -1
  303. package/src/flow/render.ts +0 -443
  304. package/test/temba-flow-render.test.ts +0 -1003
@@ -55,6 +55,10 @@ export class Select<T extends SelectOption> extends FormElement {
55
55
  --temba-options-font-size: var(--temba-select-selected-font-size);
56
56
  --icon-color: var(--color-text-dark);
57
57
  --color-options-bg: #fff;
58
+ /* Always use normal border colors for options popup, even when select is in error state */
59
+ --color-widget-border: #ddd;
60
+ --color-focus: #007bff;
61
+ --widget-box-shadow-focused: 0 0 0 3px rgba(0, 123, 255, 0.25);
58
62
  }
59
63
 
60
64
  :host:focus {
@@ -448,6 +452,9 @@ export class Select<T extends SelectOption> extends FormElement {
448
452
  @property({ type: Boolean })
449
453
  tags = false;
450
454
 
455
+ @property({ type: Boolean })
456
+ emails = false;
457
+
451
458
  @property({ type: Boolean, attribute: 'space_select' })
452
459
  spaceSelect: boolean;
453
460
 
@@ -599,7 +606,7 @@ export class Select<T extends SelectOption> extends FormElement {
599
606
 
600
607
  this.staticOptions.push(option);
601
608
  if (selected) {
602
- if (this.multi) {
609
+ if (this.isMultiMode) {
603
610
  this.addValue(option);
604
611
  } else {
605
612
  this.setValues([option]);
@@ -855,7 +862,7 @@ export class Select<T extends SelectOption> extends FormElement {
855
862
  const name = this.getAttribute('name');
856
863
 
857
864
  if (name) {
858
- if (!this.multi && this.values.length === 1) {
865
+ if (!this.isMultiMode && this.values.length === 1) {
859
866
  this.selection = this.values[0];
860
867
  this.value = this.serializeValue(this.values[0]);
861
868
  } else {
@@ -875,13 +882,13 @@ export class Select<T extends SelectOption> extends FormElement {
875
882
  }
876
883
 
877
884
  private setSelectedOption(option: any) {
878
- if (this.multi) {
885
+ if (this.isMultiMode) {
879
886
  this.addValue(option);
880
887
  } else {
881
888
  this.setValues([option]);
882
889
  }
883
890
 
884
- if (!this.multi || !this.searchable) {
891
+ if (!this.isMultiMode || !this.searchable) {
885
892
  this.blur();
886
893
  this.focused = false;
887
894
  }
@@ -904,7 +911,7 @@ export class Select<T extends SelectOption> extends FormElement {
904
911
 
905
912
  public handleOptionSelection(event: CustomEvent) {
906
913
  if (
907
- this.multi &&
914
+ this.isMultiMode &&
908
915
  this.maxItems > 0 &&
909
916
  this.values.length >= this.maxItems
910
917
  ) {
@@ -969,10 +976,27 @@ export class Select<T extends SelectOption> extends FormElement {
969
976
  this.visibleOptions = [];
970
977
  }
971
978
 
972
- private createArbitraryOptionDefault(): any {
979
+ private createArbitraryOptionDefault(input: string, _options: any[]): any {
980
+ if (this.emails && input && this.isValidEmail(input)) {
981
+ return { name: input, value: input };
982
+ }
983
+ if (this.tags && input) {
984
+ return { name: input, value: input };
985
+ }
973
986
  return null;
974
987
  }
975
988
 
989
+ private isValidEmail(email: string): boolean {
990
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
991
+ return emailRegex.test(email);
992
+ }
993
+
994
+ // Helper method to determine if this select should behave as multi-select
995
+ // Returns true if multi is explicitly set OR if emails mode is enabled
996
+ private get isMultiMode(): boolean {
997
+ return this.multi || this.emails;
998
+ }
999
+
976
1000
  public open(): void {
977
1001
  (
978
1002
  this.shadowRoot.querySelector('.select-container') as HTMLDivElement
@@ -1042,7 +1066,7 @@ export class Select<T extends SelectOption> extends FormElement {
1042
1066
  // filter out any options already selected by id
1043
1067
  // TODO: should maybe be doing a deep equals here with option to optimize
1044
1068
  if (this.values.length > 0) {
1045
- if (this.multi) {
1069
+ if (this.isMultiMode) {
1046
1070
  options = options.filter(
1047
1071
  (option) =>
1048
1072
  !this.values.find(
@@ -1063,7 +1087,7 @@ export class Select<T extends SelectOption> extends FormElement {
1063
1087
  }
1064
1088
 
1065
1089
  if (
1066
- this.multi &&
1090
+ this.isMultiMode &&
1067
1091
  this.maxItems > 0 &&
1068
1092
  this.values.length >= this.maxItems
1069
1093
  ) {
@@ -1123,6 +1147,18 @@ export class Select<T extends SelectOption> extends FormElement {
1123
1147
  }
1124
1148
  }
1125
1149
 
1150
+ if (this.emails && q) {
1151
+ if (
1152
+ this.isValidEmail(q) &&
1153
+ !options.find(
1154
+ (option: any) =>
1155
+ this.getValue(option) && this.getValue(option).toLowerCase() === q
1156
+ )
1157
+ ) {
1158
+ options.splice(0, 0, { name: query, value: query });
1159
+ }
1160
+ }
1161
+
1126
1162
  if (this.endpoint) {
1127
1163
  let url = this.endpoint;
1128
1164
  if (next) {
@@ -1149,7 +1185,7 @@ export class Select<T extends SelectOption> extends FormElement {
1149
1185
  }
1150
1186
 
1151
1187
  const cache = this.lruCache.get(url);
1152
- if (this.cache && !this.tags && cache) {
1188
+ if (this.cache && !this.tags && !this.emails && cache) {
1153
1189
  if (page === 0 && !this.next) {
1154
1190
  this.cursorIndex = 0;
1155
1191
  this.setVisibleOptions([...options, ...cache.options]);
@@ -1168,7 +1204,7 @@ export class Select<T extends SelectOption> extends FormElement {
1168
1204
  fetchResults(url).then((results: any) => {
1169
1205
  results = this.prepareOptions(results);
1170
1206
 
1171
- if (this.cache && !this.tags) {
1207
+ if (this.cache && !this.tags && !this.emails) {
1172
1208
  this.lruCache.set(url, {
1173
1209
  options: results,
1174
1210
  complete: true,
@@ -1207,7 +1243,7 @@ export class Select<T extends SelectOption> extends FormElement {
1207
1243
  this.complete = this.isComplete(results, response);
1208
1244
  }
1209
1245
 
1210
- if (this.cache && !this.tags) {
1246
+ if (this.cache && !this.tags && !this.emails) {
1211
1247
  this.lruCache.set(url, {
1212
1248
  options: results,
1213
1249
  complete: this.complete,
@@ -1249,7 +1285,7 @@ export class Select<T extends SelectOption> extends FormElement {
1249
1285
  }
1250
1286
 
1251
1287
  if (
1252
- this.multi &&
1288
+ this.isMultiMode &&
1253
1289
  this.maxItems > 0 &&
1254
1290
  this.values.length >= this.maxItems
1255
1291
  ) {
@@ -1270,7 +1306,7 @@ export class Select<T extends SelectOption> extends FormElement {
1270
1306
  expression: true
1271
1307
  };
1272
1308
 
1273
- if (this.multi) {
1309
+ if (this.isMultiMode) {
1274
1310
  if (
1275
1311
  !this.values.find((option: T) => {
1276
1312
  return (
@@ -1289,7 +1325,7 @@ export class Select<T extends SelectOption> extends FormElement {
1289
1325
  }
1290
1326
 
1291
1327
  this.input = '';
1292
- if (!this.multi) {
1328
+ if (!this.isMultiMode) {
1293
1329
  this.blur();
1294
1330
  }
1295
1331
  }
@@ -1303,6 +1339,21 @@ export class Select<T extends SelectOption> extends FormElement {
1303
1339
  this.input.indexOf('@') > -1
1304
1340
  ) {
1305
1341
  this.addInputAsValue();
1342
+ return;
1343
+ }
1344
+
1345
+ // if we are in email mode and have a valid email, add it
1346
+ if (
1347
+ evt.key === 'Enter' &&
1348
+ this.emails &&
1349
+ this.input &&
1350
+ this.isValidEmail(this.input.trim()) &&
1351
+ this.visibleOptions.length === 0
1352
+ ) {
1353
+ evt.preventDefault();
1354
+ const emailOption = { name: this.input.trim(), value: this.input.trim() };
1355
+ this.setSelectedOption(emailOption);
1356
+ return;
1306
1357
  }
1307
1358
 
1308
1359
  // see if we should open our options on a key event
@@ -1323,7 +1374,7 @@ export class Select<T extends SelectOption> extends FormElement {
1323
1374
  }
1324
1375
 
1325
1376
  // focus our last item on delete
1326
- if (this.multi && evt.key === 'Backspace' && !this.input) {
1377
+ if (this.isMultiMode && evt.key === 'Backspace' && !this.input) {
1327
1378
  if (this.visibleOptions.length > 0) {
1328
1379
  this.visibleOptions = [];
1329
1380
  return;
@@ -1447,7 +1498,10 @@ export class Select<T extends SelectOption> extends FormElement {
1447
1498
 
1448
1499
  public serializeValue(value: any): string {
1449
1500
  // static options just use their value
1450
- if (!this.jsonValue && (this.staticOptions.length > 0 || this.tags)) {
1501
+ if (
1502
+ !this.jsonValue &&
1503
+ (this.staticOptions.length > 0 || this.isMultiMode)
1504
+ ) {
1451
1505
  return value.value;
1452
1506
  }
1453
1507
 
@@ -1487,7 +1541,7 @@ export class Select<T extends SelectOption> extends FormElement {
1487
1541
 
1488
1542
  public addValue(value: any) {
1489
1543
  const oldValues = [...this.values];
1490
- this.values.push(value);
1544
+ this.values = [...this.values, value];
1491
1545
  this.requestUpdate('values', oldValues);
1492
1546
  }
1493
1547
 
@@ -1495,7 +1549,7 @@ export class Select<T extends SelectOption> extends FormElement {
1495
1549
  const oldValues = [...this.values];
1496
1550
  const idx = this.values.indexOf(valueToRemove);
1497
1551
  if (idx > -1) {
1498
- this.values.splice(idx, 1);
1552
+ this.values = this.values.filter((_, index) => index !== idx);
1499
1553
 
1500
1554
  // Also remove the 'selected' attribute from the corresponding temba-option element
1501
1555
  const valueToMatch = this.getValue(valueToRemove);
@@ -1515,7 +1569,7 @@ export class Select<T extends SelectOption> extends FormElement {
1515
1569
 
1516
1570
  public popValue() {
1517
1571
  const oldValues = [...this.values];
1518
- this.values.pop();
1572
+ this.values = this.values.slice(0, -1);
1519
1573
  this.requestUpdate('values', oldValues);
1520
1574
  this.infoText = '';
1521
1575
  }
@@ -1553,9 +1607,12 @@ export class Select<T extends SelectOption> extends FormElement {
1553
1607
  toIdx < this.values.length
1554
1608
  ) {
1555
1609
  const oldValues = [...this.values];
1556
- // Move the item from fromIdx to toIdx
1557
- const movedItem = this.values.splice(fromIdx, 1)[0];
1558
- this.values.splice(toIdx, 0, movedItem);
1610
+ // Create a new array with the moved item
1611
+ const newValues = [...this.values];
1612
+ const movedItem = newValues[fromIdx];
1613
+ newValues.splice(fromIdx, 1);
1614
+ newValues.splice(toIdx, 0, movedItem);
1615
+ this.values = newValues;
1559
1616
  this.requestUpdate('values', oldValues);
1560
1617
  }
1561
1618
  }
@@ -1568,7 +1625,7 @@ export class Select<T extends SelectOption> extends FormElement {
1568
1625
  `;
1569
1626
 
1570
1627
  const clear =
1571
- this.clearable && this.values.length > 0 && !this.multi
1628
+ this.clearable && this.values.length > 0 && !this.isMultiMode
1572
1629
  ? html`<temba-icon
1573
1630
  name="${Icon.select_clear}"
1574
1631
  size="1.1"
@@ -1578,8 +1635,8 @@ export class Select<T extends SelectOption> extends FormElement {
1578
1635
  : null;
1579
1636
 
1580
1637
  const classes = getClasses({
1581
- multi: this.multi,
1582
- single: !this.multi,
1638
+ multi: this.isMultiMode,
1639
+ single: !this.isMultiMode,
1583
1640
  searchable: this.searchable,
1584
1641
  empty: this.values.length === 0,
1585
1642
  options: this.visibleOptions.length > 0,
@@ -1615,8 +1672,8 @@ export class Select<T extends SelectOption> extends FormElement {
1615
1672
  `
1616
1673
  : placeholderDiv;
1617
1674
 
1618
- const items = html`${!this.multi && !this.resolving ? input : null}
1619
- ${this.multi && this.values.length > 1
1675
+ const items = html`${!this.isMultiMode && !this.resolving ? input : null}
1676
+ ${this.isMultiMode && this.values.length > 1
1620
1677
  ? html`
1621
1678
  <temba-sortable-list
1622
1679
  horizontal
@@ -1657,7 +1714,7 @@ export class Select<T extends SelectOption> extends FormElement {
1657
1714
  : ''}
1658
1715
  "
1659
1716
  >
1660
- ${this.multi
1717
+ ${this.isMultiMode
1661
1718
  ? html`
1662
1719
  <div
1663
1720
  class="remove-item"
@@ -1706,7 +1763,7 @@ export class Select<T extends SelectOption> extends FormElement {
1706
1763
  : ''}
1707
1764
  "
1708
1765
  >
1709
- ${this.multi
1766
+ ${this.isMultiMode
1710
1767
  ? html`
1711
1768
  <div
1712
1769
  class="remove-item"
@@ -1732,13 +1789,13 @@ export class Select<T extends SelectOption> extends FormElement {
1732
1789
  </div>
1733
1790
  `
1734
1791
  : null}
1735
- ${!this.input || this.multi
1792
+ ${!this.input || this.isMultiMode
1736
1793
  ? this.renderSelectedItem(selected)
1737
1794
  : null}
1738
1795
  </div>
1739
1796
  `
1740
1797
  )}
1741
- ${this.multi ? input : null}`;
1798
+ ${this.isMultiMode ? input : null}`;
1742
1799
 
1743
1800
  return html`
1744
1801
 
@@ -1778,7 +1835,7 @@ export class Select<T extends SelectOption> extends FormElement {
1778
1835
 
1779
1836
  <slot name="right"></slot>
1780
1837
  ${
1781
- !this.tags
1838
+ !this.tags && !this.emails
1782
1839
  ? html`<div
1783
1840
  class="right-side arrow"
1784
1841
  style="display:block;margin-right:5px"
package/src/interfaces.ts CHANGED
@@ -100,7 +100,6 @@ export interface Msg {
100
100
  channel: ObjectReference;
101
101
  quick_replies: string[];
102
102
  urn: string;
103
- id: number;
104
103
  direction: string;
105
104
  type: string;
106
105
  attachments: string[];
@@ -287,5 +286,11 @@ export enum CustomEventType {
287
286
  TicketUpdated = 'temba-ticket-updated',
288
287
  Moved = 'temba-moved',
289
288
  DateRangeChanged = 'temba-date-range-changed',
290
- NodeDeleted = 'temba-node-deleted'
289
+ NodeDeleted = 'temba-node-deleted',
290
+ ActionEditRequested = 'temba-action-edit-requested',
291
+ ActionSaved = 'temba-action-saved',
292
+ ActionEditCanceled = 'temba-action-edit-canceled',
293
+ NodeEditRequested = 'temba-node-edit-requested',
294
+ NodeSaved = 'temba-node-saved',
295
+ NodeEditCancelled = 'temba-node-edit-cancelled'
291
296
  }
@@ -11,7 +11,6 @@ import {
11
11
  import {
12
12
  fetchResults,
13
13
  getUrl,
14
- oxford,
15
14
  oxfordFn,
16
15
  postJSON,
17
16
  postUrl,
@@ -21,24 +20,18 @@ import { ContactStoreElement } from './ContactStoreElement';
21
20
  import { Compose, ComposeValue } from '../form/Compose';
22
21
  import {
23
22
  AirtimeTransferredEvent,
24
- CampaignFiredEvent,
25
23
  ChannelEvent,
26
24
  ContactEvent,
27
25
  ContactGroupsEvent,
28
26
  ContactHistoryPage,
29
27
  ContactLanguageChangedEvent,
30
- EmailSentEvent,
31
- ErrorMessageEvent,
32
28
  FlowEvent,
33
- LabelsAddedEvent,
34
29
  MsgEvent,
35
30
  NameChangedEvent,
36
31
  OptinRequestedEvent,
37
32
  TicketEvent,
38
33
  UpdateFieldEvent,
39
- UpdateResultEvent,
40
- URNsChangedEvent,
41
- WebhookEvent
34
+ URNsChangedEvent
42
35
  } from '../events';
43
36
  import { Chat, ChatEvent, MessageType } from '../display/Chat';
44
37
  import { getUserDisplay } from '../webchat';
@@ -63,19 +56,14 @@ export enum Events {
63
56
  FLOW_ENTERED = 'flow_entered',
64
57
 
65
58
  FLOW_EXITED = 'flow_exited',
66
- RUN_RESULT_CHANGED = 'run_result_changed',
67
59
  CONTACT_FIELD_CHANGED = 'contact_field_changed',
68
60
  CONTACT_GROUPS_CHANGED = 'contact_groups_changed',
69
61
  CONTACT_NAME_CHANGED = 'contact_name_changed',
70
62
  CONTACT_URNS_CHANGED = 'contact_urns_changed',
71
- CAMPAIGN_FIRED = 'campaign_fired',
72
63
  CHANNEL_EVENT = 'channel_event',
73
64
  CONTACT_LANGUAGE_CHANGED = 'contact_language_changed',
74
- WEBHOOK_CALLED = 'webhook_called',
75
65
  AIRTIME_TRANSFERRED = 'airtime_transferred',
76
66
  CALL_STARTED = 'call_started',
77
- EMAIL_SENT = 'email_sent',
78
- INPUT_LABELS_ADDED = 'input_labels_added',
79
67
  NOTE_CREATED = 'note_created',
80
68
  TICKET_ASSIGNED = 'ticket_assigned',
81
69
  TICKET_NOTE_ADDED = 'ticket_note_added',
@@ -83,9 +71,7 @@ export enum Events {
83
71
  TICKET_OPENED = 'ticket_opened',
84
72
  TICKET_REOPENED = 'ticket_reopened',
85
73
  TICKET_TOPIC_CHANGED = 'ticket_topic_changed',
86
- OPTIN_REQUESTED = 'optin_requested',
87
- ERROR = 'error',
88
- FAILURE = 'failure'
74
+ OPTIN_REQUESTED = 'optin_requested'
89
75
  }
90
76
 
91
77
  const renderInfoList = (singular: string, plural: string, items: any[]) => {
@@ -102,10 +88,6 @@ const renderInfoList = (singular: string, plural: string, items: any[]) => {
102
88
  }
103
89
  };
104
90
 
105
- const toTitleCase = (str: string) => {
106
- return str.charAt(0).toUpperCase() + str.slice(1);
107
- };
108
-
109
91
  const renderChannelEvent = (event: ChannelEvent): string => {
110
92
  if (event.event.type === 'mt_miss') {
111
93
  return 'Missed outgoing call';
@@ -144,12 +126,6 @@ const renderFlowEvent = (event: FlowEvent): string => {
144
126
  return `${verb} [**${event.flow.name}**](/flow/editor/${event.flow.uuid}/)`;
145
127
  };
146
128
 
147
- const renderResultEvent = (event: UpdateResultEvent): string => {
148
- if (!event.name.startsWith('_') && event.value) {
149
- return `Updated flow result **${event.name}** to **${event.value}**`;
150
- }
151
- };
152
-
153
129
  const renderUpdateEvent = (event: UpdateFieldEvent): string => {
154
130
  return event.value
155
131
  ? `Updated **${event.field.name}** to **${event.value.text}**`
@@ -167,16 +143,6 @@ const renderContactURNsChanged = (event: URNsChangedEvent): string => {
167
143
  )}`;
168
144
  };
169
145
 
170
- const renderEmailSent = (event: EmailSentEvent): string => {
171
- return `Email sent to **${oxford(event.to, 'and')}** with subject **${
172
- event.subject
173
- }**`;
174
- };
175
-
176
- const renderLabelsAdded = (event: LabelsAddedEvent): string => {
177
- return `Applied ${renderInfoList('label', 'labels', event.labels)}`;
178
- };
179
-
180
146
  export const renderTicketAction = (
181
147
  event: TicketEvent,
182
148
  action: string
@@ -216,31 +182,10 @@ export const renderContactGroupsEvent = (event: ContactGroupsEvent): string => {
216
182
  }
217
183
  };
218
184
 
219
- export const renderCampaignFiredEvent = (event: CampaignFiredEvent): string => {
220
- return `Campaign ${event.campaign.name}
221
- ${event.fired_result === 'S' ? 'skipped' : 'triggered'}
222
- ${event.campaign_event.offset_display}
223
- ${event.campaign_event.relative_to.name}`;
224
- };
225
-
226
185
  export const renderTicketOpened = (event: TicketEvent): string => {
227
186
  return `${event.ticket.topic.name} ticket was opened`;
228
187
  };
229
188
 
230
- export const renderErrorMessage = (event: ErrorMessageEvent): string => {
231
- return `${event.text} ${
232
- event.type === Events.FAILURE
233
- ? `Run ended prematurely, check the flow design`
234
- : null
235
- }`;
236
- };
237
-
238
- export const renderWebhookEvent = (event: WebhookEvent): string => {
239
- return event.status === 'success'
240
- ? `Successfully called ${event.url}`
241
- : `Failed to call ${event.url}`;
242
- };
243
-
244
189
  export const renderAirtimeTransferredEvent = (
245
190
  event: AirtimeTransferredEvent
246
191
  ): string => {
@@ -656,15 +601,6 @@ export class ContactChat extends ContactStoreElement {
656
601
  public getEventMessage(event: ContactEvent): ChatEvent {
657
602
  let message = null;
658
603
  switch (event.type) {
659
- case Events.ERROR:
660
- case Events.FAILURE:
661
- message = {
662
- type: MessageType.Inline,
663
- text: `Error during flow: ${toTitleCase(
664
- (event as ErrorMessageEvent).text
665
- )}`
666
- };
667
- break;
668
604
  case Events.TICKET_OPENED:
669
605
  message = {
670
606
  type: MessageType.Inline,
@@ -702,12 +638,6 @@ export class ContactChat extends ContactStoreElement {
702
638
  text: renderFlowEvent(event as FlowEvent)
703
639
  };
704
640
  break;
705
- case Events.RUN_RESULT_CHANGED:
706
- message = {
707
- type: MessageType.Inline,
708
- text: renderResultEvent(event as UpdateResultEvent)
709
- };
710
- break;
711
641
  case Events.CONTACT_FIELD_CHANGED:
712
642
  message = {
713
643
  type: MessageType.Inline,
@@ -726,30 +656,12 @@ export class ContactChat extends ContactStoreElement {
726
656
  text: renderContactURNsChanged(event as URNsChangedEvent)
727
657
  };
728
658
  break;
729
- case Events.EMAIL_SENT:
730
- message = {
731
- type: MessageType.Inline,
732
- text: renderEmailSent(event as EmailSentEvent)
733
- };
734
- break;
735
- case Events.INPUT_LABELS_ADDED:
736
- message = {
737
- type: MessageType.Inline,
738
- text: renderLabelsAdded(event as LabelsAddedEvent)
739
- };
740
- break;
741
659
  case Events.CONTACT_GROUPS_CHANGED:
742
660
  message = {
743
661
  type: MessageType.Inline,
744
662
  text: renderContactGroupsEvent(event as ContactGroupsEvent)
745
663
  };
746
664
  break;
747
- case Events.WEBHOOK_CALLED:
748
- message = {
749
- type: MessageType.Inline,
750
- text: renderWebhookEvent(event as WebhookEvent)
751
- };
752
- break;
753
665
  case Events.AIRTIME_TRANSFERRED:
754
666
  message = {
755
667
  type: MessageType.Inline,
@@ -762,12 +674,6 @@ export class ContactChat extends ContactStoreElement {
762
674
  text: renderCallStartedEvent()
763
675
  };
764
676
  break;
765
- case Events.CAMPAIGN_FIRED:
766
- message = {
767
- type: MessageType.Inline,
768
- text: renderCampaignFiredEvent(event as CampaignFiredEvent)
769
- };
770
- break;
771
677
  case Events.CHANNEL_EVENT:
772
678
  message = {
773
679
  type: MessageType.Inline,
@@ -853,8 +759,8 @@ export class ContactChat extends ContactStoreElement {
853
759
  ) {
854
760
  const msgEvent = event as MsgEvent;
855
761
  messages.push({
762
+ uuid: event.uuid,
856
763
  type: msgEvent.type === 'msg_received' ? 'msg_in' : 'msg_out',
857
- id: msgEvent.msg.id + '',
858
764
  user: this.getUserForEvent(msgEvent),
859
765
  date: new Date(msgEvent.created_on),
860
766
  attachments: msgEvent.msg.attachments,
@@ -76,7 +76,7 @@ export interface Group extends NamedObject {
76
76
 
77
77
  export interface SendMsg extends Action {
78
78
  text: string;
79
- quick_replies: string[];
79
+ quick_replies?: string[];
80
80
  }
81
81
 
82
82
  export interface SetRunResult extends Action {
@@ -91,6 +91,10 @@ export interface SetContactName extends Action {
91
91
 
92
92
  export interface CallWebhook extends Action {
93
93
  url: string;
94
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE';
95
+ headers?: Record<string, string>;
96
+ body?: string;
97
+ result_name?: string;
94
98
  }
95
99
 
96
100
  export interface AddToGroup extends Action {
@@ -99,6 +103,7 @@ export interface AddToGroup extends Action {
99
103
 
100
104
  export interface RemoveFromGroup extends Action {
101
105
  groups: Group[];
106
+ all_groups?: boolean;
102
107
  }
103
108
 
104
109
  export interface SetContactField extends Action {
@@ -0,0 +1,30 @@
1
+ {
2
+ "next": null,
3
+ "previous": null,
4
+ "results": [
5
+ {
6
+ "uuid": "contact-1",
7
+ "name": "John Doe",
8
+ "language": "eng",
9
+ "urns": ["tel:+1234567890"],
10
+ "fields": {},
11
+ "groups": [],
12
+ "blocked": false,
13
+ "stopped": false,
14
+ "created_on": "2024-01-01T00:00:00Z",
15
+ "modified_on": "2024-01-01T00:00:00Z"
16
+ },
17
+ {
18
+ "uuid": "contact-2",
19
+ "name": "Jane Smith",
20
+ "language": "eng",
21
+ "urns": ["tel:+0987654321"],
22
+ "fields": {},
23
+ "groups": [],
24
+ "blocked": false,
25
+ "stopped": false,
26
+ "created_on": "2024-01-01T00:00:00Z",
27
+ "modified_on": "2024-01-01T00:00:00Z"
28
+ }
29
+ ]
30
+ }