+ saveActionConf({
+ flow,
+ setFlow,
+ allIntegURL,
+ conf: nextCrmConf,
+ navigate,
+ id,
+ edit: 1,
+ setIsLoading,
+ setSnackbar
+ })
+ }
+ disabled={!checkMappedFields(nextCrmConf)}
+ isLoading={isLoading}
+ dataConf={nextCrmConf}
+ setDataConf={setNextCrmConf}
+ formFields={formFields}
+ />
+
+
+ )
+}
diff --git a/frontend/src/components/AllIntegrations/NextCrm/NextCrm.jsx b/frontend/src/components/AllIntegrations/NextCrm/NextCrm.jsx
new file mode 100644
index 000000000..3f3b16e53
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/NextCrm/NextCrm.jsx
@@ -0,0 +1,130 @@
+import { useState } from 'react'
+import 'react-multiple-select-dropdown-lite/dist/index.css'
+import { useNavigate, useParams } from 'react-router'
+import BackIcn from '../../../Icons/BackIcn'
+import { __ } from '../../../Utils/i18nwrap'
+import SnackMsg from '../../Utilities/SnackMsg'
+import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers'
+import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree'
+import NextCrmAuthorization from './NextCrmAuthorization'
+import { checkMappedFields } from './NextCrmCommonFunc'
+import NextCrmIntegLayout from './NextCrmIntegLayout'
+import { needsCampaign, needsContactField, needsList, needsStatus, needsTag } from './staticData'
+
+const requiredSelect = {
+ selectedStatus: needsStatus,
+ selectedField: needsContactField,
+ selectedTag: needsTag,
+ selectedList: needsList,
+ selectedCampaign: needsCampaign
+}
+
+export default function NextCrm({ formFields, setFlow, flow, allIntegURL }) {
+ const navigate = useNavigate()
+ const { formID } = useParams()
+ const [isLoading, setIsLoading] = useState(false)
+ const [step, setStep] = useState(1)
+ const [snack, setSnackbar] = useState({ show: false })
+ const [nextCrmConf, setNextCrmConf] = useState({
+ name: 'NextCRM',
+ type: 'NextCrm',
+ field_map: [{ formField: '', nextCrmField: '' }],
+ actions: {},
+ mainAction: ''
+ })
+
+ const nextPage = val => {
+ setTimeout(() => {
+ document.getElementById('btcd-settings-wrp').scrollTop = 0
+ }, 300)
+
+ if (val === 3) {
+ const missingSelect = Object.keys(requiredSelect).find(
+ key => requiredSelect[key].includes(nextCrmConf.mainAction) && !nextCrmConf?.[key]
+ )
+
+ if (missingSelect) {
+ setSnackbar({
+ show: true,
+ msg: __('Please complete all required selections to continue.', 'bit-integrations')
+ })
+ return
+ }
+
+ if (!checkMappedFields(nextCrmConf)) {
+ setSnackbar({
+ show: true,
+ msg: __('Please map all required fields to continue.', 'bit-integrations')
+ })
+ return
+ }
+
+ if (nextCrmConf.name !== '' && nextCrmConf.field_map.length > 0) {
+ setStep(val)
+ }
+ } else {
+ setStep(val)
+ }
+ }
+
+ return (
+
+
+
+
+ {/* STEP 1 */}
+
+
+ {/* STEP 2 */}
+
+
+
+
+
+
+
+
+ {/* STEP 3 */}
+
+ saveIntegConfig(flow, setFlow, allIntegURL, nextCrmConf, navigate, '', '', setIsLoading)
+ }
+ isLoading={isLoading}
+ dataConf={nextCrmConf}
+ setDataConf={setNextCrmConf}
+ formFields={formFields}
+ />
+
+ )
+}
diff --git a/frontend/src/components/AllIntegrations/NextCrm/NextCrmActions.jsx b/frontend/src/components/AllIntegrations/NextCrm/NextCrmActions.jsx
new file mode 100644
index 000000000..61058a518
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/NextCrm/NextCrmActions.jsx
@@ -0,0 +1,204 @@
+/* eslint-disable no-param-reassign */
+
+import { useState } from 'react'
+import { create } from 'mutative'
+import MultiSelect from 'react-multiple-select-dropdown-lite'
+import { __ } from '../../../Utils/i18nwrap'
+import Loader from '../../Loaders/Loader'
+import ConfirmModal from '../../Utilities/ConfirmModal'
+import TableCheckBox from '../../Utilities/TableCheckBox'
+import 'react-multiple-select-dropdown-lite/dist/index.css'
+import {
+ refreshNextCrmContactStatuses,
+ refreshNextCrmContactTypes,
+ refreshNextCrmLists,
+ refreshNextCrmTags
+} from './NextCrmCommonFunc'
+import { activityStatusOptions, yesNoOptions } from './staticData'
+
+export default function NextCrmActions({ nextCrmConf, setNextCrmConf }) {
+ const [isLoading, setIsLoading] = useState(false)
+ const [actionMdl, setActionMdl] = useState({ show: false })
+ const action = nextCrmConf?.mainAction
+ const isContactSave = ['create_contact', 'update_contact', 'create_or_update_contact'].includes(action)
+
+ const actionHandler = type => {
+ setActionMdl({ show: type })
+
+ if (type === 'contact_type') refreshNextCrmContactTypes(setNextCrmConf, setIsLoading)
+ if (type === 'status') refreshNextCrmContactStatuses(setNextCrmConf, setIsLoading)
+ if (type === 'lists') refreshNextCrmLists(setNextCrmConf, setIsLoading)
+ if (type === 'tags') refreshNextCrmTags(setNextCrmConf, setIsLoading)
+ }
+
+ const clsActionMdl = () => setActionMdl({ show: false })
+
+ const setAction = (val, name) =>
+ setNextCrmConf(prevConf =>
+ create(prevConf, draftConf => {
+ if (!draftConf.utilities) {
+ draftConf.utilities = {}
+ }
+ draftConf.utilities[name] = val
+ })
+ )
+
+ const toOptions = list => (list ?? []).map(item => ({ label: item.label, value: String(item.value) }))
+
+ const renderModal = ({ type, title, options, valueName, isMulti = false, refresher }) => (
+
+
+ {title}
+ {isLoading ? (
+
+ ) : (
+
+ setAction(isMulti ? (val ? val.split(',') : []) : val, valueName)}
+ singleSelect={!isMulti}
+ closeOnSelect={!isMulti}
+ />
+ {refresher && (
+
+ )}
+
+ )}
+
+ )
+
+ return (
+
+ {isContactSave && (
+ <>
+
actionHandler('contact_type')}
+ className="wdt-200 mt-4 mr-2"
+ value="contact_type"
+ title={__('Contact Type', 'bit-integrations')}
+ subTitle={__('Set the contact type', 'bit-integrations')}
+ />
+ actionHandler('status')}
+ className="wdt-200 mt-4 mr-2"
+ value="status"
+ title={__('Contact Status', 'bit-integrations')}
+ subTitle={__('Set the contact status', 'bit-integrations')}
+ />
+ 0}
+ onChange={() => actionHandler('lists')}
+ className="wdt-200 mt-4 mr-2"
+ value="lists"
+ title={__('Lists', 'bit-integrations')}
+ subTitle={__('Assign the contact to lists', 'bit-integrations')}
+ />
+ 0}
+ onChange={() => actionHandler('tags')}
+ className="wdt-200 mt-4 mr-2"
+ value="tags"
+ title={__('Tags', 'bit-integrations')}
+ subTitle={__('Assign tags to the contact', 'bit-integrations')}
+ />
+ >
+ )}
+
+ {action === 'add_contact_activity' && (
+ actionHandler('activity_status')}
+ className="wdt-200 mt-4 mr-2"
+ value="activity_status"
+ title={__('Activity Status', 'bit-integrations')}
+ subTitle={__('Set the activity status', 'bit-integrations')}
+ />
+ )}
+
+ {action === 'send_campaign_email' && (
+ actionHandler('skip_already_sent')}
+ className="wdt-200 mt-4 mr-2"
+ value="skip_already_sent"
+ title={__('Skip If Already Sent', 'bit-integrations')}
+ subTitle={__('Do not queue a contact twice', 'bit-integrations')}
+ />
+ )}
+
+ {renderModal({
+ type: 'contact_type',
+ title: __('Contact Type', 'bit-integrations'),
+ options: toOptions(nextCrmConf?.allContactTypes),
+ valueName: 'selected_contact_type',
+ refresher: refreshNextCrmContactTypes
+ })}
+
+ {renderModal({
+ type: 'status',
+ title: __('Contact Status', 'bit-integrations'),
+ options: toOptions(nextCrmConf?.allContactStatuses),
+ valueName: 'selected_status',
+ refresher: refreshNextCrmContactStatuses
+ })}
+
+ {renderModal({
+ type: 'lists',
+ title: __('Lists', 'bit-integrations'),
+ options: toOptions(nextCrmConf?.allLists),
+ valueName: 'selected_lists',
+ isMulti: true,
+ refresher: refreshNextCrmLists
+ })}
+
+ {renderModal({
+ type: 'tags',
+ title: __('Tags', 'bit-integrations'),
+ options: toOptions(nextCrmConf?.allTags),
+ valueName: 'selected_tags',
+ isMulti: true,
+ refresher: refreshNextCrmTags
+ })}
+
+ {renderModal({
+ type: 'activity_status',
+ title: __('Activity Status', 'bit-integrations'),
+ options: activityStatusOptions,
+ valueName: 'selected_activity_status'
+ })}
+
+ {renderModal({
+ type: 'skip_already_sent',
+ title: __('Skip If Already Sent', 'bit-integrations'),
+ options: yesNoOptions,
+ valueName: 'selected_skip_already_sent'
+ })}
+
+ )
+}
diff --git a/frontend/src/components/AllIntegrations/NextCrm/NextCrmAuthorization.jsx b/frontend/src/components/AllIntegrations/NextCrm/NextCrmAuthorization.jsx
new file mode 100644
index 000000000..1b9bec68c
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/NextCrm/NextCrmAuthorization.jsx
@@ -0,0 +1,30 @@
+import { useCallback } from 'react'
+import { AUTH_TYPES } from '../../../Utils/connectionAuth'
+import { __ } from '../../../Utils/i18nwrap'
+import tutorialLinks from '../../../Utils/StaticData/tutorialLinks'
+import Authorization from '../../Connections/Authorization'
+
+export default function NextCrmAuthorization({ nextCrmConf, setNextCrmConf, step, nextPage, isInfo }) {
+ const setStep = useCallback(value => nextPage(value), [nextPage])
+ return (
+
+ )
+}
diff --git a/frontend/src/components/AllIntegrations/NextCrm/NextCrmCommonFunc.js b/frontend/src/components/AllIntegrations/NextCrm/NextCrmCommonFunc.js
new file mode 100644
index 000000000..671818cd5
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/NextCrm/NextCrmCommonFunc.js
@@ -0,0 +1,109 @@
+import { create } from 'mutative'
+import toast from 'react-hot-toast'
+import bitsFetch from '../../../Utils/bitsFetch'
+import { __ } from '../../../Utils/i18nwrap'
+
+export const handleInput = (e, nextCrmConf, setNextCrmConf) => {
+ const { name, value } = e.target
+
+ setNextCrmConf(prevConf =>
+ create(prevConf, draftConf => {
+ draftConf[name] = value
+ })
+ )
+}
+
+const refreshOptions =
+ (route, dataKey, confKey, successMsg, errorMsg) => (setNextCrmConf, setIsLoading) => {
+ setIsLoading(true)
+ bitsFetch(null, route)
+ .then(result => {
+ if (result && result?.success && result?.data?.[dataKey]) {
+ setNextCrmConf(prevConf =>
+ create(prevConf, draftConf => {
+ draftConf[confKey] = result.data[dataKey]
+ })
+ )
+
+ setIsLoading(false)
+ toast.success(successMsg)
+ return
+ }
+ setIsLoading(false)
+ toast.error(errorMsg)
+ })
+ .catch(() => setIsLoading(false))
+ }
+
+export const refreshNextCrmTags = refreshOptions(
+ 'refresh_next_crm_tags',
+ 'tags',
+ 'allTags',
+ __('All tags fetched successfully', 'bit-integrations'),
+ __('NextCRM tags fetch failed. Please try again', 'bit-integrations')
+)
+
+export const refreshNextCrmLists = refreshOptions(
+ 'refresh_next_crm_lists',
+ 'lists',
+ 'allLists',
+ __('All lists fetched successfully', 'bit-integrations'),
+ __('NextCRM lists fetch failed. Please try again', 'bit-integrations')
+)
+
+export const refreshNextCrmCampaigns = refreshOptions(
+ 'refresh_next_crm_campaigns',
+ 'campaigns',
+ 'allCampaigns',
+ __('All campaigns fetched successfully', 'bit-integrations'),
+ __('NextCRM campaigns fetch failed. Please try again', 'bit-integrations')
+)
+
+export const refreshNextCrmContactFields = refreshOptions(
+ 'refresh_next_crm_contact_fields',
+ 'contactFields',
+ 'allContactFields',
+ __('All contact fields fetched successfully', 'bit-integrations'),
+ __('NextCRM contact fields fetch failed. Please try again', 'bit-integrations')
+)
+
+export const refreshNextCrmContactTypes = refreshOptions(
+ 'refresh_next_crm_contact_types',
+ 'contactTypes',
+ 'allContactTypes',
+ __('All contact types fetched successfully', 'bit-integrations'),
+ __('NextCRM contact types fetch failed. Please try again', 'bit-integrations')
+)
+
+export const refreshNextCrmContactStatuses = refreshOptions(
+ 'refresh_next_crm_contact_statuses',
+ 'contactStatuses',
+ 'allContactStatuses',
+ __('All contact statuses fetched successfully', 'bit-integrations'),
+ __('NextCRM contact statuses fetch failed. Please try again', 'bit-integrations')
+)
+
+export const checkMappedFields = nextCrmConf => {
+ const mappedFields = nextCrmConf?.field_map
+ ? nextCrmConf.field_map.filter(
+ mappedField =>
+ !mappedField.formField ||
+ !mappedField.nextCrmField ||
+ (mappedField.formField === 'custom' && !mappedField.customValue)
+ )
+ : []
+ if (mappedFields.length > 0) {
+ return false
+ }
+ return true
+}
+
+export const generateMappedField = fields => {
+ const requiredFlds = fields.filter(fld => fld.required === true)
+ return requiredFlds.length > 0
+ ? requiredFlds.map(field => ({
+ formField: '',
+ nextCrmField: field.key
+ }))
+ : [{ formField: '', nextCrmField: '' }]
+}
diff --git a/frontend/src/components/AllIntegrations/NextCrm/NextCrmFieldMap.jsx b/frontend/src/components/AllIntegrations/NextCrm/NextCrmFieldMap.jsx
new file mode 100644
index 000000000..3760b5477
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/NextCrm/NextCrmFieldMap.jsx
@@ -0,0 +1,104 @@
+import { useRecoilValue } from 'recoil'
+import { $appConfigState } from '../../../GlobalStates'
+import { __, sprintf } from '../../../Utils/i18nwrap'
+import { SmartTagField } from '../../../Utils/StaticData/SmartTagField'
+import TagifyInput from '../../Utilities/TagifyInput'
+import {
+ addFieldMap,
+ delFieldMap,
+ handleCustomValue,
+ handleFieldMapping
+} from '../GlobalIntegrationHelper'
+
+export default function NextCrmFieldMap({ i, formFields, field, nextCrmConf, setNextCrmConf }) {
+ const btcbi = useRecoilValue($appConfigState)
+ const { isPro } = btcbi
+
+ const requiredFlds = nextCrmConf?.nextCrmFields?.filter(fld => fld.required === true) || []
+ const nonRequiredFlds = nextCrmConf?.nextCrmFields?.filter(fld => fld.required === false) || []
+
+ return (
+
+
+
+
+
+ {field.formField === 'custom' && (
+ handleCustomValue(e, i, nextCrmConf, setNextCrmConf)}
+ label={__('Custom Value', 'bit-integrations')}
+ className="mr-2"
+ type="text"
+ value={field.customValue}
+ placeholder={__('Custom Value', 'bit-integrations')}
+ formFields={formFields}
+ />
+ )}
+
+
+
+ {i >= requiredFlds.length && (
+ <>
+
+
+ >
+ )}
+
+
+ )
+}
diff --git a/frontend/src/components/AllIntegrations/NextCrm/NextCrmIntegLayout.jsx b/frontend/src/components/AllIntegrations/NextCrm/NextCrmIntegLayout.jsx
new file mode 100644
index 000000000..94414f635
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/NextCrm/NextCrmIntegLayout.jsx
@@ -0,0 +1,215 @@
+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/IntegrationHelpers'
+import NextCrmActions from './NextCrmActions'
+import {
+ generateMappedField,
+ refreshNextCrmCampaigns,
+ refreshNextCrmContactFields,
+ refreshNextCrmContactStatuses,
+ refreshNextCrmLists,
+ refreshNextCrmTags
+} from './NextCrmCommonFunc'
+import NextCrmFieldMap from './NextCrmFieldMap'
+import {
+ hasUtilities,
+ modules,
+ needsCampaign,
+ needsContactField,
+ needsList,
+ needsStatus,
+ needsTag,
+ nextCrmStaticData
+} from './staticData'
+
+export default function NextCrmIntegLayout({
+ formID,
+ formFields,
+ nextCrmConf,
+ setNextCrmConf,
+ isLoading,
+ setIsLoading,
+ setSnackbar
+}) {
+ const btcbi = useRecoilValue($appConfigState)
+ const { isPro } = btcbi
+ const action = nextCrmConf?.mainAction
+
+ const setField = (key, value) =>
+ setNextCrmConf(prevConf =>
+ create(prevConf, draftConf => {
+ draftConf[key] = value
+ })
+ )
+
+ const handleMainAction = value => {
+ setNextCrmConf(prevConf =>
+ create(prevConf, draftConf => {
+ draftConf.mainAction = value
+ draftConf.nextCrmFields = nextCrmStaticData[value] ?? []
+ draftConf.field_map = generateMappedField(draftConf.nextCrmFields)
+ })
+ )
+
+ if (needsStatus.includes(value)) refreshNextCrmContactStatuses(setNextCrmConf, setIsLoading)
+ if (needsContactField.includes(value)) refreshNextCrmContactFields(setNextCrmConf, setIsLoading)
+ if (needsTag.includes(value)) refreshNextCrmTags(setNextCrmConf, setIsLoading)
+ if (needsList.includes(value)) refreshNextCrmLists(setNextCrmConf, setIsLoading)
+ if (needsCampaign.includes(value)) refreshNextCrmCampaigns(setNextCrmConf, setIsLoading)
+ }
+
+ const toOptions = list => (list ?? []).map(item => ({ label: item.label, value: String(item.value) }))
+
+ const renderSelect = (title, label, confKey, optionsKey, refresher) => (
+ <>
+
+
+ {label}
+ setField(confKey, value)}
+ singleSelect
+ closeOnSelect
+ />
+
+
+ >
+ )
+
+ return (
+ <>
+
+
+ {__('Action:', 'bit-integrations')}
+ handleMainAction(value)}
+ options={modules?.map(module => ({
+ label: checkIsPro(isPro, module.is_pro) ? module.label : getProLabel(module.label),
+ value: module.name,
+ disabled: !checkIsPro(isPro, module.is_pro)
+ }))}
+ singleSelect
+ closeOnSelect
+ />
+
+
+ {needsStatus.includes(action) &&
+ renderSelect(
+ 'selectedStatus',
+ __('Status:', 'bit-integrations'),
+ 'selectedStatus',
+ 'allContactStatuses',
+ refreshNextCrmContactStatuses
+ )}
+
+ {needsContactField.includes(action) &&
+ renderSelect(
+ 'selectedField',
+ __('Contact Field:', 'bit-integrations'),
+ 'selectedField',
+ 'allContactFields',
+ refreshNextCrmContactFields
+ )}
+
+ {needsTag.includes(action) &&
+ renderSelect(
+ 'selectedTag',
+ __('Tag:', 'bit-integrations'),
+ 'selectedTag',
+ 'allTags',
+ refreshNextCrmTags
+ )}
+
+ {needsList.includes(action) &&
+ renderSelect(
+ 'selectedList',
+ __('List:', 'bit-integrations'),
+ 'selectedList',
+ 'allLists',
+ refreshNextCrmLists
+ )}
+
+ {needsCampaign.includes(action) &&
+ renderSelect(
+ 'selectedCampaign',
+ __('Campaign:', 'bit-integrations'),
+ 'selectedCampaign',
+ 'allCampaigns',
+ refreshNextCrmCampaigns
+ )}
+
+ {isLoading && (
+
+ )}
+
+ {action && nextCrmConf?.nextCrmFields && (
+
+
{__('Map Fields', 'bit-integrations')}
+
+
+
+ {__('Form Fields', 'bit-integrations')}
+
+
+ {__('NextCRM Fields', 'bit-integrations')}
+
+
+
+ {nextCrmConf?.field_map?.map((itm, i) => (
+
+ ))}
+
+
+
+
+
+
+ )}
+
+ {action && nextCrmConf?.nextCrmFields && hasUtilities.includes(action) && (
+
+
{__('Utilities', 'bit-integrations')}
+
+
+
+ )}
+ >
+ )
+}
diff --git a/frontend/src/components/AllIntegrations/NextCrm/staticData.js b/frontend/src/components/AllIntegrations/NextCrm/staticData.js
new file mode 100644
index 000000000..7d2da8ba5
--- /dev/null
+++ b/frontend/src/components/AllIntegrations/NextCrm/staticData.js
@@ -0,0 +1,176 @@
+import { __ } from '../../../Utils/i18nwrap'
+
+export const modules = [
+ { name: 'create_contact', label: __('Create Contact', 'bit-integrations'), is_pro: true },
+ { name: 'update_contact', label: __('Update Contact', 'bit-integrations'), is_pro: true },
+ {
+ name: 'create_or_update_contact',
+ label: __('Create or Update Contact', 'bit-integrations'),
+ is_pro: true
+ },
+ { name: 'delete_contact', label: __('Delete Contact', 'bit-integrations'), is_pro: true },
+ {
+ name: 'change_contact_status',
+ label: __('Change Contact Status', 'bit-integrations'),
+ is_pro: true
+ },
+ {
+ name: 'update_contact_field',
+ label: __('Update Contact Field', 'bit-integrations'),
+ is_pro: true
+ },
+ { name: 'set_contact_meta', label: __('Set Contact Meta', 'bit-integrations'), is_pro: true },
+ {
+ name: 'delete_contact_meta',
+ label: __('Delete Contact Meta', 'bit-integrations'),
+ is_pro: true
+ },
+ {
+ name: 'add_contact_activity',
+ label: __('Add Contact Activity', 'bit-integrations'),
+ is_pro: true
+ },
+ { name: 'create_tag', label: __('Create Tag', 'bit-integrations'), is_pro: true },
+ { name: 'add_tag_to_contact', label: __('Add Tag to Contact', 'bit-integrations'), is_pro: true },
+ {
+ name: 'remove_tag_from_contact',
+ label: __('Remove Tag from Contact', 'bit-integrations'),
+ is_pro: true
+ },
+ { name: 'create_list', label: __('Create List', 'bit-integrations'), is_pro: true },
+ {
+ name: 'add_contact_to_list',
+ label: __('Add Contact to List', 'bit-integrations'),
+ is_pro: true
+ },
+ {
+ name: 'remove_contact_from_list',
+ label: __('Remove Contact from List', 'bit-integrations'),
+ is_pro: true
+ },
+ {
+ name: 'send_campaign_email',
+ label: __('Send Campaign Email to Contact', 'bit-integrations'),
+ is_pro: true
+ }
+]
+
+const ContactEmailField = {
+ key: 'contact_email',
+ label: __('Contact Email', 'bit-integrations'),
+ required: true
+}
+
+const AdditionalFields = [
+ { key: 'mobile', label: __('Mobile', 'bit-integrations'), required: false },
+ { key: 'source', label: __('Source', 'bit-integrations'), required: false },
+ { key: 'date_of_birth', label: __('Date of Birth', 'bit-integrations'), required: false },
+ { key: 'gender', label: __('Gender', 'bit-integrations'), required: false },
+ { key: 'address', label: __('Address', 'bit-integrations'), required: false },
+ { key: 'city', label: __('City', 'bit-integrations'), required: false },
+ { key: 'state', label: __('State', 'bit-integrations'), required: false },
+ { key: 'zip', label: __('Zip', 'bit-integrations'), required: false },
+ { key: 'country', label: __('Country', 'bit-integrations'), required: false }
+]
+
+const ContactProfileFields = [
+ { key: 'first_name', label: __('First Name', 'bit-integrations'), required: false },
+ { key: 'last_name', label: __('Last Name', 'bit-integrations'), required: false },
+ { key: 'photo', label: __('Photo URL', 'bit-integrations'), required: false },
+ { key: 'rating', label: __('Rating', 'bit-integrations'), required: false },
+ ...AdditionalFields
+]
+
+export const CreateContactFields = [
+ { key: 'email_address', label: __('Email Address', 'bit-integrations'), required: true },
+ ...ContactProfileFields
+]
+
+export const UpdateContactFields = [
+ ContactEmailField,
+ { key: 'new_email_address', label: __('New Email Address', 'bit-integrations'), required: false },
+ ...ContactProfileFields
+]
+
+export const ContactEmailOnlyFields = [ContactEmailField]
+
+export const UpdateContactFieldFields = [
+ ContactEmailField,
+ { key: 'value', label: __('Value', 'bit-integrations'), required: true }
+]
+
+export const SetContactMetaFields = [
+ ContactEmailField,
+ { key: 'meta_key', label: __('Meta Key', 'bit-integrations'), required: true },
+ { key: 'meta_value', label: __('Meta Value', 'bit-integrations'), required: true }
+]
+
+export const DeleteContactMetaFields = [
+ ContactEmailField,
+ { key: 'meta_key', label: __('Meta Key', 'bit-integrations'), required: true }
+]
+
+export const ContactActivityFields = [
+ ContactEmailField,
+ { key: 'title', label: __('Title', 'bit-integrations'), required: true },
+ { key: 'description', label: __('Description', 'bit-integrations'), required: false }
+]
+
+export const TaxonomyFields = [
+ { key: 'title', label: __('Title', 'bit-integrations'), required: true },
+ { key: 'name', label: __('Slug', 'bit-integrations'), required: false },
+ { key: 'description', label: __('Description', 'bit-integrations'), required: false }
+]
+
+export const CampaignEmailFields = [
+ ContactEmailField,
+ { key: 'body', label: __('Email Body', 'bit-integrations'), required: true },
+ { key: 'subject', label: __('Subject', 'bit-integrations'), required: false }
+]
+
+export const nextCrmStaticData = {
+ create_contact: CreateContactFields,
+ create_or_update_contact: CreateContactFields,
+ update_contact: UpdateContactFields,
+ delete_contact: ContactEmailOnlyFields,
+ change_contact_status: ContactEmailOnlyFields,
+ update_contact_field: UpdateContactFieldFields,
+ set_contact_meta: SetContactMetaFields,
+ delete_contact_meta: DeleteContactMetaFields,
+ add_contact_activity: ContactActivityFields,
+ create_tag: TaxonomyFields,
+ create_list: TaxonomyFields,
+ add_tag_to_contact: ContactEmailOnlyFields,
+ remove_tag_from_contact: ContactEmailOnlyFields,
+ add_contact_to_list: ContactEmailOnlyFields,
+ remove_contact_from_list: ContactEmailOnlyFields,
+ send_campaign_email: CampaignEmailFields
+}
+
+// Fixed option sets — rendered as selects, never mapped.
+export const activityStatusOptions = [
+ { label: __('Active', 'bit-integrations'), value: 'active' },
+ { label: __('Deactive', 'bit-integrations'), value: 'deactive' },
+ { label: __('Delete', 'bit-integrations'), value: 'delete' }
+]
+
+export const yesNoOptions = [
+ { label: __('No', 'bit-integrations'), value: 'no' },
+ { label: __('Yes', 'bit-integrations'), value: 'yes' }
+]
+
+// Required config selects → IntegLayout.
+export const needsStatus = ['change_contact_status']
+export const needsContactField = ['update_contact_field']
+export const needsTag = ['add_tag_to_contact', 'remove_tag_from_contact']
+export const needsList = ['add_contact_to_list', 'remove_contact_from_list']
+export const needsCampaign = ['send_campaign_email']
+
+// Optional config selects → Utilities.
+export const hasUtilities = [
+ 'create_contact',
+ 'update_contact',
+ 'create_or_update_contact',
+ 'add_contact_activity',
+ 'send_campaign_email'
+]
diff --git a/frontend/src/components/Flow/New/SelectAction.jsx b/frontend/src/components/Flow/New/SelectAction.jsx
index f2efc8ff1..07424a2c1 100644
--- a/frontend/src/components/Flow/New/SelectAction.jsx
+++ b/frontend/src/components/Flow/New/SelectAction.jsx
@@ -164,6 +164,7 @@ export default function SelectAction() {
{ type: 'ZagoMail', is_pro: false },
{ type: 'Drip', is_pro: false },
{ type: 'Newsletter', is_pro: false },
+ { type: 'NextCrm', name: 'NextCRM', is_pro: true },
{ type: 'SureDash', is_pro: true },
{ type: 'SureMembers', is_pro: false },
{ type: 'Mailster', is_pro: false },
diff --git a/frontend/src/resource/img/integ/nextCrm.webp b/frontend/src/resource/img/integ/nextCrm.webp
new file mode 100644
index 000000000..54c00fabe
Binary files /dev/null and b/frontend/src/resource/img/integ/nextCrm.webp differ