Skip to content

feat(integrations): add ConvertForce Popup Builder integration with campaign management#189

Open
RishadAlam wants to merge 6 commits into
mainfrom
feat/convertforce
Open

feat(integrations): add ConvertForce Popup Builder integration with campaign management#189
RishadAlam wants to merge 6 commits into
mainfrom
feat/convertforce

Conversation

@RishadAlam

Copy link
Copy Markdown
Member

Description

Adds ConvertForce Popup Builder as a new integration with full campaign management support. Users can create, update, update status, and delete campaigns directly from their automation flows.

Motivation & Context

This integration enables Bit Integrations users to manage ConvertForce popup campaigns without leaving WordPress, automating campaign lifecycle tasks triggered by form submissions and other events.

Related Links: (if applicable)

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 📚 Documentation update
  • ⚡ Improvement
  • 🔄 Code refactor

Key Changes

Integrations

  • Added ConvertForce integration with OAuth-free activation check (detects ConvertForce Popup Builder plugin)
  • Added Create Campaign action — maps title, content, status, display options, and display conditions
  • Added Update Campaign action — maps campaign ID plus updatable fields
  • Added Update Campaign Status action — maps campaign ID and new status
  • Added Delete Campaign action — maps campaign ID with a Force Delete utility checkbox
  • Added frontend components: step wizard, authorization screen, field mapping, utilities toggle, and edit mode

Frontend

  • Added lazy-loaded import of ConvertForce in NewInteg, EditInteg, IntegInfo, and SelectAction

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Tests added/updated
  • Documentation updated if needed
  • README updated if needed

Changelog

  • New Actions: ConvertForce Popup Builder: 4 new actions added — Create Campaign, Update Campaign, Update Campaign Status, Delete Campaign (Pro).

Copilot AI review requested due to automatic review settings July 2, 2026 07:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new integration for the ConvertForce Popup Builder, adding the necessary backend controllers, routes, and helpers alongside frontend React components for configuration, authorization, and editing. The review feedback highlights several key improvements: declaring generateReqDataFromFieldMap as static in RecordApiHelper.php to prevent PHP 8.x deprecation warnings, adding a .catch block to the authorization request in ConvertForceAuthorization.jsx for robust error handling, and implementing a useEffect hook in EditConvertForce.jsx to rebuild non-persisted configuration fields on mount to avoid component crashes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

return $response;
}

private function generateReqDataFromFieldMap($fieldMap, $fieldValues)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The helper method generateReqDataFromFieldMap is called statically on line 46 (static::generateReqDataFromFieldMap(...)), but it is declared as a non-static private method. To prevent PHP deprecation warnings or errors (especially in PHP 8.x), it should be explicitly declared as static.

    private static function generateReqDataFromFieldMap($fieldMap, $fieldValues)
References
  1. In PHP, declare methods as static if they do not access any instance properties ($this) and are intended to be called statically.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The method doesn't access any instance properties ($this), so declaring it private static is correct and prevents PHP deprecation warnings in PHP 8.x.

private static function generateReqDataFromFieldMap($fieldMap, $fieldValues)

