Skip to content

feat(integrations): add Heffl CRM action with lead/client/deal support#169

Open
RishadAlam wants to merge 3 commits into
mainfrom
feat/hefflCRM
Open

feat(integrations): add Heffl CRM action with lead/client/deal support#169
RishadAlam wants to merge 3 commits into
mainfrom
feat/hefflCRM

Conversation

@RishadAlam
Copy link
Copy Markdown
Member

Description

This PR adds a new Heffl CRM action integration to Bit Integrations with end-to-end setup, editing, and execution support. Users can authorize with an API key, choose an action type (lead/client/deal), map fields, and use live-fetched Heffl CRM reference data (sources, stages, pipelines, clients, leads). It also wires the integration into action selection, integration info, and edit flows.

Motivation & Context

Users need a native way to send WordPress trigger data into Heffl CRM workflows without manual export/import. This change introduces a dedicated Heffl CRM action setup experience and backend endpoints to make configuration reliable and faster, especially for dynamic lists used during lead/deal mapping.

Related Links: (if applicable)

Type of Change

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

Key Changes

Backend

  • Added HefflCRMController with API-key authorization and refresh endpoints for lead sources, lead stages, pipelines, pipeline stages, clients, and leads.
  • Added paginated list-fetch helpers with cursor handling and safe pagination caps for dynamic Heffl CRM resources.
  • Added RecordApiHelper to build mapped payloads and route execution by selected action (create_lead, create_client, create_deal).
  • Added pro-hook based execution calls (heffl_crm_create_*) with clear fallback responses and integration logging.
  • Added Heffl CRM REST routes in backend routing.

Frontend

  • Added full Heffl CRM integration UI (HefflCRM.jsx) with 3-step setup flow and required-map validation before save.
  • Added authorization UI (HefflCRMAuthorization.jsx) with setup guidance and API-key validation.
  • Added dynamic action layout (HefflCRMIntegLayout.jsx) for lead/client/deal-specific settings and refresh controls.
  • Added field mapping component (HefflCRMFieldMap.jsx) supporting required/optional fields, custom values, and smart tags.
  • Added shared integration helpers (HefflCRMCommonFunc.js) for authorization, refresh, map generation, and map validation.
  • Added static action/field definitions for lead, client, deal, client types, and priority options.

Integration Wiring & Assets

  • Added Heffl CRM to action selection list.
  • Added Heffl CRM support in new integration, edit integration, and integration info screens.
  • Added hefflCRM.webp logo asset for integration cards/icons.

Checklist

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

Changelog

  • New Actions: Added Heffl CRM action support to create leads, clients, and deals from WordPress automations.
  • Feature: Added Heffl CRM API-key authorization flow in integration setup.
  • Improvement: Added dynamic refresh for lead sources, lead stages, pipelines, pipeline stages, clients, and leads during mapping.
  • Improvement: Added required-field mapping validation to prevent incomplete Heffl CRM configurations.

- Implemented backend routes for Heffl CRM authorization and data refresh.
- Created frontend components for editing and managing Heffl CRM integration.
- Added common functions for handling input, authorization, and data refresh.
- Developed field mapping functionality for Heffl CRM fields.
- Included static data for lead, client, and deal fields.
- Integrated loading indicators and snack messages for user feedback.
- Added a new image asset for Heffl CRM integration.
Copilot AI review requested due to automatic review settings May 25, 2026 11:03
Copy link
Copy Markdown

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

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 Heffl CRM, featuring backend controllers, API helpers, and a comprehensive frontend interface for managing leads, clients, and deals. The review feedback highlights several critical improvements for PHP 8.0+ compatibility, specifically the need to use is_wp_error() to validate API responses before accessing properties. Other recommendations include declaring methods as static when instance properties are not used and adding type checks during field map iteration to prevent potential runtime warnings.

wp_send_json_error(__('Failed to fetch pipeline stages', 'bit-integrations'), HttpHelper::$responseCode ?: 400);
}

$stages = \is_object($response) && isset($response->stages) ? $response->stages : [];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

According to the general rules, when handling responses that may return a WP_Error object, you should always use the is_wp_error() function to validate the response before accessing it as an object. This prevents potential issues in PHP 8.0+.

        $stages = \is_object($response) && !is_wp_error($response) && isset($response->stages) ? $response->stages : [];
References
  1. In PHP, when handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+.

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. Added is_wp_error guard:

$stages = \is_object($response) && !is_wp_error($response) && isset($response->stages) ? $response->stages : [];

Also added a WP_Error transport-failure branch earlier in refreshPipelineStages so we return a connection error before reaching the property access.


