feat(integrations): add ConvertForce Popup Builder integration with campaign management#189
feat(integrations): add ConvertForce Popup Builder integration with campaign management#189RishadAlam wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
- In PHP, declare methods as static if they do not access any instance properties ($this) and are intended to be called statically.
There was a problem hiding this comment.
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)| 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) | ||
| }) | ||
| } |
There was a problem hiding this comment.
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
- 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.
There was a problem hiding this comment.
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')
})
})| 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' |
There was a problem hiding this comment.
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'There was a problem hiding this comment.
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).
| 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 }) |
There was a problem hiding this comment.
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
- 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.
There was a problem hiding this comment.
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 = []
}
})
)
}
}, [])
✅ WordPress Plugin Check Report
📊 ReportAll 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
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
Key Changes
Integrations
Frontend
Checklist
Changelog