From 9143ac3ca90bd310661afcf4722b6a3283ee1b4b Mon Sep 17 00:00:00 2001 From: Rishad Alam Date: Wed, 17 Jun 2026 17:34:18 +0600 Subject: [PATCH 1/4] feat(wsms): add WSMS (WP SMS) integration - 5 wp_hook triggers: SMS Sent, OTP Generated, Subscriber Added, Subscriber Verified, Number Unsubscribed (custom_form_submission, logic in Pro) - 7 actions (all Pro): Send SMS; Add/Update/Delete Subscriber; Add/Update/Delete Group. Update Subscriber is identified by mobile number and Update/Delete Group by group_id via the field map; subscriber actions expose group + status dropdowns - Free fires bit_integrations_wsms_* filters and serves group/status dropdown data; registered in AllTriggersName, the New/Edit/Info integration switches, SelectAction, plus the integration icon --- backend/Actions/Wsms/RecordApiHelper.php | 137 +++++++++++++ backend/Actions/Wsms/Routes.php | 12 ++ backend/Actions/Wsms/WsmsController.php | 89 ++++++++ backend/Core/Util/AllTriggersName.php | 1 + .../components/AllIntegrations/EditInteg.jsx | 3 + .../components/AllIntegrations/IntegInfo.jsx | 3 + .../components/AllIntegrations/NewInteg.jsx | 10 + .../AllIntegrations/Wsms/EditWsms.jsx | 76 +++++++ .../components/AllIntegrations/Wsms/Wsms.jsx | 106 ++++++++++ .../Wsms/WsmsAuthorization.jsx | 112 ++++++++++ .../AllIntegrations/Wsms/WsmsCommonFunc.js | 82 ++++++++ .../AllIntegrations/Wsms/WsmsFieldMap.jsx | 104 ++++++++++ .../AllIntegrations/Wsms/WsmsIntegLayout.jsx | 191 ++++++++++++++++++ .../AllIntegrations/Wsms/staticData.js | 54 +++++ .../src/components/Flow/New/SelectAction.jsx | 1 + frontend/src/resource/img/integ/wsms.svg | 5 + frontend/src/resource/img/integ/wsms.webp | Bin 0 -> 2806 bytes 17 files changed, 986 insertions(+) create mode 100644 backend/Actions/Wsms/RecordApiHelper.php create mode 100644 backend/Actions/Wsms/Routes.php create mode 100644 backend/Actions/Wsms/WsmsController.php create mode 100644 frontend/src/components/AllIntegrations/Wsms/EditWsms.jsx create mode 100644 frontend/src/components/AllIntegrations/Wsms/Wsms.jsx create mode 100644 frontend/src/components/AllIntegrations/Wsms/WsmsAuthorization.jsx create mode 100644 frontend/src/components/AllIntegrations/Wsms/WsmsCommonFunc.js create mode 100644 frontend/src/components/AllIntegrations/Wsms/WsmsFieldMap.jsx create mode 100644 frontend/src/components/AllIntegrations/Wsms/WsmsIntegLayout.jsx create mode 100644 frontend/src/components/AllIntegrations/Wsms/staticData.js create mode 100644 frontend/src/resource/img/integ/wsms.svg create mode 100644 frontend/src/resource/img/integ/wsms.webp diff --git a/backend/Actions/Wsms/RecordApiHelper.php b/backend/Actions/Wsms/RecordApiHelper.php new file mode 100644 index 00000000..a60a6415 --- /dev/null +++ b/backend/Actions/Wsms/RecordApiHelper.php @@ -0,0 +1,137 @@ +_integrationDetails = $integrationDetails; + $this->_integrationID = $integId; + } + + /** + * Execute the integration + * + * @param array $fieldValues Field values from form + * @param array $fieldMap Field mapping + * @param array $utilities Actions to perform + * + * @return array + */ + public function execute($fieldValues, $fieldMap, $utilities) + { + if (!\defined('WP_SMS_VERSION')) { + return [ + 'success' => false, + 'message' => __('WSMS (WP SMS) is not installed or activated', 'bit-integrations') + ]; + } + + $fieldData = static::generateReqDataFromFieldMap($fieldMap, $fieldValues); + + $mainAction = $this->_integrationDetails->mainAction ?? 'send_sms'; + + $defaultResponse = [ + 'success' => false, + // translators: %s: Plugin name + 'message' => wp_sprintf(__('%s plugin is not installed or activate', 'bit-integrations'), 'Bit Integrations Pro') + ]; + + // Route to appropriate action method + switch ($mainAction) { + case 'send_sms': + $response = Hooks::apply(Config::withPrefix('wsms_send_sms'), $defaultResponse, $fieldData); + $type = 'sms'; + $actionType = 'send_sms'; + + break; + + case 'add_subscriber': + $response = Hooks::apply(Config::withPrefix('wsms_add_subscriber'), $defaultResponse, $fieldData, $this->_integrationDetails); + $type = 'subscriber'; + $actionType = 'add_subscriber'; + + break; + + case 'update_subscriber': + $response = Hooks::apply(Config::withPrefix('wsms_update_subscriber'), $defaultResponse, $fieldData, $this->_integrationDetails); + $type = 'subscriber'; + $actionType = 'update_subscriber'; + + break; + + case 'delete_subscriber': + $response = Hooks::apply(Config::withPrefix('wsms_delete_subscriber'), $defaultResponse, $fieldData, $this->_integrationDetails); + $type = 'subscriber'; + $actionType = 'delete_subscriber'; + + break; + + case 'add_group': + $response = Hooks::apply(Config::withPrefix('wsms_add_group'), $defaultResponse, $fieldData); + $type = 'group'; + $actionType = 'add_group'; + + break; + + case 'update_group': + $response = Hooks::apply(Config::withPrefix('wsms_update_group'), $defaultResponse, $fieldData); + $type = 'group'; + $actionType = 'update_group'; + + break; + + case 'delete_group': + $response = Hooks::apply(Config::withPrefix('wsms_delete_group'), $defaultResponse, $fieldData); + $type = 'group'; + $actionType = 'delete_group'; + + break; + + default: + $response = [ + 'success' => false, + 'message' => __('Invalid action', 'bit-integrations') + ]; + $type = 'WSMS'; + $actionType = 'unknown'; + + break; + } + + $responseType = isset($response['success']) && $response['success'] ? 'success' : 'error'; + LogHandler::save($this->_integrationID, ['type' => $type, 'type_name' => $actionType], $responseType, $response); + + return $response; + } + + private function generateReqDataFromFieldMap($fieldMap, $fieldValues) + { + $dataFinal = []; + foreach ($fieldMap as $item) { + $triggerValue = $item->formField; + $actionValue = $item->wsmsField; + + $dataFinal[$actionValue] = $triggerValue === 'custom' && isset($item->customValue) ? Common::replaceFieldWithValue($item->customValue, $fieldValues) : $fieldValues[$triggerValue] ?? ''; + } + + return $dataFinal; + } +} diff --git a/backend/Actions/Wsms/Routes.php b/backend/Actions/Wsms/Routes.php new file mode 100644 index 00000000..f433d6d0 --- /dev/null +++ b/backend/Actions/Wsms/Routes.php @@ -0,0 +1,12 @@ + (int) $group->ID, + 'label' => $group->name, + ]; + } + } + + $response['groups'] = $groups; + wp_send_json_success($response, 200); + } + + public function refreshStatuses() + { + self::isExists(); + + $statuses = [ + (object) ['value' => '1', 'label' => 'Active'], + (object) ['value' => '0', 'label' => 'Inactive'], + ]; + + $response['statuses'] = $statuses; + wp_send_json_success($response, 200); + } + + public function execute($integrationData, $fieldValues) + { + $integrationDetails = $integrationData->flow_details; + $integId = $integrationData->id; + $fieldMap = $integrationDetails->field_map; + $utilities = isset($integrationDetails->utilities) ? $integrationDetails->utilities : []; + + if (empty($fieldMap)) { + return new WP_Error('field_map_empty', __('Field map is empty', 'bit-integrations')); + } + + $recordApiHelper = new RecordApiHelper($integrationDetails, $integId); + $wsmsResponse = $recordApiHelper->execute($fieldValues, $fieldMap, $utilities); + + if (is_wp_error($wsmsResponse)) { + return $wsmsResponse; + } + + return $wsmsResponse; + } +} diff --git a/backend/Core/Util/AllTriggersName.php b/backend/Core/Util/AllTriggersName.php index a611756e..ee40488e 100644 --- a/backend/Core/Util/AllTriggersName.php +++ b/backend/Core/Util/AllTriggersName.php @@ -58,6 +58,7 @@ public static function allTriggersName() 'FluentBooking' => ['name' => 'Fluent Booking', 'isPro' => true, 'is_active' => false], 'CreatorLms' => ['name' => 'Creator LMS', 'isPro' => true, 'is_active' => false], 'FluentCart' => ['name' => 'FluentCart', 'isPro' => true, 'is_active' => false], + 'Wsms' => ['name' => 'WSMS (WP SMS)', 'isPro' => true, 'is_active' => false], 'FluentCrm' => ['name' => 'Fluent CRM', 'isPro' => true, 'is_active' => false], 'FluentCommunity' => ['name' => 'Fluent Community', 'isPro' => true, 'is_active' => false], 'FluentPdfGenerator' => ['name' => 'Fluent PDF Generator', 'isPro' => true, 'is_active' => false], diff --git a/frontend/src/components/AllIntegrations/EditInteg.jsx b/frontend/src/components/AllIntegrations/EditInteg.jsx index baf8e2a1..10fdcc34 100644 --- a/frontend/src/components/AllIntegrations/EditInteg.jsx +++ b/frontend/src/components/AllIntegrations/EditInteg.jsx @@ -177,6 +177,7 @@ const EditCreatorLms = lazy(() => import('./CreatorLms/EditCreatorLms')) const EditUltimateAffiliatePro = lazy(() => import('./UltimateAffiliatePro/EditUltimateAffiliatePro')) const EditBookly = lazy(() => import('./Bookly/EditBookly')) const EditFluentCart = lazy(() => import('./FluentCart/EditFluentCart')) +const EditWsms = lazy(() => import('./Wsms/EditWsms')) const EditMoreConvertWishlist = lazy(() => import('./MoreConvertWishlist/EditMoreConvertWishlist')) const EditHefflCRM = lazy(() => import('./HefflCRM/EditHefflCRM')) const EditSecureCustomFields = lazy(() => import('./SecureCustomFields/EditSecureCustomFields')) @@ -618,6 +619,8 @@ const IntegType = memo(({ allIntegURL, flow }) => { return case 'FluentCart': return + case 'Wsms': + return case 'MoreConvert Wishlist': return case 'Heffl CRM': diff --git a/frontend/src/components/AllIntegrations/IntegInfo.jsx b/frontend/src/components/AllIntegrations/IntegInfo.jsx index c095b228..5145e4df 100644 --- a/frontend/src/components/AllIntegrations/IntegInfo.jsx +++ b/frontend/src/components/AllIntegrations/IntegInfo.jsx @@ -181,6 +181,7 @@ const UltimateAffiliateProAuthorization = lazy( ) const BooklyAuthorization = lazy(() => import('./Bookly/BooklyAuthorization')) const FluentCartAuthorization = lazy(() => import('./FluentCart/FluentCartAuthorization')) +const WsmsAuthorization = lazy(() => import('./Wsms/WsmsAuthorization')) const MoreConvertWishlistAuthorization = lazy(() => import('./MoreConvertWishlist/MoreConvertWishlistAuthorization')) const HefflCRMAuthorization = lazy(() => import('./HefflCRM/HefflCRMAuthorization')) @@ -666,6 +667,8 @@ export default function IntegInfo() { return case 'FluentCart': return + case 'Wsms': + return case 'MoreConvert Wishlist': return ( import('./CreatorLms/CreatorLms')) const UltimateAffiliatePro = lazy(() => import('./UltimateAffiliatePro/UltimateAffiliatePro')) const Bookly = lazy(() => import('./Bookly/Bookly')) const FluentCart = lazy(() => import('./FluentCart/FluentCart')) +const Wsms = lazy(() => import('./Wsms/Wsms')) const MoreConvertWishlist = lazy(() => import('./MoreConvertWishlist/MoreConvertWishlist')) const HefflCRM = lazy(() => import('./HefflCRM/HefflCRM')) const SecureCustomFields = lazy(() => import('./SecureCustomFields/SecureCustomFields')) @@ -1728,6 +1729,15 @@ export default function NewInteg({ allIntegURL }) { setFlow={setFlow} /> ) + case 'Wsms': + return ( + + ) case 'MoreConvert Wishlist': return ( + + +
+ {__('Integration Name:', 'bit-integrations')} + handleInput(e, wsmsConf, setWsmsConf)} + name="name" + value={wsmsConf.name} + type="text" + placeholder={__('Integration Name...', 'bit-integrations')} + /> +
+
+ + + + + + + saveActionConf({ + flow, + setFlow, + allIntegURL, + conf: wsmsConf, + navigate, + id, + edit: 1, + setIsLoading, + setSnackbar + }) + } + disabled={!checkMappedFields(wsmsConf)} + isLoading={isLoading} + dataConf={wsmsConf} + setDataConf={setWsmsConf} + formFields={formFields} + /> +
+ + ) +} diff --git a/frontend/src/components/AllIntegrations/Wsms/Wsms.jsx b/frontend/src/components/AllIntegrations/Wsms/Wsms.jsx new file mode 100644 index 00000000..a3c66d11 --- /dev/null +++ b/frontend/src/components/AllIntegrations/Wsms/Wsms.jsx @@ -0,0 +1,106 @@ +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 WsmsAuthorization from './WsmsAuthorization' +import { checkMappedFields } from './WsmsCommonFunc' +import WsmsIntegLayout from './WsmsIntegLayout' + +export default function Wsms({ 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 [wsmsConf, setWsmsConf] = useState({ + name: 'WSMS (WP SMS)', + type: 'Wsms', + field_map: [{ formField: '', wsmsField: '' }], + actions: {}, + mainAction: '' + }) + + const nextPage = val => { + setTimeout(() => { + document.getElementById('btcd-settings-wrp').scrollTop = 0 + }, 300) + + if (val === 3) { + if (!checkMappedFields(wsmsConf)) { + setSnackbar({ + show: true, + msg: __('Please map all required fields to continue.', 'bit-integrations') + }) + return + } + + if (wsmsConf.name !== '' && wsmsConf.field_map.length > 0) { + setStep(val) + } + } else { + setStep(val) + } + } + + return ( +
+ +
{/* */}
+ + {/* STEP 1 */} + + + {/* STEP 2 */} +
+ +
+
+
+ +
+ + {/* STEP 3 */} + + saveIntegConfig(flow, setFlow, allIntegURL, wsmsConf, navigate, '', '', setIsLoading) + } + isLoading={isLoading} + /> +
+ ) +} diff --git a/frontend/src/components/AllIntegrations/Wsms/WsmsAuthorization.jsx b/frontend/src/components/AllIntegrations/Wsms/WsmsAuthorization.jsx new file mode 100644 index 00000000..8bfb2c59 --- /dev/null +++ b/frontend/src/components/AllIntegrations/Wsms/WsmsAuthorization.jsx @@ -0,0 +1,112 @@ +import { useState } from 'react' +import BackIcn from '../../../Icons/BackIcn' +import bitsFetch from '../../../Utils/bitsFetch' +import { __ } from '../../../Utils/i18nwrap' +import LoaderSm from '../../Loaders/LoaderSm' +import TutorialLink from '../../Utilities/TutorialLink' + +export default function WsmsAuthorization({ + formID, + wsmsConf, + setWsmsConf, + step, + nextPage, + isLoading, + setIsLoading, + setSnackbar +}) { + const [isAuthorized, setIsAuthorized] = useState(false) + const [showAuthMsg, setShowAuthMsg] = useState(false) + const authorizeHandler = () => { + setIsLoading('auth') + bitsFetch({}, 'wsms_authorize').then(result => { + if (result?.success) { + setIsAuthorized(true) + setSnackbar({ + show: true, + msg: __('Connected with WSMS (WP SMS) Successfully', 'bit-integrations') + }) + } + setIsLoading(false) + setShowAuthMsg(true) + }) + } + + const handleInput = e => { + const newConf = { ...wsmsConf } + newConf[e.target.name] = e.target.value + setWsmsConf(newConf) + } + + return ( +
+ + +
+ {__('Integration Name:', 'bit-integrations')} +
+ + + {isLoading === 'auth' && ( +
+ + {__('Checking if WSMS (WP SMS) is authorized!!!', 'bit-integrations')} +
+ )} + + {showAuthMsg && !isAuthorized && !isLoading && ( +
+
+
+ +
+
+ {__('WSMS (WP SMS) is not activated or not installed', 'bit-integrations')} +
+
+
+ )} + + {showAuthMsg && isAuthorized && !isLoading && ( +
+
+ +
+
{__('WSMS (WP SMS) is activated', 'bit-integrations')}
+
+ )} + + +
+ +
+ ) +} diff --git a/frontend/src/components/AllIntegrations/Wsms/WsmsCommonFunc.js b/frontend/src/components/AllIntegrations/Wsms/WsmsCommonFunc.js new file mode 100644 index 00000000..d887e74f --- /dev/null +++ b/frontend/src/components/AllIntegrations/Wsms/WsmsCommonFunc.js @@ -0,0 +1,82 @@ +import { create } from 'mutative' +import toast from 'react-hot-toast' +import bitsFetch from '../../../Utils/bitsFetch' +import { __ } from '../../../Utils/i18nwrap' + +export const handleInput = (e, wsmsConf, setWsmsConf) => { + const { name, value } = e.target + + setWsmsConf(prevConf => + create(prevConf, draftConf => { + draftConf[name] = value + }) + ) +} + +export const refreshGroups = (setWsmsConf, setIsLoading) => { + setIsLoading(true) + bitsFetch(null, 'refresh_wsms_groups') + .then(result => { + if (result && result?.success && result?.data?.groups) { + setWsmsConf(prevConf => + create(prevConf, draftConf => { + draftConf.allGroups = result.data.groups + }) + ) + + setIsLoading(false) + toast.success(__('All groups fetched successfully', 'bit-integrations')) + return + } + setIsLoading(false) + toast.error(__('WSMS groups fetch failed. Please try again', 'bit-integrations')) + }) + .catch(() => setIsLoading(false)) +} + + +export const refreshStatuses = (setWsmsConf, setIsLoading) => { + setIsLoading(true) + bitsFetch(null, 'refresh_wsms_statuses') + .then(result => { + if (result && result?.success && result?.data?.statuses) { + setWsmsConf(prevConf => + create(prevConf, draftConf => { + draftConf.allStatuses = result.data.statuses + }) + ) + + setIsLoading(false) + toast.success(__('All statuses fetched successfully', 'bit-integrations')) + return + } + setIsLoading(false) + toast.error(__('WSMS statuses fetch failed. Please try again', 'bit-integrations')) + }) + .catch(() => setIsLoading(false)) +} + +export const checkMappedFields = wsmsConf => { + const mappedFields = wsmsConf?.field_map + ? wsmsConf.field_map.filter( + mappedField => + !mappedField.formField || + !mappedField.wsmsField || + (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: '', + wsmsField: field.key + })) + : [{ formField: '', wsmsField: '' }] +} diff --git a/frontend/src/components/AllIntegrations/Wsms/WsmsFieldMap.jsx b/frontend/src/components/AllIntegrations/Wsms/WsmsFieldMap.jsx new file mode 100644 index 00000000..c319513d --- /dev/null +++ b/frontend/src/components/AllIntegrations/Wsms/WsmsFieldMap.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 WsmsFieldMap({ i, formFields, field, wsmsConf, setWsmsConf }) { + const btcbi = useRecoilValue($appConfigState) + const { isPro } = btcbi + + const requiredFlds = wsmsConf?.wsmsFields?.filter(fld => fld.required === true) || [] + const nonRequiredFlds = wsmsConf?.wsmsFields?.filter(fld => fld.required === false) || [] + + return ( +
+
+
+ + + {field.formField === 'custom' && ( + handleCustomValue(e, i, wsmsConf, setWsmsConf)} + 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/Wsms/WsmsIntegLayout.jsx b/frontend/src/components/AllIntegrations/Wsms/WsmsIntegLayout.jsx new file mode 100644 index 00000000..2128bee4 --- /dev/null +++ b/frontend/src/components/AllIntegrations/Wsms/WsmsIntegLayout.jsx @@ -0,0 +1,191 @@ +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 { generateMappedField, refreshGroups, refreshStatuses } from './WsmsCommonFunc' +import WsmsFieldMap from './WsmsFieldMap' +import { modules, WsmsStaticData } from './staticData' + +const GROUP_ACTIONS = ['add_subscriber', 'update_subscriber', 'delete_subscriber'] +const STATUS_ACTIONS = ['add_subscriber', 'update_subscriber'] + +export default function WsmsIntegLayout({ + formID, + formFields, + wsmsConf, + setWsmsConf, + isLoading, + setIsLoading, + setSnackbar +}) { + const btcbi = useRecoilValue($appConfigState) + const { isPro } = btcbi + + const handleMainAction = value => { + setWsmsConf(prevConf => + create(prevConf, draftConf => { + draftConf.mainAction = value + draftConf.wsmsFields = WsmsStaticData[value] || [] + draftConf.field_map = generateMappedField(draftConf.wsmsFields) + }) + ) + + if (GROUP_ACTIONS.includes(value)) { + refreshGroups(setWsmsConf, setIsLoading) + } + if (STATUS_ACTIONS.includes(value)) { + refreshStatuses(setWsmsConf, setIsLoading) + } + } + + return ( + <> +
+
+ {__('Action:', 'bit-integrations')} + handleMainAction(value)} + options={modules?.map(action => ({ + label: checkIsPro(isPro, action.is_pro) ? action.label : getProLabel(action.label), + value: action.name, + disabled: checkIsPro(isPro, action.is_pro) ? false : true + }))} + singleSelect + closeOnSelect + /> +
+ + {GROUP_ACTIONS.includes(wsmsConf?.mainAction) && ( + <> +
+
+ {__('Group:', 'bit-integrations')} + ({ + label: group.label, + value: group.value?.toString() + })) + } + onChange={val => + setWsmsConf(prevConf => + create(prevConf, draftConf => { + draftConf.groupId = val + }) + ) + } + singleSelect + closeOnSelect + /> + +
+ + )} + + {STATUS_ACTIONS.includes(wsmsConf?.mainAction) && ( + <> +
+
+ {__('Status:', 'bit-integrations')} + ({ + label: status.label, + value: status.value?.toString() + })) + } + onChange={val => + setWsmsConf(prevConf => + create(prevConf, draftConf => { + draftConf.status = val + }) + ) + } + singleSelect + closeOnSelect + /> + +
+ + )} + + {isLoading && ( + + )} + + {wsmsConf?.mainAction && wsmsConf.wsmsFields && wsmsConf.wsmsFields.length > 0 && ( +
+ {__('Map Fields', 'bit-integrations')} +
+
+
+ {__('Form Fields', 'bit-integrations')} +
+
+ {__('WSMS Fields', 'bit-integrations')} +
+
+ + {wsmsConf?.field_map?.map((itm, i) => ( + + ))} +
+ +
+
+
+ )} + + ) +} diff --git a/frontend/src/components/AllIntegrations/Wsms/staticData.js b/frontend/src/components/AllIntegrations/Wsms/staticData.js new file mode 100644 index 00000000..d124ffe4 --- /dev/null +++ b/frontend/src/components/AllIntegrations/Wsms/staticData.js @@ -0,0 +1,54 @@ +import { __ } from '../../../Utils/i18nwrap' + +export const modules = [ + { name: 'send_sms', label: __('Send SMS', 'bit-integrations'), is_pro: true }, + { name: 'add_subscriber', label: __('Add Subscriber', 'bit-integrations'), is_pro: true }, + { name: 'update_subscriber', label: __('Update Subscriber', 'bit-integrations'), is_pro: true }, + { name: 'delete_subscriber', label: __('Delete Subscriber', 'bit-integrations'), is_pro: true }, + { name: 'add_group', label: __('Add Group', 'bit-integrations'), is_pro: true }, + { name: 'update_group', label: __('Update Group', 'bit-integrations'), is_pro: true }, + { name: 'delete_group', label: __('Delete Group', 'bit-integrations'), is_pro: true } +] + +const SendSmsFields = [ + { key: 'to', label: __('To (Recipient Numbers)', 'bit-integrations'), required: true }, + { key: 'message', label: __('Message', 'bit-integrations'), required: true }, + { key: 'sender_id', label: __('Sender ID', 'bit-integrations'), required: false }, + { key: 'is_flash', label: __('Is Flash', 'bit-integrations'), required: false }, + { key: 'media_urls', label: __('Media URLs', 'bit-integrations'), required: false } +] + +const AddSubscriberFields = [ + { key: 'name', label: __('Name', 'bit-integrations'), required: true }, + { key: 'mobile', label: __('Mobile', 'bit-integrations'), required: true } +] + +const UpdateSubscriberFields = [ + { key: 'name', label: __('Name', 'bit-integrations'), required: true }, + { key: 'mobile', label: __('Mobile', 'bit-integrations'), required: true } +] + +const DeleteSubscriberFields = [ + { key: 'mobile', label: __('Mobile', 'bit-integrations'), required: true } +] + +const AddGroupFields = [{ key: 'name', label: __('Group Name', 'bit-integrations'), required: true }] + +const UpdateGroupFields = [ + { key: 'group_id', label: __('Group ID', 'bit-integrations'), required: true }, + { key: 'name', label: __('Group Name', 'bit-integrations'), required: true } +] + +const DeleteGroupFields = [ + { key: 'group_id', label: __('Group ID', 'bit-integrations'), required: true } +] + +export const WsmsStaticData = { + send_sms: SendSmsFields, + add_subscriber: AddSubscriberFields, + update_subscriber: UpdateSubscriberFields, + delete_subscriber: DeleteSubscriberFields, + add_group: AddGroupFields, + update_group: UpdateGroupFields, + delete_group: DeleteGroupFields +} diff --git a/frontend/src/components/Flow/New/SelectAction.jsx b/frontend/src/components/Flow/New/SelectAction.jsx index 9259273f..809d381a 100644 --- a/frontend/src/components/Flow/New/SelectAction.jsx +++ b/frontend/src/components/Flow/New/SelectAction.jsx @@ -180,6 +180,7 @@ export default function SelectAction() { { type: 'CreatorLms' }, { type: 'Bookly' }, { type: 'FluentCart' }, + { type: 'Wsms', name: 'WSMS (WP SMS)' }, { type: 'MoreConvert Wishlist', logo: 'moreConvertWishlist' }, { type: 'Heffl CRM' }, { type: 'Secure Custom Fields' }, diff --git a/frontend/src/resource/img/integ/wsms.svg b/frontend/src/resource/img/integ/wsms.svg new file mode 100644 index 00000000..93933e9a --- /dev/null +++ b/frontend/src/resource/img/integ/wsms.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/resource/img/integ/wsms.webp b/frontend/src/resource/img/integ/wsms.webp new file mode 100644 index 0000000000000000000000000000000000000000..30995d99055007e2d4f69f7624e39aef0fd1880c GIT binary patch literal 2806 zcmVJ?JRN z`vBoa{_lVM6&D7Fg9~9iboz^74uo!}IJrOBhJI)^N{a}2p0^1KjF&-y8hY&Q_PXUY$Rb&0cdz#tZau zE3A=La{;$Y%{!3At)ReSp34yhS=>H15lBqGdCrL}ZlA{GY5+0m;!QiUxP6rxXaGEz zai?6~Ba7R&xjtIZ!qx0V7Pq!iD+EaTon8|NK^3=-T6qBSL)j#<)`E*SX+LhI#k&#m zLs{*)j=84)AfX&RKBb^q#Jr0jo5O(_0;CpkLAM2e6cGyn)wCNIoye-^@u4~%wBWDP z{f#_m!QbWkIAN1#f^6E$<)=KDJUi><9@xTlA+kzo^QncvZ>6{%+mGt?FN8dpMecjC zRU;v35Zunba}rzJK8}ik^aN9{U3MU=UL*zU0S{)v`P(*R)tgK|NY6j!xW`*$)%!v# zq{=bu7q!SLN2TQ<1h?CzY1rbnQSOA`cD=L&TikZ~R6%gN!n_$Ca}}KJ7$SJ&BEkoP$%YUcnZ(FH?dH5Zs=+`FRayY#G#`KZ?t3* zs>W~oA-LV*HJf@xmF)w;?YPCQcJ;^ugyL+L+hA+s1#Ho427MEX(&bwL`2$%ku3<|}zIdJb-~T{h zjsU3(J+`Y_snUe5idY2g+0ja2F-(qYWqTeNrr(RLP1_@KIGPVWK zZt|L^0-q6Z`E6rL39H4m^nFG$1W4mfx-ho8WaM9ghlTA6lB$_ zv_K=s&3*z~^)AmxfEc!KTyP+(Y-QGGpc? a-h5v-W{UQ>cd0>fHd@$QoYBx*ss2{mn_UyF~6N+Z7xQhC)Gpk-;&Rz>R2ZiMxTUD^*d&?MZ zE9Hj}FQn@Mh5oQ}m@(WoSUX|=0^p>MKWignxb5(%0X&##D)<+GKEFc3R`vzt3xLD% zs+(N}rVxQGH5!@%GNDZPL4lM>Zn7CLBm-ou!`#N@=&&6CPxLKpq#=gU}=mOyRu3$3I@D z`x{X_Hi+<{)op>d6U+1O!@^Nzc}NKUq5N!h=E1e+7oIQtFC#(e0Mb^Ed?mc$ed2ARM^b%PcPlA?RP6^_E^j@DHWKyO!`E2Lkeb?}$hG6Fm$|{AEvo z{0qA<& zFR@4VKl#4E|EoX$`#5@O`T_g{@B*9kX~c?8?j%xwaUt$Rxy9nJ_RO&G?&6HI|E{0?o4vw zYj|fut^gpQ_DJcKX9!%b{A5uwpn%IDaA2#W4i_uaAL?|I?vZ!Gp@gZ~Pq}!JN&Uo% zPwT({{{Ev-021;8_V%kl@?F8nQm0xqq(M1EFb+6YmQp%0!}pK?Bd5jSEIelV_LK^2 z!n-O4fZT))=0><5@vttujp`*GikB%2eW4pG7n{Qx`G5sEG{DzQa<2Z^1)t{65Ji@xzaxD&oiH>V?J|z`^>W3!k)59eKllfE{L*;&Hc@$D z7PLHnqs5Nw>><~$YhppJ6e3p3i^;JYK|3q{s Date: Wed, 17 Jun 2026 17:37:47 +0600 Subject: [PATCH 2/4] feat: register missing custom_form_submission triggers in customFormIntegrations Add B2BKing, FormyChat, GiveWp, SenseiLMS, SureDash, WpDataTables, WpErp and Wsms to the customFormIntegrations list. These Pro triggers declare type custom_form_submission but were absent from the frontend list, so they were not handled as custom-form integrations in the UI. --- frontend/src/Utils/StaticData/webhookIntegrations.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/Utils/StaticData/webhookIntegrations.js b/frontend/src/Utils/StaticData/webhookIntegrations.js index cc893d01..3ca08e6e 100644 --- a/frontend/src/Utils/StaticData/webhookIntegrations.js +++ b/frontend/src/Utils/StaticData/webhookIntegrations.js @@ -102,7 +102,15 @@ export const customFormIntegrations = [ 'WordPress', 'FluentPdfGenerator', 'BookingPress', - 'FluentPdfGenerator' + 'FluentPdfGenerator', + 'B2BKing', + 'FormyChat', + 'GiveWp', + 'SenseiLMS', + 'SureDash', + 'WpDataTables', + 'WpErp', + 'Wsms' ] export const actionHookIntegrations = ['ActionHook'] From 8c864323efab13a0bb5b43a8de464c3ced91244c Mon Sep 17 00:00:00 2001 From: Rishad Alam Date: Wed, 17 Jun 2026 18:08:41 +0600 Subject: [PATCH 3/4] fix(wsms): address code-review findings (frontend) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - WsmsFieldMap: compare i < requiredFlds.length (was missing .length, so the locked required-field branch never ran) and bind the select value to the field key, not its label - WsmsIntegLayout: clear groupId/status when the action changes so a previous action's dropdown selection no longer leaks into the next one - statuses are now a static frontend list (wsmsStatuses) instead of a backend AJAX round-trip for two constants — removes the refreshStatuses route/method and the loading-state race with refreshGroups --- backend/Actions/Wsms/Routes.php | 1 - backend/Actions/Wsms/WsmsController.php | 13 --------- .../AllIntegrations/Wsms/WsmsCommonFunc.js | 22 --------------- .../AllIntegrations/Wsms/WsmsFieldMap.jsx | 2 +- .../AllIntegrations/Wsms/WsmsIntegLayout.jsx | 27 +++++-------------- .../AllIntegrations/Wsms/staticData.js | 5 ++++ 6 files changed, 12 insertions(+), 58 deletions(-) diff --git a/backend/Actions/Wsms/Routes.php b/backend/Actions/Wsms/Routes.php index f433d6d0..a19aab24 100644 --- a/backend/Actions/Wsms/Routes.php +++ b/backend/Actions/Wsms/Routes.php @@ -9,4 +9,3 @@ Route::post('wsms_authorize', [WsmsController::class, 'wsmsAuthorize']); Route::post('refresh_wsms_groups', [WsmsController::class, 'refreshGroups']); -Route::post('refresh_wsms_statuses', [WsmsController::class, 'refreshStatuses']); diff --git a/backend/Actions/Wsms/WsmsController.php b/backend/Actions/Wsms/WsmsController.php index b22a4657..8c610d56 100644 --- a/backend/Actions/Wsms/WsmsController.php +++ b/backend/Actions/Wsms/WsmsController.php @@ -53,19 +53,6 @@ public function refreshGroups() wp_send_json_success($response, 200); } - public function refreshStatuses() - { - self::isExists(); - - $statuses = [ - (object) ['value' => '1', 'label' => 'Active'], - (object) ['value' => '0', 'label' => 'Inactive'], - ]; - - $response['statuses'] = $statuses; - wp_send_json_success($response, 200); - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; diff --git a/frontend/src/components/AllIntegrations/Wsms/WsmsCommonFunc.js b/frontend/src/components/AllIntegrations/Wsms/WsmsCommonFunc.js index d887e74f..2be8af4f 100644 --- a/frontend/src/components/AllIntegrations/Wsms/WsmsCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Wsms/WsmsCommonFunc.js @@ -34,28 +34,6 @@ export const refreshGroups = (setWsmsConf, setIsLoading) => { .catch(() => setIsLoading(false)) } - -export const refreshStatuses = (setWsmsConf, setIsLoading) => { - setIsLoading(true) - bitsFetch(null, 'refresh_wsms_statuses') - .then(result => { - if (result && result?.success && result?.data?.statuses) { - setWsmsConf(prevConf => - create(prevConf, draftConf => { - draftConf.allStatuses = result.data.statuses - }) - ) - - setIsLoading(false) - toast.success(__('All statuses fetched successfully', 'bit-integrations')) - return - } - setIsLoading(false) - toast.error(__('WSMS statuses fetch failed. Please try again', 'bit-integrations')) - }) - .catch(() => setIsLoading(false)) -} - export const checkMappedFields = wsmsConf => { const mappedFields = wsmsConf?.field_map ? wsmsConf.field_map.filter( diff --git a/frontend/src/components/AllIntegrations/Wsms/WsmsFieldMap.jsx b/frontend/src/components/AllIntegrations/Wsms/WsmsFieldMap.jsx index c319513d..777c2fb3 100644 --- a/frontend/src/components/AllIntegrations/Wsms/WsmsFieldMap.jsx +++ b/frontend/src/components/AllIntegrations/Wsms/WsmsFieldMap.jsx @@ -65,7 +65,7 @@ export default function WsmsFieldMap({ i, formFields, field, wsmsConf, setWsmsCo className="btcd-paper-inp" disabled={i < requiredFlds.length} name="wsmsField" - value={i < requiredFlds ? requiredFlds[i].label || '' : field.wsmsField || ''} + value={i < requiredFlds.length ? requiredFlds[i].key || '' : field.wsmsField || ''} onChange={ev => handleFieldMapping(ev, i, wsmsConf, setWsmsConf)}> {i < requiredFlds.length ? ( diff --git a/frontend/src/components/AllIntegrations/Wsms/WsmsIntegLayout.jsx b/frontend/src/components/AllIntegrations/Wsms/WsmsIntegLayout.jsx index 2128bee4..8a4358c2 100644 --- a/frontend/src/components/AllIntegrations/Wsms/WsmsIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/Wsms/WsmsIntegLayout.jsx @@ -6,9 +6,9 @@ import { __ } from '../../../Utils/i18nwrap' import Loader from '../../Loaders/Loader' import { checkIsPro, getProLabel } from '../../Utilities/ProUtilHelpers' import { addFieldMap } from '../IntegrationHelpers/IntegrationHelpers' -import { generateMappedField, refreshGroups, refreshStatuses } from './WsmsCommonFunc' +import { generateMappedField, refreshGroups } from './WsmsCommonFunc' import WsmsFieldMap from './WsmsFieldMap' -import { modules, WsmsStaticData } from './staticData' +import { modules, wsmsStatuses, WsmsStaticData } from './staticData' const GROUP_ACTIONS = ['add_subscriber', 'update_subscriber', 'delete_subscriber'] const STATUS_ACTIONS = ['add_subscriber', 'update_subscriber'] @@ -31,15 +31,15 @@ export default function WsmsIntegLayout({ draftConf.mainAction = value draftConf.wsmsFields = WsmsStaticData[value] || [] draftConf.field_map = generateMappedField(draftConf.wsmsFields) + // Clear identifiers from the previous action so they do not leak into the new one. + delete draftConf.groupId + delete draftConf.status }) ) if (GROUP_ACTIONS.includes(value)) { refreshGroups(setWsmsConf, setIsLoading) } - if (STATUS_ACTIONS.includes(value)) { - refreshStatuses(setWsmsConf, setIsLoading) - } } return ( @@ -110,14 +110,7 @@ export default function WsmsIntegLayout({ title="status" defaultValue={wsmsConf?.status ?? null} className="btcd-paper-drpdwn w-5" - options={ - wsmsConf?.allStatuses && - Array.isArray(wsmsConf.allStatuses) && - wsmsConf.allStatuses.map(status => ({ - label: status.label, - value: status.value?.toString() - })) - } + options={wsmsStatuses} onChange={val => setWsmsConf(prevConf => create(prevConf, draftConf => { @@ -128,14 +121,6 @@ export default function WsmsIntegLayout({ singleSelect closeOnSelect /> -
)} diff --git a/frontend/src/components/AllIntegrations/Wsms/staticData.js b/frontend/src/components/AllIntegrations/Wsms/staticData.js index d124ffe4..b76d511e 100644 --- a/frontend/src/components/AllIntegrations/Wsms/staticData.js +++ b/frontend/src/components/AllIntegrations/Wsms/staticData.js @@ -52,3 +52,8 @@ export const WsmsStaticData = { update_group: UpdateGroupFields, delete_group: DeleteGroupFields } + +export const wsmsStatuses = [ + { value: '1', label: __('Active', 'bit-integrations') }, + { value: '0', label: __('Inactive', 'bit-integrations') } +] From 7cb7858a242bd742dff96ce430d073ccef9681e2 Mon Sep 17 00:00:00 2001 From: Rishad Alam Date: Wed, 17 Jun 2026 18:22:37 +0600 Subject: [PATCH 4/4] fix(wsms): address PR review comments (frontend) - RecordApiHelper: declare generateReqDataFromFieldMap static (no instance state, already called via static::); fix "activate" -> "activated" typo - WsmsController: declare refreshGroups static (no $this, static route callback) - WsmsAuthorization: add a .catch so loading clears and the auth message shows on a failed/rejected request - WsmsIntegLayout: default the group options to [] via a ternary instead of && --- backend/Actions/Wsms/RecordApiHelper.php | 4 +-- backend/Actions/Wsms/WsmsController.php | 2 +- .../Wsms/WsmsAuthorization.jsx | 27 +++++++++++-------- .../AllIntegrations/Wsms/WsmsIntegLayout.jsx | 12 ++++----- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/backend/Actions/Wsms/RecordApiHelper.php b/backend/Actions/Wsms/RecordApiHelper.php index a60a6415..26e37b3e 100644 --- a/backend/Actions/Wsms/RecordApiHelper.php +++ b/backend/Actions/Wsms/RecordApiHelper.php @@ -51,7 +51,7 @@ public function execute($fieldValues, $fieldMap, $utilities) $defaultResponse = [ 'success' => false, // translators: %s: Plugin name - 'message' => wp_sprintf(__('%s plugin is not installed or activate', 'bit-integrations'), 'Bit Integrations Pro') + 'message' => wp_sprintf(__('%s plugin is not installed or activated', 'bit-integrations'), 'Bit Integrations Pro') ]; // Route to appropriate action method @@ -122,7 +122,7 @@ public function execute($fieldValues, $fieldMap, $utilities) return $response; } - private function generateReqDataFromFieldMap($fieldMap, $fieldValues) + private static function generateReqDataFromFieldMap($fieldMap, $fieldValues) { $dataFinal = []; foreach ($fieldMap as $item) { diff --git a/backend/Actions/Wsms/WsmsController.php b/backend/Actions/Wsms/WsmsController.php index 8c610d56..5e91c1fa 100644 --- a/backend/Actions/Wsms/WsmsController.php +++ b/backend/Actions/Wsms/WsmsController.php @@ -32,7 +32,7 @@ public static function wsmsAuthorize() wp_send_json_success(true); } - public function refreshGroups() + public static function refreshGroups() { self::isExists(); diff --git a/frontend/src/components/AllIntegrations/Wsms/WsmsAuthorization.jsx b/frontend/src/components/AllIntegrations/Wsms/WsmsAuthorization.jsx index 8bfb2c59..53ad0243 100644 --- a/frontend/src/components/AllIntegrations/Wsms/WsmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Wsms/WsmsAuthorization.jsx @@ -19,17 +19,22 @@ export default function WsmsAuthorization({ const [showAuthMsg, setShowAuthMsg] = useState(false) const authorizeHandler = () => { setIsLoading('auth') - bitsFetch({}, 'wsms_authorize').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with WSMS (WP SMS) Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) + bitsFetch({}, 'wsms_authorize') + .then(result => { + if (result?.success) { + setIsAuthorized(true) + setSnackbar({ + show: true, + msg: __('Connected with WSMS (WP SMS) Successfully', 'bit-integrations') + }) + } + setIsLoading(false) + setShowAuthMsg(true) + }) + .catch(() => { + setIsLoading(false) + setShowAuthMsg(true) + }) } const handleInput = e => { diff --git a/frontend/src/components/AllIntegrations/Wsms/WsmsIntegLayout.jsx b/frontend/src/components/AllIntegrations/Wsms/WsmsIntegLayout.jsx index 8a4358c2..36b903c7 100644 --- a/frontend/src/components/AllIntegrations/Wsms/WsmsIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/Wsms/WsmsIntegLayout.jsx @@ -72,12 +72,12 @@ export default function WsmsIntegLayout({ defaultValue={wsmsConf?.groupId ?? null} className="btcd-paper-drpdwn w-5" options={ - wsmsConf?.allGroups && - Array.isArray(wsmsConf.allGroups) && - wsmsConf.allGroups.map(group => ({ - label: group.label, - value: group.value?.toString() - })) + Array.isArray(wsmsConf?.allGroups) + ? wsmsConf.allGroups.map(group => ({ + label: group.label, + value: group.value?.toString() + })) + : [] } onChange={val => setWsmsConf(prevConf =>