private static function extractList($response)
{
if (\is_object($response) && isset($response->items) && \is_array($response->items)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

As per the general rules, ensure the response is not a WP_Error before checking for properties like items.

        if (\is_object($response) && !is_wp_error($response) && isset($response->items) && \is_array($response->items)) {
References
  1. In PHP, when handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+.

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. Added is_wp_error guard in extractList:

if (\is_object($response) && !is_wp_error($response) && isset($response->items) && \is_array($response->items)) {


private static function nextCursor($response)
{
if (\is_object($response) && !empty($response->hasMore) && !empty($response->nextCursor)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

As per the general rules, ensure the response is not a WP_Error before checking for properties like hasMore or nextCursor.

        if (\is_object($response) && !is_wp_error($response) && !empty($response->hasMore) && !empty($response->nextCursor)) {
References
  1. In PHP, when handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+.

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. Added is_wp_error guard in nextCursor:

if (\is_object($response) && !is_wp_error($response) && !empty($response->hasMore) && !empty($response->nextCursor)) {

);
}

public function execute($integrationData, $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 execute method does not access any instance properties ($this). According to the general rules, it should be declared as static.

    public static function execute($integrationData, $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.

Keeping execute as an instance method. The Flow engine instantiates the controller and invokes it as an instance call (backend/Flow/Flow.php:524):

$handler = new $integration($flowData->id);
$handler->execute($flowData, $data);

Every other action controller in backend/Actions/* follows the same public function execute(...) convention. Changing only this one would diverge from the project-wide contract without a functional benefit.

Comment on lines +80 to +81
foreach ($fieldMap as $item) {
$triggerValue = $item->formField;
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

It is safer to verify that $item is an object before accessing its properties to avoid potential warnings or errors in PHP 8.0+ if the data structure is unexpected.

        foreach ($fieldMap as $item) {
            if (!\is_object($item)) {
                continue;
            }
            $triggerValue = $item->formField;

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. Added is_object guard before property access:

foreach ($fieldMap as $item) {
    if (!\is_object($item)) {
        continue;
    }
    $triggerValue = $item->formField;

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 25, 2026

🔍 WordPress Plugin Check Report

⚠️ Status: Passed with warnings

📊 Report

🎯 Total Issues ❌ Errors ⚠️ Warnings
1 0 1

⚠️ Warnings (1)

📁 readme.txt (1 warning)
📍 Line 🔖 Check 💬 Message
0 mismatched_plugin_name Plugin name "Bit integrations - Form Integration, Webhook, Spreadsheets, CRM, LMS & Email Automation" is different from the name declared in plugin header "Bit Integrations".

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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new Heffl CRM action integration (frontend setup/edit/info flows + backend AJAX endpoints) so users can authorize via API key, select an action (lead/client/deal), refresh reference data, map fields, and execute via Pro hooks.

Changes:

  • Wired Heffl CRM into action selection, new integration creation, edit integration, and integration info screens.
  • Implemented a new frontend integration UI (authorization, action-specific layout, field mapping, refresh helpers, static field definitions).
  • Added backend AJAX routes + controller for authorization and refreshing CRM reference lists; added execution helper that delegates to Pro hooks with logging.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
frontend/src/components/Flow/New/SelectAction.jsx Adds “Heffl CRM” to the action picker list.
frontend/src/components/AllIntegrations/NewInteg.jsx Lazy-loads and renders the new Heffl CRM integration setup flow.
frontend/src/components/AllIntegrations/IntegInfo.jsx Adds Heffl CRM authorization/info rendering in integration info view.
frontend/src/components/AllIntegrations/HefflCRM/staticData.js Defines Heffl CRM actions and field metadata for mapping and select options.
frontend/src/components/AllIntegrations/HefflCRM/HefflCRMIntegLayout.jsx Step 2 UI: action selection, dynamic reference-data selects, refresh buttons, and field-map section.
frontend/src/components/AllIntegrations/HefflCRM/HefflCRMFieldMap.jsx Field mapping row component with custom value + smart tag support.
frontend/src/components/AllIntegrations/HefflCRM/HefflCRMCommonFunc.js Common handlers for authorize/refresh + required-field mapping validation.
frontend/src/components/AllIntegrations/HefflCRM/HefflCRMAuthorization.jsx Step 1 UI for name + API key authorization and instructions.
frontend/src/components/AllIntegrations/HefflCRM/HefflCRM.jsx 3-step “new integration” wizard wiring for Heffl CRM.
frontend/src/components/AllIntegrations/HefflCRM/EditHefflCRM.jsx Edit screen for existing Heffl CRM action configs.
frontend/src/components/AllIntegrations/EditInteg.jsx Wires Heffl CRM into the edit-integration switch.
backend/Actions/HefflCRM/Routes.php Registers AJAX endpoints for authorize + refresh list data.
backend/Actions/HefflCRM/RecordApiHelper.php Builds mapped payload and dispatches execution via Pro hooks + logging.
backend/Actions/HefflCRM/HefflCRMController.php Implements authorize + refresh endpoints and action execution entrypoint.
Comments suppressed due to low confidence (2)

backend/Actions/HefflCRM/HefflCRMController.php:71

  • refreshPipelineStages() doesn’t handle HttpHelper::get() returning a WP_Error. In that case HttpHelper::$responseCode may be empty and the method will respond with a generic “Failed to fetch pipeline stages”/400, dropping the underlying error. Consider checking is_wp_error($response) and returning the error message (and an appropriate status) to make failures diagnosable.
        $response = HttpHelper::get(
            self::BASE_URL . '/pipelines/' . rawurlencode($requestParams->pipelineId),
            null,
            self::buildHeaders($requestParams->api_key)
        );

        if (HttpHelper::$responseCode < 200 || HttpHelper::$responseCode >= 300) {
            wp_send_json_error(__('Failed to fetch pipeline stages', 'bit-integrations'), HttpHelper::$responseCode ?: 400);
        }

backend/Actions/HefflCRM/HefflCRMController.php:146

  • fetchListItems() assumes HttpHelper::get() returns a decoded response object/array, but it doesn’t guard against WP_Error returns. If the request fails, the current code will likely emit “Failed to fetch from Heffl CRM” with a potentially empty/incorrect status code, and then continue cursor logic against an error object. Consider handling is_wp_error($response) immediately after the request and surfacing $response->get_error_message().
        do {
            $separator = strpos($path, '?') === false ? '?' : '&';
            $query = $separator . 'limit=' . self::PAGE_LIMIT . ($cursor ? '&cursor=' . rawurlencode($cursor) : '');
            $response = HttpHelper::get(self::BASE_URL . $path . $query, null, $headers);

            if (HttpHelper::$responseCode < 200 || HttpHelper::$responseCode >= 300) {
                wp_send_json_error(__('Failed to fetch from Heffl CRM', 'bit-integrations'), HttpHelper::$responseCode ?: 400);
            }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +48 to +59
draftConf.mainAction = value

delete draftConf.leadSources
delete draftConf.leadStages
delete draftConf.pipelines
delete draftConf.pipelineStages
delete draftConf.clients
delete draftConf.leads
delete draftConf.dealSourceId
delete draftConf.priority
delete draftConf.clientType

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 switch now also clears the stored selection IDs so each action starts clean:

delete draftConf.leadSourceId
delete draftConf.leadStageId
delete draftConf.pipelineId
delete draftConf.dealStageId
delete draftConf.dealSourceId
delete draftConf.clientId
delete draftConf.leadId
delete draftConf.priority
delete draftConf.clientType

Comment on lines +23 to +36
public static function hefflCRMAuthorize($requestParams)
{
if (empty($requestParams->api_key)) {
wp_send_json_error(__('API key is required', 'bit-integrations'), 400);
}

HttpHelper::get(self::BASE_URL . '/leads?limit=1', null, self::buildHeaders($requestParams->api_key));

if (HttpHelper::$responseCode >= 200 && HttpHelper::$responseCode < 300) {
wp_send_json_success(__('Authorization successful', 'bit-integrations'), 200);
}

wp_send_json_error(__('Invalid Heffl CRM API key', 'bit-integrations'), 400);
}
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 at all three locations (hefflCRMAuthorize, refreshPipelineStages, fetchListItems). Each HttpHelper::get call now captures the return value and surfaces a distinct connection-failure message before falling back to API-level errors:

$response = HttpHelper::get(self::BASE_URL . '/leads?limit=1', null, self::buildHeaders($requestParams->api_key));

if (is_wp_error($response)) {
    wp_send_json_error(__('Failed to connect to Heffl CRM: ', 'bit-integrations') . $response->get_error_message(), 400);
}

@RishadAlam
Copy link
Copy Markdown
Member Author

Reviewed Plugin Check report:

Error — backend/Actions/WpDataTables/RecordApiHelper.php:37 (missing translators comment):
Fixed locally — added // translators: %s is the plugin name above the __() call. Will be included in the next commit. Note: this file is outside PR #169's scope (HefflCRM), but bundling the fix since the check is currently failing.

Warning — readme.txt plugin name mismatch:
Pre-existing mismatch between header (Plugin Name: Bit Integrations) and readme heading (=== Bit integrations - Form Integration, Webhook, Spreadsheets, CRM, LMS & Email Automation ===). Not introduced by this PR — leaving as-is to avoid changing the WordPress.org-displayed name in an unrelated PR.

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