Comment on lines +20 to +33
const authorizeHandler = () => {
setIsLoading('auth')
bitsFetch({}, 'convert_force_authorize').then(result => {
if (result?.success) {
setIsAuthorized(true)
setSnackbar({
show: true,
msg: __('Connected with ConvertForce Popup Builder Successfully', 'bit-integrations')
})
}
setIsLoading(false)
setShowAuthMsg(true)
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When handling asynchronous API requests, always append a .catch block to ensure that loading states are reset and error messages are displayed if the request fails or encounters a network error.

  const authorizeHandler = () => {
    setIsLoading('auth')
    bitsFetch({}, 'convert_force_authorize')
      .then(result => {
        if (result?.success) {
          setIsAuthorized(true)
          setSnackbar({
            show: true,
            msg: __('Connected with ConvertForce Popup Builder Successfully', 'bit-integrations')
          })
        }
        setIsLoading(false)
        setShowAuthMsg(true)
      })
      .catch(() => {
        setIsLoading(false)
        setSnackbar({
          show: true,
          msg: __('Failed to authorize ConvertForce Popup Builder', 'bit-integrations')
        })
      })
  }
References
  1. When handling asynchronous API requests, always append a .catch block to ensure that loading states are reset and error messages are displayed if the request fails or encounters a network error.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added .catch() handler to reset loading state and show an error message on network failure. This prevents the UI from getting stuck in a loading state if the request fails.

bitsFetch({}, 'convert_force_authorize')
  .then(result => { ... })
  .catch(() => {
    setIsLoading(false)
    setSnackbar({
      show: true,
      msg: __('Failed to authorize ConvertForce Popup Builder', 'bit-integrations')
    })
  })

Comment on lines +1 to +11
import { 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 } from './ConvertForceCommonFunc'
import ConvertForceIntegLayout from './ConvertForceIntegLayout'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To support rebuilding non-persisted configuration fields on mount, we need to import useEffect from 'react', create from 'mutative', and the static fields from ./staticData.

import { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router'
import { useRecoilState, useRecoilValue } from 'recoil'
import { create } from 'mutative'
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 } from './ConvertForceCommonFunc'
import ConvertForceIntegLayout from './ConvertForceIntegLayout'
import {
  CampaignDeleteFields,
  CampaignFields,
  CampaignStatusFields,
  CampaignUpdateFields
} from './staticData'

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the required imports: useEffect from React, create from mutative, and static field definitions from ./staticData. Used in the useEffect mount handler (see next thread).

Comment on lines +13 to +21
export default function EditConvertForce({ allIntegURL }) {
const navigate = useNavigate()
const { id } = useParams()

const [convertForceConf, setConvertForceConf] = useRecoilState($actionConf)
const [flow, setFlow] = useRecoilState($newFlow)
const formFields = useRecoilValue($formFields)
const [isLoading, setIsLoading] = useState(false)
const [snack, setSnackbar] = useState({ show: false })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When editing an integration, non-persisted configuration fields (like static fields) must be rebuilt on mount from the saved action without modifying the saved field mappings. This prevents empty options and component crashes.

export default function EditConvertForce({ allIntegURL }) {
  const navigate = useNavigate()
  const { id } = useParams()

  const [convertForceConf, setConvertForceConf] = useRecoilState($actionConf)
  const [flow, setFlow] = useRecoilState($newFlow)
  const formFields = useRecoilValue($formFields)
  const [isLoading, setIsLoading] = useState(false)
  const [snack, setSnackbar] = useState({ show: false })

  useEffect(() => {
    if (convertForceConf?.mainAction) {
      setConvertForceConf(prevConf =>
        create(prevConf, draftConf => {
          switch (convertForceConf.mainAction) {
            case 'createCampaign':
              draftConf.convertForceFields = CampaignFields
              break
            case 'updateCampaign':
              draftConf.convertForceFields = CampaignUpdateFields
              break
            case 'updateCampaignStatus':
              draftConf.convertForceFields = CampaignStatusFields
              break
            case 'deleteCampaign':
              draftConf.convertForceFields = CampaignDeleteFields
              break
            default:
              draftConf.convertForceFields = []
          }
        })
      )
    }
  }, [])
References
  1. When editing an integration, ensure that non-persisted configuration fields (like static fields) are rebuilt on mount from the saved action without modifying the saved field mappings, to prevent empty options and component crashes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added useEffect to rebuild non-persisted convertForceFields on mount. This ensures the field mapping dropdowns have the correct options when editing a saved integration, even if the static field definitions are not present in the persisted config.

useEffect(() => {
  if (convertForceConf?.mainAction) {
    setConvertForceConf(prevConf =>
      create(prevConf, draftConf => {
        switch (convertForceConf.mainAction) {
          case 'createCampaign':
            draftConf.convertForceFields = CampaignFields
            break
          case 'updateCampaign':
            draftConf.convertForceFields = CampaignUpdateFields
            break
          case 'updateCampaignStatus':
            draftConf.convertForceFields = CampaignStatusFields
            break
          case 'deleteCampaign':
            draftConf.convertForceFields = CampaignDeleteFields
            break
          default:
            draftConf.convertForceFields = []
        }
      })
    )
  }
}, [])

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

- Make generateReqDataFromFieldMap static to match static call
- Add .catch() handler to authorization promise
- Add useEffect to rebuild convertForceFields on edit mount
Copilot AI review requested due to automatic review settings July 14, 2026 05:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 14, 2026 05:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants