diff --git a/backend/Actions/Instasent/InstasentController.php b/backend/Actions/Instasent/InstasentController.php
new file mode 100644
index 000000000..3317f1e03
--- /dev/null
+++ b/backend/Actions/Instasent/InstasentController.php
@@ -0,0 +1,130 @@
+auth_token)) {
+ wp_send_json_error(
+ __(
+ 'Requested parameter is empty',
+ 'bit-integrations'
+ ),
+ 400
+ );
+ }
+
+ $endpoint = self::$_baseUrl . '/organization/account';
+ $header = [
+ 'Authorization' => 'Bearer ' . $refreshFieldsRequestParams->auth_token,
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json',
+ ];
+
+ $response = HttpHelper::get($endpoint, null, $header);
+
+ if (is_wp_error($response)) {
+ wp_send_json_error($response->get_error_message(), 400);
+ }
+
+ if (HttpHelper::$responseCode == 200) {
+ wp_send_json_success('Authorization Successful', 200);
+
+ return;
+ }
+
+ wp_send_json_error(
+ $response->message ?? $response ?? 'Authorization Failed',
+ 400
+ );
+ }
+
+ public function refreshDatasources($refreshFieldsRequestParams)
+ {
+ if (empty($refreshFieldsRequestParams) || empty($refreshFieldsRequestParams->auth_token) || empty($refreshFieldsRequestParams->projectId)) {
+ wp_send_json_error(
+ __('Requested parameter is empty', 'bit-integrations'),
+ 400
+ );
+ }
+
+ $project = rawurlencode($refreshFieldsRequestParams->projectId);
+ $endpoint = self::$_baseUrl . "/v1/project/{$project}/datasource";
+ $header = [
+ 'Authorization' => 'Bearer ' . $refreshFieldsRequestParams->auth_token,
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json',
+ ];
+
+ $response = HttpHelper::get($endpoint, null, $header);
+
+ if (is_wp_error($response)) {
+ wp_send_json_error($response->get_error_message(), 400);
+ }
+
+ if (HttpHelper::$responseCode == 200) {
+ $entities = $response->entities ?? [];
+ $formattedResponse = [];
+ foreach ($entities as $entity) {
+ $formattedResponse[] = [
+ 'id' => $entity->id ?? '',
+ 'name' => $entity->name ?? ($entity->id ?? ''),
+ ];
+ }
+
+ wp_send_json_success($formattedResponse, 200);
+
+ return;
+ }
+
+ wp_send_json_error(
+ $response->message ?? $response ?? __('Failed to fetch data sources', 'bit-integrations'),
+ 400
+ );
+ }
+
+ public function execute($integrationData, $fieldValues)
+ {
+ $integrationDetails = $integrationData->flow_details;
+ $integId = $integrationData->id;
+ $auth_token = $integrationDetails->auth_token ?? '';
+ $fieldMap = $integrationDetails->field_map ?? '';
+ $action = $integrationDetails->action ?? '';
+ $actions = isset($integrationDetails->actions) ? $integrationDetails->actions : (object) [];
+
+ if (
+ empty($fieldMap)
+ || empty($auth_token)
+ || empty($action)
+ ) {
+ // translators: %s: Placeholder value
+ return new WP_Error('REQ_FIELD_EMPTY', wp_sprintf(__('module, fields are required for %s api', 'bit-integrations'), 'Instasent'));
+ }
+
+ $recordApiHelper = new RecordApiHelper($auth_token, $integrationDetails, $integId);
+ $instasentApiResponse = $recordApiHelper->execute(
+ $integrationDetails,
+ $fieldValues,
+ $fieldMap,
+ $auth_token,
+ $action,
+ $actions
+ );
+
+ return $instasentApiResponse;
+ }
+}
diff --git a/backend/Actions/Instasent/RecordApiHelper.php b/backend/Actions/Instasent/RecordApiHelper.php
new file mode 100644
index 000000000..bdfdf3f08
--- /dev/null
+++ b/backend/Actions/Instasent/RecordApiHelper.php
@@ -0,0 +1,142 @@
+_authToken = $auth_token;
+ $this->_integrationDetails = $integrationDetails;
+ $this->_integrationID = $integId;
+ }
+
+ public function generateReqDataFromFieldMap($data, $fieldMap)
+ {
+ $dataFinal = [];
+
+ foreach ($fieldMap as $key => $value) {
+ $triggerValue = $value->formField;
+ $actionValue = $value->instasentFormField;
+
+ if (empty($actionValue)) {
+ continue;
+ }
+
+ if ($triggerValue === 'custom' && isset($value->customValue)) {
+ $dataFinal[$actionValue] = Common::replaceFieldWithValue($value->customValue, $data);
+ } elseif (isset($data[$triggerValue])) {
+ $dataFinal[$actionValue] = $data[$triggerValue];
+ }
+ }
+
+ return $dataFinal;
+ }
+
+ public function execute(
+ $integrationDetails,
+ $fieldValues,
+ $fieldMap,
+ $auth_token,
+ $action,
+ $utilities = null
+ ) {
+ $fieldData = $this->generateReqDataFromFieldMap($fieldValues, $fieldMap);
+
+ if ($action === 'send_sms' && isset($utilities->allowUnicode)) {
+ $fieldData['allowUnicode'] = $utilities->allowUnicode;
+ }
+
+ $default = [
+ 'success' => false,
+ 'message' => __('Bit Integrations Pro is required.', 'bit-integrations'),
+ ];
+
+ switch ($action) {
+ case 'send_sms':
+ $apiResponse = Hooks::apply(Config::withPrefix('instasent_send_sms'), $default, $fieldData, $auth_token);
+ $typeName = 'send-sms';
+
+ break;
+
+ case 'create_lookup':
+ $apiResponse = Hooks::apply(Config::withPrefix('instasent_create_lookup'), $default, $fieldData, $auth_token);
+ $typeName = 'create-lookup';
+
+ break;
+
+ case 'create_datasource':
+ $apiResponse = Hooks::apply(Config::withPrefix('instasent_create_datasource'), $default, $fieldData, $integrationDetails, $auth_token);
+ $typeName = 'create-datasource';
+
+ break;
+
+ case 'create_or_update_contact':
+ $apiResponse = Hooks::apply(Config::withPrefix('instasent_create_or_update_contact'), $default, $fieldData, $integrationDetails, $auth_token);
+ $typeName = 'create-or-update-contact';
+
+ break;
+
+ case 'delete_contact':
+ $apiResponse = Hooks::apply(Config::withPrefix('instasent_delete_contact'), $default, $fieldData, $integrationDetails, $auth_token);
+ $typeName = 'delete-contact';
+
+ break;
+
+ case 'create_contact_event':
+ $apiResponse = Hooks::apply(Config::withPrefix('instasent_create_contact_event'), $default, $fieldData, $integrationDetails, $auth_token);
+ $typeName = 'create-contact-event';
+
+ break;
+
+ default:
+ $apiResponse = $default;
+ $typeName = $action;
+
+ break;
+ }
+
+ if (is_wp_error($apiResponse)) {
+ LogHandler::save(
+ $this->_integrationID,
+ wp_json_encode(['type' => 'action', 'type_name' => $typeName]),
+ 'error',
+ wp_json_encode(['message' => $apiResponse->get_error_message()])
+ );
+
+ return $apiResponse;
+ }
+
+ $apiResponse = \is_array($apiResponse) ? (object) $apiResponse : $apiResponse;
+
+ if (
+ (isset($apiResponse->success) && $apiResponse->success)
+ || isset($apiResponse->id)
+ || isset($apiResponse->data)
+ ) {
+ LogHandler::save($this->_integrationID, wp_json_encode(['type' => 'action', 'type_name' => $typeName]), 'success', wp_json_encode($apiResponse));
+ } else {
+ LogHandler::save($this->_integrationID, wp_json_encode(['type' => 'action', 'type_name' => $typeName]), 'error', wp_json_encode($apiResponse));
+ }
+
+ return $apiResponse;
+ }
+}
diff --git a/backend/Actions/Instasent/Routes.php b/backend/Actions/Instasent/Routes.php
new file mode 100644
index 000000000..a1378dab5
--- /dev/null
+++ b/backend/Actions/Instasent/Routes.php
@@ -0,0 +1,11 @@
+ import('./Acumbamail/EditAcumbamail'))
const EditGroundhogg = lazy(() => import('./Groundhogg/EditGroundhogg'))
const EditSendFox = lazy(() => import('./SendFox/EditSendFox'))
const EditMailerLite = lazy(() => import('./MailerLite/EditMailerLite'))
+const EditInstasent = lazy(() => import('./Instasent/EditInstasent'))
const EditVbout = lazy(() => import('./Vbout/EditVbout'))
const EditWhatsApp = lazy(() => import('./WhatsApp/EditWhatsApp'))
const EditLearnDash = lazy(() => import('./LearnDash/EditLearnDash'))
@@ -397,6 +398,8 @@ const IntegType = memo(({ allIntegURL, flow }) => {
return
case 'MailerLite':
return
+ case 'Instasent':
+ return
case 'Vbout':
return
case 'WhatsApp':
diff --git a/frontend/src/components/AllIntegrations/Instasent/EditInstasent.jsx b/frontend/src/components/AllIntegrations/Instasent/EditInstasent.jsx
new file mode 100644
index 000000000..f5b2cedbc
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/Instasent/EditInstasent.jsx
@@ -0,0 +1,113 @@
+/* eslint-disable no-param-reassign */
+
+import { create } from 'mutative'
+import { useEffect, useState } from 'react'
+import { useNavigate, useParams } from 'react-router'
+import { useRecoilState, useRecoilValue } from 'recoil'
+import { $actionConf, $formFields, $newFlow } from '../../../GlobalStates'
+import { __ } from '../../../Utils/i18nwrap'
+import SnackMsg from '../../Utilities/SnackMsg'
+import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers'
+import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree'
+import SetEditIntegComponents from '../IntegrationHelpers/SetEditIntegComponents'
+import { checkMappedFields, handleInput, InstasentStaticData } from './InstasentCommonFunc'
+import InstasentIntegLayout from './InstasentIntegLayout'
+
+function EditInstasent({ allIntegURL }) {
+ const navigate = useNavigate()
+ const { id } = useParams()
+ const [flow, setFlow] = useRecoilState($newFlow)
+ const [instasentConf, setInstasentConf] = useRecoilState($actionConf)
+ const [isLoading, setIsLoading] = useState(false)
+ const [name, setName] = useState(instasentConf?.name || '')
+ const [loading, setLoading] = useState({
+ list: false,
+ field: false,
+ auth: false
+ })
+ const [snack, setSnackbar] = useState({ show: false })
+ const formField = useRecoilValue($formFields)
+
+ const saveConfig = () => {
+ if (!checkMappedFields(instasentConf)) {
+ setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') })
+ return
+ }
+
+ saveActionConf({
+ flow,
+ allIntegURL,
+ conf: instasentConf,
+ navigate,
+ edit: 1,
+ setLoading,
+ setSnackbar
+ })
+ }
+
+ useEffect(() => {
+ setInstasentConf(prev =>
+ create(prev, draftConf => {
+ const action = draftConf.action || 'send_sms'
+ draftConf.action = action
+ // instasentFields isn't persisted; rebuild it from the saved action so the
+ // field map can render its options on edit (without touching the saved field_map).
+ draftConf.instasentFields = InstasentStaticData[action] || []
+ })
+ )
+ }, [])
+
+ const handleEditIntegName = e => {
+ setName(e.target.value)
+
+ setInstasentConf(prevConf =>
+ create(prevConf, draftConF => {
+ draftConF.name = e.target.value
+ })
+ )
+ }
+
+ return (
+
+
+
+
+ {__('Integration Name:', 'bit-integrations')}
+
+
+
+
+
+
handleInput(e, instasentConf, setInstasentConf, loading, setLoading)}
+ instasentConf={instasentConf}
+ setInstasentConf={setInstasentConf}
+ loading={loading}
+ setLoading={setLoading}
+ setSnackbar={setSnackbar}
+ />
+
+
+
+
+ )
+}
+
+export default EditInstasent
diff --git a/frontend/src/components/AllIntegrations/Instasent/Instasent.jsx b/frontend/src/components/AllIntegrations/Instasent/Instasent.jsx
new file mode 100644
index 000000000..2e0be1b11
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/Instasent/Instasent.jsx
@@ -0,0 +1,133 @@
+/* eslint-disable no-unused-expressions */
+import { useState } from 'react'
+import 'react-multiple-select-dropdown-lite/dist/index.css'
+import toast from 'react-hot-toast'
+import { useNavigate } from 'react-router'
+import { __ } from '../../../Utils/i18nwrap'
+import SnackMsg from '../../Utilities/SnackMsg'
+import Steps from '../../Utilities/Steps'
+import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers'
+import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree'
+import InstasentAuthorization from './InstasentAuthorization'
+import { checkMappedFields, handleInput } from './InstasentCommonFunc'
+import InstasentIntegLayout from './InstasentIntegLayout'
+
+function Instasent({ formFields, setFlow, flow, allIntegURL }) {
+ const navigate = useNavigate()
+ const [isLoading, setIsLoading] = useState(false)
+ const [loading, setLoading] = useState({
+ list: false,
+ field: false,
+ auth: false,
+ datasource: false
+ })
+
+ const [step, setstep] = useState(1)
+ const [snack, setSnackbar] = useState({ show: false })
+
+ const [instasentConf, setInstasentConf] = useState({
+ name: 'Instasent',
+ type: 'Instasent',
+ auth_token: '',
+ action: '',
+ projectId: '',
+ datasourceId: '',
+ instasentFields: [],
+ field_map: [{ formField: '', instasentFormField: '' }],
+ actions: {},
+ default: { datasources: [] }
+ })
+
+ const saveConfig = () => {
+ setIsLoading(true)
+ const resp = saveIntegConfig(
+ flow,
+ setFlow,
+ allIntegURL,
+ instasentConf,
+ navigate,
+ '',
+ '',
+ setIsLoading
+ )
+ resp.then(res => {
+ if (res.success) {
+ toast.success(res.data?.msg)
+ navigate(allIntegURL)
+ } else {
+ toast.error(res.data || res)
+ }
+ })
+ }
+
+ const nextPage = pageNo => {
+ setTimeout(() => {
+ document.getElementById('btcd-settings-wrp').scrollTop = 0
+ }, 300)
+
+ if (!checkMappedFields(instasentConf)) {
+ toast.error(__('Please map mandatory fields', 'bit-integrations'))
+ return
+ }
+ instasentConf.field_map.length > 0 && setstep(pageNo)
+ }
+
+ return (
+
+
+
+
+
+
+ {/* STEP 1 */}
+
+
+
+ {/* STEP 2 */}
+
+
handleInput(e, instasentConf, setInstasentConf, loading, setLoading)}
+ instasentConf={instasentConf}
+ setInstasentConf={setInstasentConf}
+ loading={loading}
+ setLoading={setLoading}
+ setSnackbar={setSnackbar}
+ />
+
+ {instasentConf?.action && (
+
+ )}
+
+
+ {/* STEP 3 */}
+
saveConfig()}
+ isLoading={isLoading}
+ dataConf={instasentConf}
+ setDataConf={setInstasentConf}
+ formFields={formFields}
+ />
+
+ )
+}
+
+export default Instasent
diff --git a/frontend/src/components/AllIntegrations/Instasent/InstasentActions.jsx b/frontend/src/components/AllIntegrations/Instasent/InstasentActions.jsx
new file mode 100644
index 000000000..6cfe19c73
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/Instasent/InstasentActions.jsx
@@ -0,0 +1,31 @@
+import { __ } from '../../../Utils/i18nwrap'
+import TableCheckBox from '../../Utilities/TableCheckBox'
+
+export default function InstasentActions({ instasentConf, setInstasentConf }) {
+ const actionHandler = (e, type) => {
+ const newConf = { ...instasentConf, actions: { ...instasentConf.actions } }
+ if (e.target.checked) {
+ newConf.actions[type] = true
+ } else {
+ delete newConf.actions[type]
+ }
+
+ setInstasentConf({ ...newConf })
+ }
+
+ return (
+
+
actionHandler(e, 'allowUnicode')}
+ className="wdt-200 mt-4 mr-2"
+ value="allow_unicode"
+ title={__('Allow Unicode', 'bit-integrations')}
+ subTitle={__(
+ 'Enable Unicode support for SMS messages (e.g. emojis, special characters)',
+ 'bit-integrations'
+ )}
+ />
+
+ )
+}
diff --git a/frontend/src/components/AllIntegrations/Instasent/InstasentAuthorization.jsx b/frontend/src/components/AllIntegrations/Instasent/InstasentAuthorization.jsx
new file mode 100644
index 000000000..3e1b99749
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/Instasent/InstasentAuthorization.jsx
@@ -0,0 +1,118 @@
+import { useState } from 'react'
+import { __ } from '../../../Utils/i18nwrap'
+import LoaderSm from '../../Loaders/LoaderSm'
+import Note from '../../Utilities/Note'
+import TutorialLink from '../../Utilities/TutorialLink'
+import { authorization } from './InstasentCommonFunc'
+
+export default function InstasentAuthorization({
+ instasentConf,
+ setInstasentConf,
+ step,
+ setstep,
+ loading,
+ setLoading,
+ setSnackbar,
+ isInfo
+}) {
+ const [isAuthorized, setIsAuthorized] = useState(false)
+ const [error, setError] = useState({ name: '', auth_token: '' })
+ const url = 'https://app.instasent.com/'
+
+ const nextPage = () => {
+ setTimeout(() => {
+ document.getElementById('btcd-settings-wrp').scrollTop = 0
+ }, 300)
+
+ setstep(2)
+ }
+
+ const handleInput = e => {
+ const newConf = { ...instasentConf }
+ const rmError = { ...error }
+ rmError[e.target.name] = ''
+ newConf[e.target.name] = e.target.value
+ setError(rmError)
+ setInstasentConf(newConf)
+ }
+
+ const note = `
+ ${__('Step of generate API token:', 'bit-integrations')}
+
+ - ${__('Goto', 'bit-integrations')} Instasent Dashboard
+ - ${__(
+ 'Copy the API Token and paste into API Token field of your authorization form.',
+ 'bit-integrations'
+ )}
+ - ${__('Finally, click Authorize button.', 'bit-integrations')}
+
+ `
+
+ return (
+
+
+
+
+ {__('Integration Name:', 'bit-integrations')}
+
+
+
+
+ {__('API Token:', 'bit-integrations')}
+
+
+
{error.auth_token}
+
+
+ {__('To Get API Token, Please Visit', 'bit-integrations')}
+
+
+ {__('Instasent Dashboard', 'bit-integrations')}
+
+
+
+
+
+ {!isInfo && (
+
+
+
+
+
+ )}
+
+
+ )
+}
diff --git a/frontend/src/components/AllIntegrations/Instasent/InstasentCommonFunc.js b/frontend/src/components/AllIntegrations/Instasent/InstasentCommonFunc.js
new file mode 100644
index 000000000..90b7e16a8
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/Instasent/InstasentCommonFunc.js
@@ -0,0 +1,198 @@
+/* eslint-disable no-else-return */
+import { create } from 'mutative'
+import toast from 'react-hot-toast'
+import bitsFetch from '../../../Utils/bitsFetch'
+import { __ } from '../../../Utils/i18nwrap'
+
+// Actions that need a Project / Data Source. These ids are selected at config time
+// (Project Id as text, Data Source as a fetched dropdown) instead of in the field map.
+export const ACTIONS_WITH_PROJECT = [
+ 'create_datasource',
+ 'create_or_update_contact',
+ 'delete_contact',
+ 'create_contact_event'
+]
+
+export const ACTIONS_WITH_DATASOURCE = [
+ 'create_or_update_contact',
+ 'delete_contact',
+ 'create_contact_event'
+]
+
+// Actions whose field map may carry arbitrary keys (contact custom attributes /
+// event parameters), entered via the "Custom Field..." option in the field map.
+export const ACTIONS_WITH_CUSTOM_FIELDS = ['create_or_update_contact', 'create_contact_event']
+
+// Sentinel chosen in the Instasent-field select to switch a row to a free-text key.
+export const CUSTOM_FIELD_KEY = '__custom_field__'
+
+export const InstasentStaticData = {
+ send_sms: [
+ { key: 'from', label: 'From', required: true },
+ { key: 'to', label: 'To', required: true },
+ { key: 'text', label: 'Text', required: true },
+ { key: 'clientId', label: 'Client Id', required: false }
+ ],
+ create_lookup: [{ key: 'to', label: 'To', required: true }],
+ create_datasource: [
+ { key: 'name', label: 'Name', required: true },
+ { key: 'description', label: 'Description', required: false },
+ { key: 'defaultCountry', label: 'Default Country', required: false },
+ { key: 'locale', label: 'Locale', required: false },
+ { key: 'timezone', label: 'Timezone', required: false }
+ ],
+ create_or_update_contact: [
+ { key: 'userId', label: 'User Id', required: true },
+ { key: 'firstName', label: 'First Name', required: false },
+ { key: 'lastName', label: 'Last Name', required: false },
+ { key: 'email', label: 'Email', required: false },
+ { key: 'phoneMobile', label: 'Phone Mobile', required: false }
+ ],
+ delete_contact: [{ key: 'userId', label: 'User Id', required: true }],
+ create_contact_event: [
+ { key: 'userId', label: 'User Id', required: true },
+ { key: 'eventId', label: 'Event Id', required: true },
+ { key: 'eventType', label: 'Event Type', required: true }
+ ]
+}
+
+export const handleInput = (e, instasentConf, setInstasentConf, loading, setLoading) => {
+ const { name, value } = e.target
+
+ const updatedConf = create(instasentConf, draftConf => {
+ draftConf[name] = value
+
+ // Changing the project invalidates the fetched data sources + the selection.
+ if (name === 'projectId') {
+ draftConf.datasourceId = ''
+ if (draftConf.default) {
+ draftConf.default.datasources = []
+ }
+ }
+ })
+
+ setInstasentConf(updatedConf)
+
+ if (name === 'action' && value !== '') {
+ instasentRefreshFields(updatedConf, setInstasentConf, loading, setLoading)
+ }
+}
+
+export const generateMappedField = instasentConf => {
+ const requiredFlds = instasentConf?.instasentFields.filter(fld => fld.required === true)
+ return requiredFlds.length > 0
+ ? requiredFlds.map(field => ({
+ formField: '',
+ instasentFormField: field.key
+ }))
+ : [{ formField: '', instasentFormField: '' }]
+}
+
+export const checkMappedFields = instasentConf => {
+ if (ACTIONS_WITH_PROJECT.includes(instasentConf?.action) && !instasentConf?.projectId) {
+ return false
+ }
+
+ if (ACTIONS_WITH_DATASOURCE.includes(instasentConf?.action) && !instasentConf?.datasourceId) {
+ return false
+ }
+
+ const mappedFields = instasentConf?.field_map
+ ? instasentConf.field_map.filter(
+ mappedField =>
+ !mappedField.formField ||
+ !mappedField.instasentFormField ||
+ mappedField.instasentFormField === CUSTOM_FIELD_KEY ||
+ (mappedField.formField === 'custom' && !mappedField.customValue)
+ )
+ : []
+ if (mappedFields.length > 0) {
+ return false
+ }
+ return true
+}
+
+export const authorization = (confTmp, setIsAuthorized, loading, setLoading) => {
+ if (!confTmp.auth_token) {
+ toast.error(__("API Token can't be empty", 'bit-integrations'))
+
+ return
+ }
+
+ setLoading({ ...loading, auth: true })
+
+ const requestParams = {
+ auth_token: confTmp.auth_token
+ }
+
+ bitsFetch(requestParams, 'instasent_authorize').then(result => {
+ setLoading({ ...loading, auth: false })
+
+ if (result && result.success) {
+ setIsAuthorized(true)
+
+ toast.success(__('Authorized Successfully', 'bit-integrations'))
+
+ return
+ }
+
+ toast.error(__('Authorized failed', 'bit-integrations'))
+ })
+}
+
+export const instasentRefreshFields = (confTmp, setConf, loading, setLoading) => {
+ setLoading({ ...loading, field: true })
+
+ const staticFields = InstasentStaticData[confTmp?.action] || []
+
+ setConf(prev =>
+ create(prev, draftConf => {
+ draftConf.instasentFields = staticFields
+ draftConf.field_map = generateMappedField(draftConf)
+ })
+ )
+
+ setLoading({ ...loading, field: false })
+
+ toast.success(__('Fields refresh successfully', 'bit-integrations'))
+}
+
+export const refreshDatasources = (confTmp, setConf, loading, setLoading) => {
+ if (!confTmp.auth_token) {
+ toast.error(__("API Token can't be empty", 'bit-integrations'))
+
+ return
+ }
+
+ if (!confTmp.projectId) {
+ toast.error(__('Please enter a Project Id first', 'bit-integrations'))
+
+ return
+ }
+
+ setLoading({ ...loading, datasource: true })
+
+ const requestParams = {
+ auth_token: confTmp.auth_token,
+ projectId: confTmp.projectId
+ }
+
+ bitsFetch(requestParams, 'refresh_instasent_datasources').then(result => {
+ setLoading({ ...loading, datasource: false })
+
+ if (result && result.success) {
+ setConf(prev =>
+ create(prev, draftConf => {
+ draftConf.default = draftConf.default || {}
+ draftConf.default.datasources = result.data || []
+ })
+ )
+
+ toast.success(__('Data sources fetched successfully', 'bit-integrations'))
+
+ return
+ }
+
+ toast.error(__('Data sources fetch failed', 'bit-integrations'))
+ })
+}
diff --git a/frontend/src/components/AllIntegrations/Instasent/InstasentFieldMap.jsx b/frontend/src/components/AllIntegrations/Instasent/InstasentFieldMap.jsx
new file mode 100644
index 000000000..958061df9
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/Instasent/InstasentFieldMap.jsx
@@ -0,0 +1,128 @@
+import { useRecoilValue } from 'recoil'
+import { $appConfigState } from '../../../GlobalStates'
+import { SmartTagField } from '../../../Utils/StaticData/SmartTagField'
+import { __, sprintf } from '../../../Utils/i18nwrap'
+import { handleCustomValue } from '../IntegrationHelpers/IntegrationHelpers'
+import TagifyInput from '../../Utilities/TagifyInput'
+import { addFieldMap, delFieldMap, handleFieldMapping } from './IntegrationHelpers'
+import { ACTIONS_WITH_CUSTOM_FIELDS, CUSTOM_FIELD_KEY } from './InstasentCommonFunc'
+
+export default function InstasentFieldMap({ i, formFields, field, instasentConf, setInstasentConf }) {
+ const btcbi = useRecoilValue($appConfigState)
+ const { isPro } = btcbi
+
+ const requiredFlds = instasentConf?.instasentFields?.filter(fld => fld.required === true) || []
+ const nonRequiredFlds = instasentConf?.instasentFields?.filter(fld => fld.required === false) || []
+
+ const isExtraRow = i >= requiredFlds.length
+ const customCapable = ACTIONS_WITH_CUSTOM_FIELDS.includes(instasentConf?.action)
+ const predefinedKeys = (instasentConf?.instasentFields || []).map(fld => fld.key)
+ const isCustomKey =
+ isExtraRow &&
+ customCapable &&
+ !!field.instasentFormField &&
+ field.instasentFormField !== CUSTOM_FIELD_KEY &&
+ !predefinedKeys.includes(field.instasentFormField)
+ const showCustomKeyInput = field.instasentFormField === CUSTOM_FIELD_KEY || isCustomKey
+
+ return (
+
+
+
+
+
+ {field.formField === 'custom' && (
+ handleCustomValue(e, i, instasentConf, setInstasentConf)}
+ label={__('Custom Value', 'bit-integrations')}
+ className="mr-2"
+ type="text"
+ value={field.customValue}
+ placeholder={__('Custom Value', 'bit-integrations')}
+ formFields={formFields}
+ />
+ )}
+
+ {showCustomKeyInput ? (
+ handleFieldMapping(ev, i, instasentConf, setInstasentConf)}
+ />
+ ) : (
+
+ )}
+
+ {i >= requiredFlds.length && (
+ <>
+
+
+ >
+ )}
+
+
+ )
+}
diff --git a/frontend/src/components/AllIntegrations/Instasent/InstasentIntegLayout.jsx b/frontend/src/components/AllIntegrations/Instasent/InstasentIntegLayout.jsx
new file mode 100644
index 000000000..7330a7486
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/Instasent/InstasentIntegLayout.jsx
@@ -0,0 +1,227 @@
+import { create } from 'mutative'
+import MultiSelect from 'react-multiple-select-dropdown-lite'
+import { useRecoilValue } from 'recoil'
+import { $appConfigState } from '../../../GlobalStates'
+import { __ } from '../../../Utils/i18nwrap'
+import Loader from '../../Loaders/Loader'
+import { checkIsPro, getProLabel } from '../../Utilities/ProUtilHelpers'
+import { addFieldMap } from './IntegrationHelpers'
+import {
+ ACTIONS_WITH_DATASOURCE,
+ ACTIONS_WITH_PROJECT,
+ instasentRefreshFields,
+ refreshDatasources
+} from './InstasentCommonFunc'
+import InstasentActions from './InstasentActions'
+import InstasentFieldMap from './InstasentFieldMap'
+
+const actionOptions = [
+ {
+ value: 'send_sms',
+ label: __('Send SMS', 'bit-integrations'),
+ isPro: true
+ },
+ {
+ value: 'create_lookup',
+ label: __('Create Lookup', 'bit-integrations'),
+ isPro: true
+ },
+ {
+ value: 'create_datasource',
+ label: __('Create Datasource', 'bit-integrations'),
+ isPro: true
+ },
+ {
+ value: 'create_or_update_contact',
+ label: __('Create or Update Contact', 'bit-integrations'),
+ isPro: true
+ },
+ {
+ value: 'delete_contact',
+ label: __('Delete Contact', 'bit-integrations'),
+ isPro: true
+ },
+ {
+ value: 'create_contact_event',
+ label: __('Create Contact Event', 'bit-integrations'),
+ isPro: true
+ }
+]
+
+export default function InstasentIntegLayout({
+ formFields,
+ handleInput,
+ instasentConf,
+ setInstasentConf,
+ loading,
+ setLoading,
+ setSnackbar
+}) {
+ const btcbi = useRecoilValue($appConfigState)
+ const { isPro } = btcbi
+
+ const handleMainAction = value => {
+ const updatedConf = create(instasentConf, draftConf => {
+ draftConf.action = value
+ })
+
+ setInstasentConf(updatedConf)
+
+ if (value !== '') {
+ instasentRefreshFields(updatedConf, setInstasentConf, loading, setLoading)
+ }
+ }
+
+ const setDatasource = value => {
+ setInstasentConf(prev =>
+ create(prev, draftConf => {
+ draftConf.datasourceId = value
+ })
+ )
+ }
+
+ return (
+ <>
+
+
+ {__('Action:', 'bit-integrations')}
+ handleMainAction(value)}
+ options={actionOptions.map(action => ({
+ label: checkIsPro(isPro, action.isPro) ? action.label : getProLabel(action.label),
+ value: action.value,
+ disabled: !checkIsPro(isPro, action.isPro)
+ }))}
+ singleSelect
+ closeOnSelect
+ />
+
+
+
+ {ACTIONS_WITH_PROJECT.includes(instasentConf?.action) && (
+
+ {__('Project Id:', 'bit-integrations')}
+
+
+ )}
+
+ {ACTIONS_WITH_DATASOURCE.includes(instasentConf?.action) && (
+ <>
+
+
+ {__('Data Source:', 'bit-integrations')}
+ setDatasource(val)}
+ options={(instasentConf?.default?.datasources || []).map(ds => ({
+ label: ds?.name || ds?.id || '',
+ value: String(ds?.id || '')
+ }))}
+ singleSelect
+ closeOnSelect
+ />
+
+
+ >
+ )}
+
+ {loading.field && (
+
+ )}
+
+ {instasentConf?.action && !loading?.field && (
+ <>
+
+
+ {__('Field Map', 'bit-integrations')}
+
+
+
+
+
+
+
+ {__('Form Fields', 'bit-integrations')}
+
+
+ {__('Instasent Fields', 'bit-integrations')}
+
+
+
+ {instasentConf?.field_map.map((itm, i) => (
+
+ ))}
+
+
+
+
+
+
+ {instasentConf?.action === 'send_sms' && (
+ <>
+
+ {__('Utilities', 'bit-integrations')}
+
+
+
+ >
+ )}
+ >
+ )}
+ >
+ )
+}
diff --git a/frontend/src/components/AllIntegrations/Instasent/IntegrationHelpers.jsx b/frontend/src/components/AllIntegrations/Instasent/IntegrationHelpers.jsx
new file mode 100644
index 000000000..9a0438351
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/Instasent/IntegrationHelpers.jsx
@@ -0,0 +1,24 @@
+export const addFieldMap = (i, confTmp, setConf) => {
+ const newConf = { ...confTmp }
+ newConf.field_map.splice(i, 0, {})
+ setConf({ ...newConf })
+}
+
+export const delFieldMap = (i, confTmp, setConf) => {
+ const newConf = { ...confTmp }
+ if (newConf.field_map.length > 1) {
+ newConf.field_map.splice(i, 1)
+ }
+
+ setConf({ ...newConf })
+}
+
+export const handleFieldMapping = (event, index, conftTmp, setConf) => {
+ const newConf = { ...conftTmp }
+ newConf.field_map[index][event.target.name] = event.target.value
+
+ if (event.target.value === 'custom') {
+ newConf.field_map[index].customValue = ''
+ }
+ setConf({ ...newConf })
+}
diff --git a/frontend/src/components/AllIntegrations/IntegInfo.jsx b/frontend/src/components/AllIntegrations/IntegInfo.jsx
index d64be1fe2..2a7f15131 100644
--- a/frontend/src/components/AllIntegrations/IntegInfo.jsx
+++ b/frontend/src/components/AllIntegrations/IntegInfo.jsx
@@ -68,6 +68,7 @@ const GroundhoggAuthorization = lazy(() => import('./Groundhogg/GroundhoggAuthor
const SendFoxAuthorization = lazy(() => import('./SendFox/SendFoxAuthorization'))
const TwilioAuthorization = lazy(() => import('./Twilio/TwilioAuthorization'))
const MailerLiteAuthorization = lazy(() => import('./MailerLite/MailerLiteAuthorization'))
+const InstasentAuthorization = lazy(() => import('./Instasent/InstasentAuthorization'))
const VboutAuthorization = lazy(() => import('./Vbout/VboutAuthorization'))
const FreshdeskAuthorization = lazy(() => import('./Freshdesk/FreshdeskAuthorization'))
const GoogleContactsAuthorization = lazy(() => import('./GoogleContacts/GoogleContactsAuthorization'))
@@ -429,6 +430,8 @@ export default function IntegInfo() {
return
case 'MailerLite':
return
+ case 'Instasent':
+ return
case 'Vbout':
return
case 'Freshdesk':
diff --git a/frontend/src/components/AllIntegrations/NewInteg.jsx b/frontend/src/components/AllIntegrations/NewInteg.jsx
index 842ed6ac0..b6d931852 100644
--- a/frontend/src/components/AllIntegrations/NewInteg.jsx
+++ b/frontend/src/components/AllIntegrations/NewInteg.jsx
@@ -70,6 +70,7 @@ const Acumbamail = lazy(() => import('./Acumbamail/Acumbamail'))
const Groundhogg = lazy(() => import('./Groundhogg/Groundhogg'))
const SendFox = lazy(() => import('./SendFox/SendFox'))
const MailerLite = lazy(() => import('./MailerLite/MailerLite'))
+const Instasent = lazy(() => import('./Instasent/Instasent'))
const Vbout = lazy(() => import('./Vbout/Vbout'))
const WhatsApp = lazy(() => import('./WhatsApp/WhatsApp'))
const LearnDesh = lazy(() => import('./LearnDash/LearnDash'))
@@ -736,6 +737,15 @@ export default function NewInteg({ allIntegURL }) {
setFlow={setFlow}
/>
)
+ case 'Instasent':
+ return (
+
+ )
case 'Vbout':
return (
!inte.disable && (isPro || !inte.is_pro) && setAction(inte.type)}
role="button"
tabIndex="0"
- className={`btcd-inte-card inte-sm mr-4 mt-3 ${
- inte.disable && (isPro || !inte.is_pro) && 'btcd-inte-dis'
- } ${inte.is_pro && !isPro && 'btcd-inte-pro'}`}>
+ className={`btcd-inte-card inte-sm mr-4 mt-3 ${inte.disable && (isPro || !inte.is_pro) && 'btcd-inte-dis'
+ } ${inte.is_pro && !isPro && 'btcd-inte-pro'}`}>
{inte.is_pro && !isPro && (