@nyaruka/temba-components 0.131.0 → 0.131.2
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.
- package/.github/workflows/publish.yml +4 -1
- package/CHANGELOG.md +67 -1
- package/demo/data/flows/food-order.json +2 -2
- package/demo/data/flows/sample-flow.json +74 -125
- package/dist/static/svg/index.svg +1 -1
- package/dist/temba-components.js +1156 -619
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/Icons.js +4 -1
- package/out-tsc/src/Icons.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/CanvasMenu.js +200 -0
- package/out-tsc/src/flow/CanvasMenu.js.map +1 -0
- package/out-tsc/src/flow/CanvasNode.js +327 -19
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +562 -66
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +240 -93
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/NodeTypeSelector.js +499 -0
- package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -0
- package/out-tsc/src/flow/actions/add_contact_groups.js +3 -3
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_urn.js +62 -4
- package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +3 -3
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/play_audio.js +2 -2
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -5
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/request_optin.js +2 -2
- package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +2 -2
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/send_broadcast.js +76 -23
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
- package/out-tsc/src/flow/actions/send_email.js +4 -5
- package/out-tsc/src/flow/actions/send_email.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +9 -19
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js +5 -9
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_field.js +19 -20
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_language.js +2 -2
- package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_name.js +2 -12
- package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_status.js +2 -2
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
- package/out-tsc/src/flow/actions/set_run_result.js +3 -3
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/actions/start_session.js +180 -6
- package/out-tsc/src/flow/actions/start_session.js.map +1 -1
- package/out-tsc/src/flow/config.js +11 -15
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/currencies.js +45 -0
- package/out-tsc/src/flow/currencies.js.map +1 -0
- package/out-tsc/src/flow/nodes/shared-rules.js +257 -0
- package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -0
- package/out-tsc/src/flow/nodes/shared.js +17 -0
- package/out-tsc/src/flow/nodes/shared.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_airtime.js +205 -5
- package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_contact_field.js +147 -3
- package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_expression.js +68 -2
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_groups.js +12 -9
- package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_intent.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_llm.js +3 -2
- package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +2 -2
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_random.js +3 -3
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_resthook.js +108 -0
- package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_run_result.js +206 -3
- package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_scheme.js +153 -2
- package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_subflow.js +6 -4
- package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_ticket.js +3 -2
- package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_webhook.js +3 -2
- package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_audio.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_image.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_location.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +32 -567
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_video.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -1
- package/out-tsc/src/flow/types.js +71 -12
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/flow/utils.js +101 -14
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/form/ContactSearch.js +1 -1
- package/out-tsc/src/form/ContactSearch.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +2 -4
- package/out-tsc/src/form/FieldRenderer.js.map +1 -1
- package/out-tsc/src/interfaces.js +3 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +98 -33
- package/out-tsc/src/list/SortableList.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +15 -18
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/store/AppState.js +53 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/utils.js +254 -13
- package/out-tsc/src/utils.js.map +1 -1
- package/out-tsc/temba-modules.js +4 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/ActionHelper.js +3 -3
- package/out-tsc/test/ActionHelper.js.map +1 -1
- package/out-tsc/test/NodeHelper.js +6 -3
- package/out-tsc/test/NodeHelper.js.map +1 -1
- package/out-tsc/test/actions/add_contact_urn.test.js +202 -0
- package/out-tsc/test/actions/add_contact_urn.test.js.map +1 -0
- package/out-tsc/test/actions/send_broadcast.test.js +148 -0
- package/out-tsc/test/actions/send_broadcast.test.js.map +1 -0
- package/out-tsc/test/actions/send_email.test.js +17 -23
- package/out-tsc/test/actions/send_email.test.js.map +1 -1
- package/out-tsc/test/actions/send_msg.test.js +33 -15
- package/out-tsc/test/actions/send_msg.test.js.map +1 -1
- package/out-tsc/test/actions/start_session.test.js +116 -0
- package/out-tsc/test/actions/start_session.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_airtime.test.js +604 -0
- package/out-tsc/test/nodes/split_by_airtime.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_contact_field.test.js +387 -0
- package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_expression.test.js +614 -0
- package/out-tsc/test/nodes/split_by_expression.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_random.test.js +3 -3
- package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_resthook.test.js +337 -0
- package/out-tsc/test/nodes/split_by_resthook.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_run_result.test.js +920 -0
- package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_scheme.test.js +399 -0
- package/out-tsc/test/nodes/split_by_scheme.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_subflow.test.js +333 -0
- package/out-tsc/test/nodes/split_by_subflow.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +2 -2
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
- package/out-tsc/test/nodes/wait_for_response.test.js +2 -1
- package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
- package/out-tsc/test/temba-action-drag-between-nodes.test.js +252 -0
- package/out-tsc/test/temba-action-drag-between-nodes.test.js.map +1 -0
- package/out-tsc/test/temba-canvas-menu.test.js +122 -0
- package/out-tsc/test/temba-canvas-menu.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +85 -2
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor.test.js +7 -8
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-node-editor.test.js +3 -1
- package/out-tsc/test/temba-node-editor.test.js.map +1 -1
- package/out-tsc/test/temba-node-type-selector.test.js +115 -0
- package/out-tsc/test/temba-node-type-selector.test.js.map +1 -0
- package/out-tsc/test/temba-omnibox.test.js +2 -1
- package/out-tsc/test/temba-omnibox.test.js.map +1 -1
- package/out-tsc/test/temba-sortable-list.test.js +51 -0
- package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
- package/out-tsc/test/temba-utils-index.test.js +1 -27
- package/out-tsc/test/temba-utils-index.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +2 -0
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +2 -1
- package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
- package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
- package/screenshots/truth/actions/start_session/editor/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
- package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
- package/screenshots/truth/canvas-menu/open.png +0 -0
- package/screenshots/truth/editor/router.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/list/fields-dragging.png +0 -0
- package/screenshots/truth/list/sortable-dragging.png +0 -0
- package/screenshots/truth/node-type-selector/action-mode.png +0 -0
- package/screenshots/truth/node-type-selector/split-mode.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/src/Icons.ts +4 -1
- package/src/events.ts +2 -6
- package/src/flow/CanvasMenu.ts +217 -0
- package/src/flow/CanvasNode.ts +408 -10
- package/src/flow/Editor.ts +683 -44
- package/src/flow/NodeEditor.ts +304 -125
- package/src/flow/NodeTypeSelector.ts +592 -0
- package/src/flow/actions/add_contact_groups.ts +4 -4
- package/src/flow/actions/add_contact_urn.ts +76 -4
- package/src/flow/actions/add_input_labels.ts +4 -4
- package/src/flow/actions/play_audio.ts +2 -2
- package/src/flow/actions/remove_contact_groups.ts +14 -6
- package/src/flow/actions/request_optin.ts +2 -2
- package/src/flow/actions/say_msg.ts +2 -2
- package/src/flow/actions/send_broadcast.ts +85 -23
- package/src/flow/actions/send_email.ts +10 -6
- package/src/flow/actions/send_msg.ts +22 -32
- package/src/flow/actions/set_contact_channel.ts +5 -11
- package/src/flow/actions/set_contact_field.ts +20 -25
- package/src/flow/actions/set_contact_language.ts +9 -4
- package/src/flow/actions/set_contact_name.ts +3 -15
- package/src/flow/actions/set_contact_status.ts +3 -3
- package/src/flow/actions/set_run_result.ts +4 -4
- package/src/flow/actions/start_session.ts +208 -6
- package/src/flow/config.ts +13 -15
- package/src/flow/currencies.ts +51 -0
- package/src/flow/nodes/shared-rules.ts +301 -0
- package/src/flow/nodes/shared.ts +18 -0
- package/src/flow/nodes/split_by_airtime.ts +238 -5
- package/src/flow/nodes/split_by_contact_field.ts +185 -3
- package/src/flow/nodes/split_by_expression.ts +94 -2
- package/src/flow/nodes/split_by_groups.ts +15 -10
- package/src/flow/nodes/split_by_intent.ts +7 -0
- package/src/flow/nodes/split_by_llm.ts +4 -3
- package/src/flow/nodes/split_by_llm_categorize.ts +4 -4
- package/src/flow/nodes/split_by_random.ts +5 -5
- package/src/flow/nodes/split_by_resthook.ts +130 -0
- package/src/flow/nodes/split_by_run_result.ts +249 -3
- package/src/flow/nodes/split_by_scheme.ts +192 -2
- package/src/flow/nodes/split_by_subflow.ts +6 -4
- package/src/flow/nodes/split_by_ticket.ts +4 -3
- package/src/flow/nodes/split_by_webhook.ts +6 -5
- package/src/flow/nodes/wait_for_audio.ts +2 -2
- package/src/flow/nodes/wait_for_digits.ts +2 -2
- package/src/flow/nodes/wait_for_image.ts +2 -2
- package/src/flow/nodes/wait_for_location.ts +2 -2
- package/src/flow/nodes/wait_for_menu.ts +2 -2
- package/src/flow/nodes/wait_for_response.ts +48 -679
- package/src/flow/nodes/wait_for_video.ts +2 -2
- package/src/flow/types.ts +109 -23
- package/src/flow/utils.ts +108 -14
- package/src/form/ContactSearch.ts +1 -1
- package/src/form/FieldRenderer.ts +2 -4
- package/src/interfaces.ts +3 -0
- package/src/list/SortableList.ts +109 -34
- package/src/live/ContactChat.ts +15 -18
- package/src/store/AppState.ts +69 -0
- package/src/store/flow-definition.d.ts +2 -5
- package/src/utils.ts +332 -12
- package/static/api/channels.json +46 -0
- package/static/api/resthooks.json +31 -0
- package/static/svg/index.svg +1 -1
- package/static/svg/work/traced/lightning-02.svg +1 -0
- package/static/svg/work/used/lightning-02.svg +3 -0
- package/temba-modules.ts +4 -0
- package/test/ActionHelper.ts +3 -3
- package/test/NodeHelper.ts +6 -3
- package/test/actions/add_contact_urn.test.ts +287 -0
- package/test/actions/send_broadcast.test.ts +190 -0
- package/test/actions/send_email.test.ts +17 -23
- package/test/actions/send_msg.test.ts +39 -15
- package/test/actions/start_session.test.ts +151 -0
- package/test/nodes/split_by_airtime.test.ts +673 -0
- package/test/nodes/split_by_contact_field.test.ts +451 -0
- package/test/nodes/split_by_expression.test.ts +751 -0
- package/test/nodes/split_by_random.test.ts +3 -3
- package/test/nodes/split_by_resthook.test.ts +398 -0
- package/test/nodes/split_by_run_result.test.ts +1109 -0
- package/test/nodes/split_by_scheme.test.ts +486 -0
- package/test/nodes/split_by_subflow.test.ts +381 -0
- package/test/nodes/wait_for_digits.test.ts +2 -2
- package/test/nodes/wait_for_response.test.ts +2 -1
- package/test/temba-action-drag-between-nodes.test.ts +301 -0
- package/test/temba-canvas-menu.test.ts +156 -0
- package/test/temba-flow-editor-node.test.ts +102 -2
- package/test/temba-flow-editor.test.ts +7 -8
- package/test/temba-node-editor.test.ts +3 -1
- package/test/temba-node-type-selector.test.ts +152 -0
- package/test/temba-omnibox.test.ts +2 -1
- package/test/temba-sortable-list.test.ts +69 -0
- package/test/temba-utils-index.test.ts +0 -35
- package/test/utils.test.ts +2 -0
- package/test-assets/contacts/history.json +14 -20
- package/web-dev-server.config.mjs +3 -1
- package/out-tsc/src/flow/actions/call_classifier.js +0 -11
- package/out-tsc/src/flow/actions/call_classifier.js.map +0 -1
- package/out-tsc/src/flow/actions/call_resthook.js +0 -11
- package/out-tsc/src/flow/actions/call_resthook.js.map +0 -1
- package/out-tsc/src/flow/actions/split_by_expression_example.js +0 -77
- package/out-tsc/src/flow/actions/split_by_expression_example.js.map +0 -1
- package/out-tsc/src/flow/actions/transfer_airtime.js +0 -11
- package/out-tsc/src/flow/actions/transfer_airtime.js.map +0 -1
- package/src/flow/actions/call_classifier.ts +0 -12
- package/src/flow/actions/call_resthook.ts +0 -12
- package/src/flow/actions/split_by_expression_example.ts +0 -88
- package/src/flow/actions/transfer_airtime.ts +0 -12
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { getOperatorConfig } from '../operators';
|
|
2
|
+
import { generateDefaultCategoryName } from '../../utils';
|
|
3
|
+
import { FormData } from '../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared helper function to get operator value from various formats.
|
|
7
|
+
* Handles string, object, and array formats that can come from the form system.
|
|
8
|
+
*/
|
|
9
|
+
export const getOperatorValue = (operator: any): string => {
|
|
10
|
+
if (typeof operator === 'string') {
|
|
11
|
+
return operator.trim();
|
|
12
|
+
} else if (Array.isArray(operator) && operator.length > 0) {
|
|
13
|
+
const firstOperator = operator[0];
|
|
14
|
+
if (
|
|
15
|
+
firstOperator &&
|
|
16
|
+
typeof firstOperator === 'object' &&
|
|
17
|
+
firstOperator.value
|
|
18
|
+
) {
|
|
19
|
+
return firstOperator.value.trim();
|
|
20
|
+
}
|
|
21
|
+
} else if (operator && typeof operator === 'object' && operator.value) {
|
|
22
|
+
return operator.value.trim();
|
|
23
|
+
}
|
|
24
|
+
return '';
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Shared isEmptyItem function for rules array.
|
|
29
|
+
* Determines if a rule item is considered empty and should be filtered out.
|
|
30
|
+
*/
|
|
31
|
+
export const isEmptyRuleItem = (item: any): boolean => {
|
|
32
|
+
// Check if operator and category are provided
|
|
33
|
+
const operatorValue = getOperatorValue(item.operator);
|
|
34
|
+
if (!operatorValue || !item.category || item.category.trim() === '') {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if value is required based on operator configuration
|
|
39
|
+
const operatorConfig = getOperatorConfig(operatorValue);
|
|
40
|
+
if (operatorConfig && operatorConfig.operands === 1) {
|
|
41
|
+
return !item.value1 || item.value1.trim() === '';
|
|
42
|
+
} else if (operatorConfig && operatorConfig.operands === 2) {
|
|
43
|
+
return (
|
|
44
|
+
!item.value1 ||
|
|
45
|
+
item.value1.trim() === '' ||
|
|
46
|
+
!item.value2 ||
|
|
47
|
+
item.value2.trim() === ''
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Shared onItemChange function for rules array.
|
|
56
|
+
* Handles auto-updating category names based on operator and value changes.
|
|
57
|
+
*/
|
|
58
|
+
export const createRuleItemChangeHandler = () => {
|
|
59
|
+
return (itemIndex: number, field: string, value: any, allItems: any[]) => {
|
|
60
|
+
const updatedItems = [...allItems];
|
|
61
|
+
const item = { ...updatedItems[itemIndex] };
|
|
62
|
+
|
|
63
|
+
// Update the changed field
|
|
64
|
+
item[field] = value;
|
|
65
|
+
|
|
66
|
+
// Get operator values (before and after the change)
|
|
67
|
+
const oldItem = allItems[itemIndex] || {};
|
|
68
|
+
const oldOperatorValue =
|
|
69
|
+
field === 'operator'
|
|
70
|
+
? getOperatorValue(oldItem.operator)
|
|
71
|
+
: getOperatorValue(item.operator);
|
|
72
|
+
const newOperatorValue = getOperatorValue(item.operator);
|
|
73
|
+
|
|
74
|
+
// Calculate what the default category name should be before the change
|
|
75
|
+
const oldDefaultCategory = generateDefaultCategoryName(
|
|
76
|
+
oldOperatorValue,
|
|
77
|
+
getOperatorConfig,
|
|
78
|
+
field === 'value1' ? oldItem.value1 : item.value1,
|
|
79
|
+
field === 'value2' ? oldItem.value2 : item.value2
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Calculate what the new default category name should be after the change
|
|
83
|
+
const newDefaultCategory = generateDefaultCategoryName(
|
|
84
|
+
newOperatorValue,
|
|
85
|
+
getOperatorConfig,
|
|
86
|
+
item.value1,
|
|
87
|
+
item.value2
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Determine if we should auto-update the category
|
|
91
|
+
const shouldUpdateCategory =
|
|
92
|
+
!item.category ||
|
|
93
|
+
item.category.trim() === '' ||
|
|
94
|
+
item.category === oldDefaultCategory;
|
|
95
|
+
|
|
96
|
+
// Auto-populate or update category if conditions are met
|
|
97
|
+
if (shouldUpdateCategory && newDefaultCategory) {
|
|
98
|
+
item.category = newDefaultCategory;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
updatedItems[itemIndex] = item;
|
|
102
|
+
return updatedItems;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Shared visibility condition for value1 field.
|
|
108
|
+
*/
|
|
109
|
+
export const value1VisibilityCondition = (formData: Record<string, any>) => {
|
|
110
|
+
const operatorValue = getOperatorValue(formData.operator);
|
|
111
|
+
const operatorConfig = getOperatorConfig(operatorValue);
|
|
112
|
+
return operatorConfig ? operatorConfig.operands >= 1 : true;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Shared visibility condition for value2 field.
|
|
117
|
+
*/
|
|
118
|
+
export const value2VisibilityCondition = (formData: Record<string, any>) => {
|
|
119
|
+
const operatorValue = getOperatorValue(formData.operator);
|
|
120
|
+
const operatorConfig = getOperatorConfig(operatorValue);
|
|
121
|
+
return operatorConfig ? operatorConfig.operands === 2 : false;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Shared item configuration for rules array.
|
|
126
|
+
* This defines the operator, value1, value2, and category fields.
|
|
127
|
+
*/
|
|
128
|
+
export const createRulesItemConfig = () => ({
|
|
129
|
+
operator: {
|
|
130
|
+
type: 'select' as const,
|
|
131
|
+
required: true,
|
|
132
|
+
multi: false,
|
|
133
|
+
options: [], // Will be set by the caller
|
|
134
|
+
flavor: 'xsmall' as const,
|
|
135
|
+
width: '200px'
|
|
136
|
+
},
|
|
137
|
+
value1: {
|
|
138
|
+
type: 'text' as const,
|
|
139
|
+
flavor: 'xsmall' as const,
|
|
140
|
+
conditions: {
|
|
141
|
+
visible: value1VisibilityCondition
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
value2: {
|
|
145
|
+
type: 'text' as const,
|
|
146
|
+
flavor: 'xsmall' as const,
|
|
147
|
+
conditions: {
|
|
148
|
+
visible: value2VisibilityCondition
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
category: {
|
|
152
|
+
type: 'text' as const,
|
|
153
|
+
placeholder: 'Category',
|
|
154
|
+
required: true,
|
|
155
|
+
maxWidth: '120px',
|
|
156
|
+
flavor: 'xsmall' as const
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Shared function to extract rules from form data.
|
|
162
|
+
* Filters and transforms form rules into the format expected by createRulesRouter.
|
|
163
|
+
*/
|
|
164
|
+
export const extractUserRules = (formData: FormData) => {
|
|
165
|
+
return (formData.rules || [])
|
|
166
|
+
.filter((rule: any) => {
|
|
167
|
+
const operatorValue = getOperatorValue(rule?.operator);
|
|
168
|
+
if (
|
|
169
|
+
!operatorValue ||
|
|
170
|
+
!rule?.category ||
|
|
171
|
+
operatorValue === '' ||
|
|
172
|
+
rule.category.trim() === ''
|
|
173
|
+
) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const operatorConfig = getOperatorConfig(operatorValue);
|
|
178
|
+
if (operatorConfig && operatorConfig.operands === 1) {
|
|
179
|
+
return rule?.value1 && rule.value1.trim() !== '';
|
|
180
|
+
} else if (operatorConfig && operatorConfig.operands === 2) {
|
|
181
|
+
return (
|
|
182
|
+
rule?.value1 &&
|
|
183
|
+
rule.value1.trim() !== '' &&
|
|
184
|
+
rule?.value2 &&
|
|
185
|
+
rule.value2.trim() !== ''
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return true;
|
|
190
|
+
})
|
|
191
|
+
.map((rule: any) => {
|
|
192
|
+
const operatorValue = getOperatorValue(rule.operator);
|
|
193
|
+
const operatorConfig = getOperatorConfig(operatorValue);
|
|
194
|
+
|
|
195
|
+
let value = '';
|
|
196
|
+
|
|
197
|
+
if (operatorConfig && operatorConfig.operands === 1) {
|
|
198
|
+
value = rule.value1 ? rule.value1.trim() : '';
|
|
199
|
+
} else if (operatorConfig && operatorConfig.operands === 2) {
|
|
200
|
+
const val1 = rule.value1 ? rule.value1.trim() : '';
|
|
201
|
+
const val2 = rule.value2 ? rule.value2.trim() : '';
|
|
202
|
+
value = `${val1} ${val2}`.trim();
|
|
203
|
+
} else {
|
|
204
|
+
value = '';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
operator: operatorValue,
|
|
209
|
+
value: value,
|
|
210
|
+
category: rule.category.trim()
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Shared function to transform router cases to form rules.
|
|
217
|
+
* Converts node router cases back into the form data structure.
|
|
218
|
+
*/
|
|
219
|
+
export const casesToFormRules = (node: any) => {
|
|
220
|
+
const rules = [];
|
|
221
|
+
if (node.router?.cases && node.router?.categories) {
|
|
222
|
+
node.router.cases.forEach((case_: any) => {
|
|
223
|
+
// Find the category for this case
|
|
224
|
+
const category = node.router!.categories.find(
|
|
225
|
+
(cat: any) => cat.uuid === case_.category_uuid
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Skip system categories
|
|
229
|
+
if (category && !isSystemCategory(category.name)) {
|
|
230
|
+
// Handle different operator types
|
|
231
|
+
const operatorConfig = getOperatorConfig(case_.type);
|
|
232
|
+
const operatorDisplayName = operatorConfig
|
|
233
|
+
? operatorConfig.name
|
|
234
|
+
: case_.type;
|
|
235
|
+
let value1 = '';
|
|
236
|
+
let value2 = '';
|
|
237
|
+
|
|
238
|
+
if (operatorConfig && operatorConfig.operands === 0) {
|
|
239
|
+
value1 = '';
|
|
240
|
+
value2 = '';
|
|
241
|
+
} else if (operatorConfig && operatorConfig.operands === 1) {
|
|
242
|
+
value1 = case_.arguments.join(' ');
|
|
243
|
+
value2 = '';
|
|
244
|
+
} else if (operatorConfig && operatorConfig.operands === 2) {
|
|
245
|
+
value1 = case_.arguments[0] || '';
|
|
246
|
+
value2 = case_.arguments[1] || '';
|
|
247
|
+
} else {
|
|
248
|
+
value1 = case_.arguments.join(' ');
|
|
249
|
+
value2 = '';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
rules.push({
|
|
253
|
+
operator: { value: case_.type, name: operatorDisplayName },
|
|
254
|
+
value1: value1,
|
|
255
|
+
value2: value2,
|
|
256
|
+
category: category.name
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return rules;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Helper to check if a category is a system category
|
|
266
|
+
*/
|
|
267
|
+
function isSystemCategory(categoryName: string): boolean {
|
|
268
|
+
return ['No Response', 'Other', 'All Responses', 'Timeout'].includes(
|
|
269
|
+
categoryName
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Creates a complete rules array configuration for forms.
|
|
275
|
+
* This is the shared configuration used by both wait_for_response and split_by_expression.
|
|
276
|
+
*
|
|
277
|
+
* @param operatorOptions - The operator options to use (from operatorsToSelectOptions)
|
|
278
|
+
* @param helpText - The help text to display for the rules array
|
|
279
|
+
* @returns A complete array field configuration object
|
|
280
|
+
*/
|
|
281
|
+
export const createRulesArrayConfig = (
|
|
282
|
+
operatorOptions: any[],
|
|
283
|
+
helpText: string = 'Define rules to categorize responses'
|
|
284
|
+
) => ({
|
|
285
|
+
type: 'array' as const,
|
|
286
|
+
helpText,
|
|
287
|
+
itemLabel: 'Rule',
|
|
288
|
+
minItems: 0,
|
|
289
|
+
maxItems: 100,
|
|
290
|
+
sortable: true,
|
|
291
|
+
maintainEmptyItem: true,
|
|
292
|
+
isEmptyItem: isEmptyRuleItem,
|
|
293
|
+
onItemChange: createRuleItemChangeHandler(),
|
|
294
|
+
itemConfig: {
|
|
295
|
+
...createRulesItemConfig(),
|
|
296
|
+
operator: {
|
|
297
|
+
...createRulesItemConfig().operator,
|
|
298
|
+
options: operatorOptions
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TextFieldConfig } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared result_name field configuration for router nodes.
|
|
5
|
+
* This provides a consistent "Save as..." optional field interface across all splits.
|
|
6
|
+
*
|
|
7
|
+
* The field is hidden by default and revealed via a "Save as..." link.
|
|
8
|
+
* Once revealed, it cannot be hidden again (the link disappears).
|
|
9
|
+
* If the field already has a value, it's shown immediately without the link.
|
|
10
|
+
*/
|
|
11
|
+
export const resultNameField: TextFieldConfig = {
|
|
12
|
+
type: 'text',
|
|
13
|
+
label: 'Result Name',
|
|
14
|
+
required: false,
|
|
15
|
+
placeholder: '(optional)',
|
|
16
|
+
helpText: 'The name to use to reference this result in the flow',
|
|
17
|
+
optionalLink: 'Save result as...'
|
|
18
|
+
};
|
|
@@ -1,9 +1,242 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ACTION_GROUPS, FormData, NodeConfig } from '../types';
|
|
2
|
+
import { TransferAirtime, Node } from '../../store/flow-definition';
|
|
3
|
+
import { generateUUID, createSuccessFailureRouter } from '../../utils';
|
|
4
|
+
import { html } from 'lit';
|
|
5
|
+
import { CURRENCY_OPTIONS, CURRENCIES } from '../currencies';
|
|
6
|
+
import { resultNameField } from './shared';
|
|
3
7
|
|
|
4
8
|
export const split_by_airtime: NodeConfig = {
|
|
5
9
|
type: 'split_by_airtime',
|
|
6
|
-
name: '
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
name: 'Send Airtime',
|
|
11
|
+
group: ACTION_GROUPS.services,
|
|
12
|
+
showAsAction: true,
|
|
13
|
+
form: {
|
|
14
|
+
amounts: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
label: 'Airtime Amounts',
|
|
17
|
+
helpText: 'Define the currencies and amounts to transfer',
|
|
18
|
+
required: true,
|
|
19
|
+
itemLabel: 'Amount',
|
|
20
|
+
sortable: false,
|
|
21
|
+
minItems: 1,
|
|
22
|
+
maxItems: 10,
|
|
23
|
+
isEmptyItem: (item: any) => {
|
|
24
|
+
return !item.currency || !item.amount || item.amount.trim() === '';
|
|
25
|
+
},
|
|
26
|
+
itemConfig: {
|
|
27
|
+
currency: {
|
|
28
|
+
type: 'select',
|
|
29
|
+
placeholder: 'Select a currency',
|
|
30
|
+
required: true,
|
|
31
|
+
options: CURRENCY_OPTIONS,
|
|
32
|
+
searchable: true,
|
|
33
|
+
multi: false,
|
|
34
|
+
width: '200px'
|
|
35
|
+
},
|
|
36
|
+
amount: {
|
|
37
|
+
type: 'text',
|
|
38
|
+
placeholder: 'Amount',
|
|
39
|
+
required: true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
result_name: resultNameField
|
|
44
|
+
},
|
|
45
|
+
layout: ['amounts', 'result_name'],
|
|
46
|
+
validate: (formData: FormData) => {
|
|
47
|
+
const errors: { [key: string]: string } = {};
|
|
48
|
+
|
|
49
|
+
// Validate that we have at least one amount
|
|
50
|
+
if (formData.amounts && Array.isArray(formData.amounts)) {
|
|
51
|
+
const validAmounts = formData.amounts.filter(
|
|
52
|
+
(item: any) =>
|
|
53
|
+
item?.currency && item?.amount && item.amount.trim() !== ''
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (validAmounts.length === 0) {
|
|
57
|
+
errors.amounts = 'At least one currency and amount is required';
|
|
58
|
+
return {
|
|
59
|
+
valid: false,
|
|
60
|
+
errors
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check for duplicate currencies
|
|
65
|
+
const currencies = new Set();
|
|
66
|
+
const duplicates: string[] = [];
|
|
67
|
+
|
|
68
|
+
validAmounts.forEach((item: any) => {
|
|
69
|
+
// Extract currency code from selection
|
|
70
|
+
const currencyCode =
|
|
71
|
+
Array.isArray(item.currency) && item.currency.length > 0
|
|
72
|
+
? item.currency[0].value
|
|
73
|
+
: typeof item.currency === 'string'
|
|
74
|
+
? item.currency
|
|
75
|
+
: item.currency?.value;
|
|
76
|
+
|
|
77
|
+
if (currencies.has(currencyCode)) {
|
|
78
|
+
duplicates.push(currencyCode);
|
|
79
|
+
} else {
|
|
80
|
+
currencies.add(currencyCode);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (duplicates.length > 0) {
|
|
85
|
+
errors.amounts = `Duplicate currencies found: ${duplicates.join(', ')}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Validate amounts are numeric
|
|
89
|
+
for (const item of validAmounts) {
|
|
90
|
+
const amount = item.amount.trim();
|
|
91
|
+
if (isNaN(Number(amount)) || Number(amount) <= 0) {
|
|
92
|
+
errors.amounts = 'All amounts must be valid positive numbers';
|
|
93
|
+
return {
|
|
94
|
+
valid: false,
|
|
95
|
+
errors
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
errors.amounts = 'At least one currency and amount is required';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
valid: Object.keys(errors).length === 0,
|
|
105
|
+
errors
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
render: (node: Node) => {
|
|
109
|
+
const transferAirtimeAction = node.actions?.find(
|
|
110
|
+
(action) => action.type === 'transfer_airtime'
|
|
111
|
+
) as TransferAirtime;
|
|
112
|
+
|
|
113
|
+
if (!transferAirtimeAction || !transferAirtimeAction.amounts) {
|
|
114
|
+
return html`<div class="body">Configure airtime transfer</div>`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const amounts = transferAirtimeAction.amounts;
|
|
118
|
+
const currencies = Object.keys(amounts);
|
|
119
|
+
|
|
120
|
+
if (currencies.length === 0) {
|
|
121
|
+
return html`<div class="body">Configure airtime transfer</div>`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Display the first currency amount, with indicator if there are more
|
|
125
|
+
const firstCurrency = currencies[0];
|
|
126
|
+
const firstAmount = amounts[firstCurrency];
|
|
127
|
+
const moreCount = currencies.length - 1;
|
|
128
|
+
|
|
129
|
+
return html`
|
|
130
|
+
<div class="body">
|
|
131
|
+
${firstCurrency}
|
|
132
|
+
${firstAmount}${moreCount > 0
|
|
133
|
+
? html` <span style="color: #999;">+${moreCount} more</span>`
|
|
134
|
+
: ''}
|
|
135
|
+
</div>
|
|
136
|
+
`;
|
|
137
|
+
},
|
|
138
|
+
toFormData: (node: Node) => {
|
|
139
|
+
// Extract data from the existing node structure
|
|
140
|
+
const transferAirtimeAction = node.actions?.find(
|
|
141
|
+
(action) => action.type === 'transfer_airtime'
|
|
142
|
+
) as TransferAirtime;
|
|
143
|
+
|
|
144
|
+
const amounts: any[] = [];
|
|
145
|
+
if (transferAirtimeAction && transferAirtimeAction.amounts) {
|
|
146
|
+
Object.entries(transferAirtimeAction.amounts).forEach(
|
|
147
|
+
([currency, amount]) => {
|
|
148
|
+
amounts.push({
|
|
149
|
+
currency: [
|
|
150
|
+
{
|
|
151
|
+
value: currency,
|
|
152
|
+
name: CURRENCIES[currency]?.name
|
|
153
|
+
? `${CURRENCIES[currency].name} (${currency})`
|
|
154
|
+
: currency
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
amount: String(amount)
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
uuid: node.uuid,
|
|
165
|
+
amounts: amounts,
|
|
166
|
+
result_name: node.router?.result_name || ''
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
fromFormData: (formData: FormData, originalNode: Node): Node => {
|
|
170
|
+
// Get user amounts and convert to amounts object
|
|
171
|
+
const amountsObject: Record<string, number> = {};
|
|
172
|
+
|
|
173
|
+
if (formData.amounts && Array.isArray(formData.amounts)) {
|
|
174
|
+
formData.amounts.forEach((item: any) => {
|
|
175
|
+
if (!item?.currency || !item?.amount || item.amount.trim() === '') {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Extract currency code from selection (handle both array and object formats)
|
|
180
|
+
let currencyCode: string;
|
|
181
|
+
if (Array.isArray(item.currency) && item.currency.length > 0) {
|
|
182
|
+
currencyCode = item.currency[0].value;
|
|
183
|
+
} else if (typeof item.currency === 'string') {
|
|
184
|
+
currencyCode = item.currency;
|
|
185
|
+
} else if (item.currency?.value) {
|
|
186
|
+
currencyCode = item.currency.value;
|
|
187
|
+
} else {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const amount = parseFloat(item.amount.trim());
|
|
192
|
+
if (!isNaN(amount) && amount > 0) {
|
|
193
|
+
amountsObject[currencyCode] = amount;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Find existing transfer_airtime action to preserve its UUID
|
|
199
|
+
const existingTransferAirtimeAction = originalNode.actions?.find(
|
|
200
|
+
(action) => action.type === 'transfer_airtime'
|
|
201
|
+
);
|
|
202
|
+
const transferAirtimeUuid =
|
|
203
|
+
existingTransferAirtimeAction?.uuid || generateUUID();
|
|
204
|
+
|
|
205
|
+
// Create transfer_airtime action
|
|
206
|
+
const transferAirtimeAction: TransferAirtime = {
|
|
207
|
+
type: 'transfer_airtime',
|
|
208
|
+
uuid: transferAirtimeUuid,
|
|
209
|
+
amounts: amountsObject
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Create categories and exits for Success and Failure
|
|
213
|
+
const existingCategories = originalNode.router?.categories || [];
|
|
214
|
+
const existingExits = originalNode.exits || [];
|
|
215
|
+
const existingCases = originalNode.router?.cases || [];
|
|
216
|
+
|
|
217
|
+
const { router, exits } = createSuccessFailureRouter(
|
|
218
|
+
'@locals._new_transfer',
|
|
219
|
+
{
|
|
220
|
+
type: 'has_text',
|
|
221
|
+
arguments: []
|
|
222
|
+
},
|
|
223
|
+
existingCategories,
|
|
224
|
+
existingExits,
|
|
225
|
+
existingCases
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Add result_name if provided
|
|
229
|
+
const finalRouter: any = { ...router };
|
|
230
|
+
if (formData.result_name && formData.result_name.trim() !== '') {
|
|
231
|
+
finalRouter.result_name = formData.result_name.trim();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Return the complete node
|
|
235
|
+
return {
|
|
236
|
+
uuid: originalNode.uuid,
|
|
237
|
+
actions: [transferAirtimeAction],
|
|
238
|
+
router: finalRouter,
|
|
239
|
+
exits: exits
|
|
240
|
+
};
|
|
241
|
+
}
|
|
9
242
|
};
|