diff --git a/frontend-dev/src/components/AllIntegrations/EditInteg.jsx b/frontend-dev/src/components/AllIntegrations/EditInteg.jsx index 4b1de084..be46b418 100644 --- a/frontend-dev/src/components/AllIntegrations/EditInteg.jsx +++ b/frontend-dev/src/components/AllIntegrations/EditInteg.jsx @@ -32,6 +32,7 @@ const EditZohoFlow = lazy(() => import('./ZohoFlow/EditZohoFlow')) const EditTelegram = lazy(() => import('./Telegram/EditTelegram')) const EditTutorLms = lazy(() => import('./TutorLms/EditTutorLms')) const EditFluentCrm = lazy(() => import('./FluentCRM/EditFluentCrm')) +const EditFluentCommunity = lazy(() => import('./FluentCommunity/EditFluentCommunity')) const EditEncharge = lazy(() => import('./Encharge/EditEncharge')) const EditAutonami = lazy(() => import('./Autonami/EditAutonami')) const EditDropbox = lazy(() => import('./Dropbox/EditDropbox')) @@ -303,6 +304,8 @@ const IntegType = memo(({ allIntegURL, flow }) => { return case 'Fluent Crm': return + case 'Fluent Community': + return case 'Encharge': return case 'Registration': diff --git a/frontend-dev/src/components/AllIntegrations/FluentCommunity/EditFluentCommunity.jsx b/frontend-dev/src/components/AllIntegrations/FluentCommunity/EditFluentCommunity.jsx new file mode 100644 index 00000000..0d7ce329 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/FluentCommunity/EditFluentCommunity.jsx @@ -0,0 +1,92 @@ +/* eslint-disable no-param-reassign */ + +import { useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import { useRecoilState, useRecoilValue } from 'recoil' +import { $actionConf, $formFields, $newFlow } from '../../../GlobalStates' +import { __ } from '../../../Utils/i18nwrap' +import SnackMsg from '../../Utilities/SnackMsg' +import EditFormInteg from '../EditFormInteg' +import SetEditIntegComponents from '../IntegrationHelpers/SetEditIntegComponents' +import EditWebhookInteg from '../EditWebhookInteg' +import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' +import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' +import { handleInput, checkMappedFields } from './FluentCommunityCommonFunc' +import FluentCommunityIntegLayout from './FluentCommunityIntegLayout' + +function EditFluentCommunity({ allIntegURL }) { + const navigate = useNavigate() + const { id, formID } = useParams() + + const [fluentCommunityConf, setFluentCommunityConf] = useRecoilState($actionConf) + const [flow, setFlow] = useRecoilState($newFlow) + const formFields = useRecoilValue($formFields) + const [isLoading, setIsLoading] = useState(false) + const [loading, setLoading] = useState({}) + const [snack, setSnackbar] = useState({ show: false }) + const saveConfig = () => { + if (!checkMappedFields(fluentCommunityConf)) { + setSnackbar({ + show: true, + msg: __('Please map all required fields to continue.', 'bit-integrations') + }) + return + } + saveActionConf({ + flow, + setFlow, + allIntegURL, + conf: fluentCommunityConf, + navigate, + edit: 1, + setIsLoading, + setSnackbar + }) + } + + return ( +
+ + +
+ {__('Integration Name:', 'bit-integrations')} + handleInput(e, fluentCommunityConf, setFluentCommunityConf)} + name="name" + value={fluentCommunityConf.name} + type="text" + placeholder={__('Integration Name...', 'bit-integrations')} + /> +
+
+ + + + + +
+
+ ) +} + +export default EditFluentCommunity diff --git a/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunity.jsx b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunity.jsx new file mode 100644 index 00000000..fbffc95f --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunity.jsx @@ -0,0 +1,145 @@ +import { useState } from 'react' +import 'react-multiple-select-dropdown-lite/dist/index.css' +import { useNavigate, useParams } from 'react-router-dom' +import BackIcn from '../../../Icons/BackIcn' +import { __ } from '../../../Utils/i18nwrap' +import SnackMsg from '../../Utilities/SnackMsg' +import Steps from '../../Utilities/Steps' +import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers' +import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' +import FluentCommunityAuthorization from './FluentCommunityAuthorization' +import { checkMappedFields, refreshCommunityList } from './FluentCommunityCommonFunc' +import FluentCommunityIntegLayout from './FluentCommunityIntegLayout' + +export default function FluentCommunity({ formFields, setFlow, flow, allIntegURL }) { + const navigate = useNavigate() + const { formID } = useParams() + const [isLoading, setIsLoading] = useState(false) + const [loading, setLoading] = useState({}) + const [step, setStep] = useState(1) + const [snack, setSnackbar] = useState({ show: false }) + const [fluentCommunityConf, setFluentCommunityConf] = useState({ + name: 'Fluent Community', + type: 'Fluent Community', + actionName: '', + field_map: [{ formField: '', fluentCommunityField: '' }], + actions: {} + }) + + const nextPage = val => { + setTimeout(() => { + document.getElementById('btcd-settings-wrp').scrollTop = 0 + }, 300) + if (val === 3) { + // All actions need field mapping validation + if (!checkMappedFields(fluentCommunityConf)) { + setSnackbar({ + show: true, + msg: __('Please map all required fields to continue.', 'bit-integrations') + }) + return + } + if (fluentCommunityConf?.actionName === 'add-user' && !fluentCommunityConf.list_id) { + setSnackbar({ show: true, msg: __('Please select space to continue.', 'bit-integrations') }) + return + } + if ( + (fluentCommunityConf?.actionName === 'add-course' || + fluentCommunityConf?.actionName === 'remove-course') && + !fluentCommunityConf.course_id + ) { + setSnackbar({ show: true, msg: __('Please select course to continue.', 'bit-integrations') }) + return + } + if ( + fluentCommunityConf?.actionName === 'create-post' && + (!fluentCommunityConf.post_space_id || !fluentCommunityConf.post_user_id) + ) { + setSnackbar({ + show: true, + msg: __('Please select space and user to continue.', 'bit-integrations') + }) + return + } + if (fluentCommunityConf.name !== '' && fluentCommunityConf.field_map.length > 0) { + setStep(val) + } + } else { + setStep(val) + } + } + + return ( +
+ +
+ +
+ + {/* STEP 1 */} + + + {/* STEP 2 */} +
+ +
+
+
+ +
+ + {/* STEP 3 */} + + saveIntegConfig( + flow, + setFlow, + allIntegURL, + fluentCommunityConf, + navigate, + '', + '', + setIsLoading + ) + } + isLoading={isLoading} + dataConf={fluentCommunityConf} + setDataConf={setFluentCommunityConf} + formFields={formFields} + /> +
+ ) +} diff --git a/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityActions.jsx b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityActions.jsx new file mode 100644 index 00000000..9c92579b --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityActions.jsx @@ -0,0 +1,152 @@ +/* eslint-disable no-param-reassign */ + +import { useState } from 'react' +import MultiSelect from 'react-multiple-select-dropdown-lite' +import { useRecoilValue } from 'recoil' +import { $btcbi } from '../../../GlobalStates' +import { __ } from '../../../Utils/i18nwrap' +import Loader from '../../Loaders/Loader' +import ConfirmModal from '../../Utilities/ConfirmModal' +import TableCheckBox from '../../Utilities/TableCheckBox' +import { ProFeatureSubtitle, ProFeatureTitle } from '../IntegrationHelpers/ActionProFeatureLabels' +import { getAllCompanies } from './FluentCommunityCommonFunc' + +export default function FluentCommunityActions({ + fluentCommunityConf, + setFluentCommunityConf, + loading, + setLoading, + setSnackbar +}) { + const [actionMdl, setActionMdl] = useState({ show: false }) + const btcbi = useRecoilValue($btcbi) + const { isPro } = btcbi + + const actionHandler = (e, type) => { + const newConf = { ...fluentCommunityConf } + if (type === 'exists') { + if (e.target.checked) { + newConf.actions.skip_if_exists = true + } else { + delete newConf.actions.skip_if_exists + } + } + if (type === 'doubleOpIn') { + if (e.target.checked) { + newConf.actions.double_opt_in = true + } else { + delete newConf.actions.double_opt_in + } + } + if (type === 'company_id') { + setActionMdl({ show: 'company_id' }) + getAllCompanies(fluentCommunityConf, setFluentCommunityConf, loading, setLoading, setSnackbar) + } + if (type === 'company') { + newConf.actions.company_id = e + } + + setFluentCommunityConf({ ...newConf }) + } + + const clsActionMdl = () => { + setActionMdl({ show: false }) + } + + return ( +
+ actionHandler(e, 'exists')} + className="wdt-200 mt-4 mr-2" + value="skip_if_exists" + title={__('Skip exist Contact', 'bit-integrations')} + subTitle={__('Skip if contact already exist in FluentCommunity', 'bit-integrations')} + /> + actionHandler(e, 'doubleOpIn')} + className="wdt-200 mt-4 mr-2" + value="double_opt_in" + title={__('Double Opt-in', 'bit-integrations')} + subTitle={__('Enable Double Option for new contacts', 'bit-integrations')} + /> + actionHandler(e, 'company_id')} + className="wdt-200 mt-4 mr-2" + value="company_id" + isInfo={!isPro} + title={} + subTitle={ + + } + /> + + {isPro && ( + +
+
{__('Select Company', 'bit-integrations')}
+ {loading?.company ? ( + + ) : ( +
+ ({ + label: item.label, + value: item.id.toString() + })) + } + className="msl-wrp-options" + defaultValue={fluentCommunityConf.actions?.company_id?.toString() || ''} + onChange={val => actionHandler(val, 'company')} + singleSelect + selectOnClose + /> + +
+ )} + + )} +
+ ) +} diff --git a/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityAuthorization.jsx b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityAuthorization.jsx new file mode 100644 index 00000000..9ed6f2f1 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityAuthorization.jsx @@ -0,0 +1,115 @@ +/* eslint-disable react/jsx-no-useless-fragment */ +import { useEffect, useState } from 'react' +import { __ } from '../../../Utils/i18nwrap' +import bitsFetch from '../../../Utils/bitsFetch' +import LoaderSm from '../../Loaders/LoaderSm' +import BackIcn from '../../../Icons/BackIcn' +import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import TutorialLink from '../../Utilities/TutorialLink' + +export default function FluentCommunityAuthorization({ + formID, + fluentCommunityConf, + setFluentCommunityConf, + step, + nextPage, + setSnackbar, + isInfo +}) { + const [isAuthorized, setisAuthorized] = useState(false) + const [error, setError] = useState({ integrationName: '' }) + const [showAuthMsg, setShowAuthMsg] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const { fluentCommunity } = tutorialLinks + const [isMounted, setIsMounted] = useState(true) + useEffect( + () => () => { + setIsMounted(false) + }, + [] + ) + + const handleAuthorize = () => { + setIsLoading('auth') + bitsFetch({}, 'fluent_community_authorize').then(result => { + if (isMounted) { + if (result?.success) { + setisAuthorized(true) + setSnackbar({ show: true, msg: __('Connected Successfully', 'bit-integrations') }) + } + setShowAuthMsg(true) + setIsLoading(false) + } + }) + } + const handleInput = e => { + const newConf = { ...fluentCommunityConf } + const rmError = { ...error } + rmError[e.target.name] = '' + newConf[e.target.name] = e.target.value + setError(rmError) + setFluentCommunityConf(newConf) + } + + return ( + <> +
+ {fluentCommunity?.youTubeLink && ( + + )} + {fluentCommunity?.docLink && ( + + )} + +
+ {__('Integration Name:', 'bit-integrations')} +
+ + {isLoading === 'auth' && ( +
+ + {__('Checking if Fluent Community is active!!!', 'bit-integrations')} +
+ )} + + {showAuthMsg && !isAuthorized && !isLoading && ( +
+ + × + + {__('Please! First Install Fluent Community Plugins', 'bit-integrations')} +
+ )} + +
+ +
+ + ) +} diff --git a/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityCommonFunc.js b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityCommonFunc.js new file mode 100644 index 00000000..d9fa5f35 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityCommonFunc.js @@ -0,0 +1,268 @@ +import { __ } from '../../../Utils/i18nwrap' +import bitsFetch from '../../../Utils/bitsFetch' +import { create } from 'mutative' +import { actionFields, memberRoles } from './staticData' + +export const refreshCommunityList = ( + formID, + fluentCommunityConf, + setFluentCommunityConf, + loading, + setLoading, + setSnackbar +) => { + setLoading({ ...loading, fluentCommunityList: true }) + bitsFetch({}, 'refresh_fluent_community_lists') + .then(result => { + if (result && result.success) { + setFluentCommunityConf(prevConf => + create(prevConf, newConf => { + newConf.fluentCommunityList = result.data.fluentCommunityList + }) + ) + setSnackbar({ + show: true, + msg: __('FluentCommunity spaces refreshed', 'bit-integrations') + }) + } else if ( + (result && result.data && result.data.data) || + (!result.success && typeof result.data === 'string') + ) { + setSnackbar({ + show: true, + msg: `${__( + 'FluentCommunity spaces refresh failed Cause:', + 'bit-integrations' + )}${result.data.data || result.data}. ${__('please try again', 'bit-integrations')}` + }) + } else { + setSnackbar({ + show: true, + msg: __('FluentCommunity spaces refresh failed. please try again', 'bit-integrations') + }) + } + setLoading({ ...loading, fluentCommunityList: false }) + }) + .catch(() => setLoading({ ...loading, fluentCommunityList: false })) +} + +export const refreshFluentCommunityHeader = ( + fluentCommunityConf, + setFluentCommunityConf, + setIsLoading, + setSnackbar +) => { + setIsLoading(true) + + // Get fields from static data based on action + const actionName = fluentCommunityConf?.actionName || '' + const fields = actionFields[actionName] || [] + + if (fields.length > 0) { + // Convert static data to the format expected by the UI + const fluentCommunityFields = {} + fields.forEach(field => { + fluentCommunityFields[field.key] = { + key: field.key, + label: field.label, + type: 'primary', + required: field.required + } + }) + + setFluentCommunityConf(prevConf => + create(prevConf, newConf => { + newConf.fluentCommunityFields = fluentCommunityFields + newConf.field_map = mapNewRequiredFields(newConf) + }) + ) + + setSnackbar({ + show: true, + msg: __('Fluent Community fields refreshed', 'bit-integrations') + }) + } else { + setSnackbar({ + show: true, + msg: __('No Fluent Community fields found for this action', 'bit-integrations') + }) + } + + setIsLoading(false) +} + +export const refreshMemberRoles = ( + fluentCommunityConf, + setFluentCommunityConf, + loading, + setLoading, + setSnackbar +) => { + setLoading({ ...loading, memberRoles: true }) + + // Use static member roles data + setFluentCommunityConf(prevConf => + create(prevConf, newConf => { + newConf.memberRoles = memberRoles + }) + ) + + setSnackbar({ + show: true, + msg: __('FluentCommunity member roles refreshed', 'bit-integrations') + }) + + setLoading({ ...loading, memberRoles: false }) +} + +export const refreshCourseList = ( + formID, + fluentCommunityConf, + setFluentCommunityConf, + loading, + setLoading, + setSnackbar +) => { + setLoading({ ...loading, fluentCommunityCourses: true }) + bitsFetch({}, 'refresh_fluent_community_courses') + .then(result => { + if (result && result.success) { + setFluentCommunityConf(prevConf => + create(prevConf, newConf => { + newConf.fluentCommunityCourses = result.data.fluentCommunityCourses + }) + ) + setSnackbar({ + show: true, + msg: __('FluentCommunity courses refreshed', 'bit-integrations') + }) + } else if ( + (result && result.data && result.data.data) || + (!result.success && typeof result.data === 'string') + ) { + setSnackbar({ + show: true, + msg: `${__( + 'FluentCommunity courses refresh failed Cause:', + 'bit-integrations' + )}${result.data.data || result.data}. ${__('please try again', 'bit-integrations')}` + }) + } else { + setSnackbar({ + show: true, + msg: __('FluentCommunity courses refresh failed. please try again', 'bit-integrations') + }) + } + setLoading({ ...loading, fluentCommunityCourses: false }) + }) + .catch(() => setLoading({ ...loading, fluentCommunityCourses: false })) +} + +export const refreshUserList = ( + formID, + fluentCommunityConf, + setFluentCommunityConf, + loading, + setLoading, + setSnackbar +) => { + setLoading({ ...loading, fluentCommunityUsers: true }) + bitsFetch({}, 'refresh_fluent_community_users') + .then(result => { + if (result && result.success) { + setFluentCommunityConf(prevConf => + create(prevConf, newConf => { + newConf.fluentCommunityUsers = result.data.fluentCommunityUsers + }) + ) + setSnackbar({ + show: true, + msg: __('FluentCommunity users refreshed', 'bit-integrations') + }) + } else if ( + (result && result.data && result.data.data) || + (!result.success && typeof result.data === 'string') + ) { + setSnackbar({ + show: true, + msg: `${__( + 'FluentCommunity users refresh failed Cause:', + 'bit-integrations' + )}${result.data.data || result.data}. ${__('please try again', 'bit-integrations')}` + }) + } else { + setSnackbar({ + show: true, + msg: __('FluentCommunity users refresh failed. please try again', 'bit-integrations') + }) + } + setLoading({ ...loading, fluentCommunityUsers: false }) + }) + .catch(() => setLoading({ ...loading, fluentCommunityUsers: false })) +} + +export const getAllCompanies = ( + fluentCommunityConf, + setFluentCommunityConf, + loading, + setLoading, + setSnackbar +) => { + setLoading({ ...loading, company: true }) + bitsFetch({}, 'fluent_community_get_all_company').then(result => { + setLoading({ ...loading, company: false }) + + if (result.success && result?.data) { + setFluentCommunityConf(prevConf => + create(prevConf, draftConf => { + draftConf.companies = result.data + }) + ) + setSnackbar({ show: true, msg: __('Fluent Community Companies refreshed', 'bit-integrations') }) + + return + } + + setSnackbar({ + show: true, + msg: __('Fluent Community Companies refresh failed. please try again', 'bit-integrations') + }) + }) +} + +export const mapNewRequiredFields = fluentCommunityConf => { + const { field_map } = fluentCommunityConf + const { fluentCommunityFields } = fluentCommunityConf + const required = Object.values(fluentCommunityFields) + .filter(f => f.required) + .map(f => ({ formField: '', fluentCommunityField: f.key, required: true })) + const requiredFieldNotInFieldMap = required.filter( + f => !field_map.find(m => m.fluentCommunityField === f.fluentCommunityField) + ) + const notEmptyFieldMap = field_map.filter(f => f.fluentCommunityField || f.formField) + const newFieldMap = notEmptyFieldMap.map(f => { + const field = fluentCommunityFields[f.fluentCommunityField] + if (field) { + return { ...f, formField: field.label } + } + return f + }) + return [...requiredFieldNotInFieldMap, ...newFieldMap] +} + +export const handleInput = (e, fluentCommunityConf, setFluentCommunityConf) => { + const newConf = { ...fluentCommunityConf } + newConf.name = e.target.value + setFluentCommunityConf({ ...newConf }) +} +export const checkMappedFields = fluentCommunityConf => { + const mappedFields = fluentCommunityConf?.field_map + ? fluentCommunityConf.field_map.filter( + mappedField => !mappedField.formField && mappedField.fluentCommunityField && mappedField.required + ) + : [] + if (mappedFields.length > 0) { + return false + } + return true +} diff --git a/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityFieldMap.jsx b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityFieldMap.jsx new file mode 100644 index 00000000..97a12058 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityFieldMap.jsx @@ -0,0 +1,125 @@ +import { useRecoilValue } from 'recoil' +import { $btcbi } from '../../../GlobalStates' +import TrashIcn from '../../../Icons/TrashIcn' +import { __ } from '../../../Utils/i18nwrap' +import { SmartTagField } from '../../../Utils/StaticData/SmartTagField' +import MtInput from '../../Utilities/MtInput' +import TagifyInput from '../../Utilities/TagifyInput' +import { handleCustomValue } from '../IntegrationHelpers/IntegrationHelpers' + +export default function FluentCommunityFieldMap({ + i, + formFields, + field, + fluentCommunityConf, + setFluentCommunityConf +}) { + const isRequired = field.required + const notResquiredField = + fluentCommunityConf?.fluentCommunityFields && + Object.values(fluentCommunityConf?.fluentCommunityFields).filter(f => !f.required) + const btcbi = useRecoilValue($btcbi) + const { isPro } = btcbi + const addFieldMap = indx => { + const newConf = { ...fluentCommunityConf } + newConf.field_map.splice(indx, 0, {}) + setFluentCommunityConf(newConf) + } + + const delFieldMap = indx => { + const newConf = { ...fluentCommunityConf } + if (newConf.field_map.length > 1) { + newConf.field_map.splice(indx, 1) + } + setFluentCommunityConf(newConf) + } + + const handleFieldMapping = (event, indx) => { + const newConf = { ...fluentCommunityConf } + newConf.field_map[indx][event.target.name] = event.target.value + + if (event.target.value === 'custom') { + newConf.field_map[indx].customValue = '' + } + setFluentCommunityConf(newConf) + } + + return ( +
+
+ + + {field.formField === 'custom' && ( + handleCustomValue(e, i, fluentCommunityConf, setFluentCommunityConf)} + label={__('Custom Value', 'bit-integrations')} + className="mr-2" + type="text" + value={field.customValue} + placeholder={__('Custom Value', 'bit-integrations')} + formFields={formFields} + /> + )} + + +
+ {!isRequired && ( + <> + + + + )} +
+ ) +} diff --git a/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityIntegLayout.jsx b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityIntegLayout.jsx new file mode 100644 index 00000000..9e184e3f --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/FluentCommunity/FluentCommunityIntegLayout.jsx @@ -0,0 +1,391 @@ +import { __ } from '../../../Utils/i18nwrap' +import Loader from '../../Loaders/Loader' +import { + refreshCommunityList, + refreshFluentCommunityHeader, + refreshMemberRoles, + refreshCourseList +} from './FluentCommunityCommonFunc' +import FluentCommunityFieldMap from './FluentCommunityFieldMap' +import { actions, pollTypes } from './staticData' +import { useRecoilValue } from 'recoil' +import { $btcbi } from '../../../GlobalStates' +import { checkIsPro, getProLabel } from '../../Utilities/ProUtilHelpers' + +export default function FluentCommunityIntegLayout({ + formID, + formFields, + fluentCommunityConf, + setFluentCommunityConf, + isLoading, + setIsLoading, + loading, + setLoading, + setSnackbar +}) { + const btcbi = useRecoilValue($btcbi) + const { isPro } = btcbi + + // Use static data for actions with pro feature labels + const action = actions.map(action => ({ + value: action.name, + label: checkIsPro(isPro, action.is_pro) ? action.label : getProLabel(action.label), + is_pro: action.is_pro + })) + + const inputHendler = e => { + const newConf = { ...fluentCommunityConf } + if (e.target.name === 'list_id') { + newConf.list_id = e.target.value + } else if (e.target.name === 'course_id') { + newConf.course_id = e.target.value + } else if (e.target.name === 'post_space_id') { + newConf.post_space_id = e.target.value + } else if (e.target.name === 'post_user_id') { + newConf.post_user_id = e.target.value + } else if (e.target.name === 'poll_space_id') { + newConf.poll_space_id = e.target.value + } else if (e.target.name === 'poll_options') { + newConf.poll_options = e.target.value + } + setFluentCommunityConf({ ...newConf }) + } + + const memberRoleHandler = e => { + const newConf = { ...fluentCommunityConf } + newConf.member_role = e.target.value + setFluentCommunityConf({ ...newConf }) + } + + const handleAction = e => { + const newConf = { ...fluentCommunityConf } + const { name, value } = e.target + delete newConf?.fluentCommunityList + delete newConf?.fluentCommunityTags + + if (e.target.value !== '') { + newConf[name] = value + + // Clear existing field mapping and fields when switching actions + newConf.field_map = [] + newConf.fluentCommunityFields = {} + + // First set the action name + setFluentCommunityConf(newConf) + + // Then refresh fields with the updated action name + refreshFluentCommunityHeader(newConf, setFluentCommunityConf, setIsLoading, setSnackbar) + + if (value === 'add-user' || value === 'remove-user') { + refreshCommunityList(formID, newConf, setFluentCommunityConf, loading, setLoading, setSnackbar) + refreshMemberRoles(newConf, setFluentCommunityConf, loading, setLoading, setSnackbar) + } + if (value === 'add-course' || value === 'remove-course') { + refreshCourseList(formID, newConf, setFluentCommunityConf, loading, setLoading, setSnackbar) + } + if (value === 'create-post') { + refreshCommunityList(formID, newConf, setFluentCommunityConf, loading, setLoading, setSnackbar) + } + if (value === 'create-poll') { + refreshCommunityList(formID, newConf, setFluentCommunityConf, loading, setLoading, setSnackbar) + } + } else { + delete newConf[name] + setFluentCommunityConf(newConf) + } + } + + return ( + <> +
+
+ {__('Action:', 'bit-integrations')} + +
+
+ {(loading.fluentCommunityList || + loading.memberRoles || + loading.fluentCommunityCourses || + loading.fluentCommunityUsers) && ( + + )} + {(fluentCommunityConf?.actionName === 'add-user' || + fluentCommunityConf?.actionName === 'remove-user') && + fluentCommunityConf?.fluentCommunityList && + !loading.fluentCommunityList && ( +
+ {__('Fluent Community Space:', 'bit-integrations')} + + +
+ )} + {fluentCommunityConf?.actionName === 'add-user' && + fluentCommunityConf?.memberRoles && + !loading.memberRoles && ( +
+ {__('Member Role:', 'bit-integrations')} + + +
+ )} + {(fluentCommunityConf?.actionName === 'add-course' || + fluentCommunityConf?.actionName === 'remove-course') && + fluentCommunityConf?.fluentCommunityCourses && + !loading.fluentCommunityCourses && ( +
+ {__('Fluent Community Course:', 'bit-integrations')} + + +
+ )} + {fluentCommunityConf?.actionName === 'create-poll' && + fluentCommunityConf?.fluentCommunityList && + !loading.fluentCommunityList && ( +
+ {__('Space:', 'bit-integrations')} + + +
+ )} + {fluentCommunityConf?.actionName === 'create-poll' && ( +
+ {__('Poll Type:', 'bit-integrations')} + +
+ )} + {fluentCommunityConf?.actionName === 'create-post' && + fluentCommunityConf?.fluentCommunityList && + !loading.fluentCommunityList && ( +
+ {__('Space:', 'bit-integrations')} + + +
+ )} + {isLoading && ( + + )} + {fluentCommunityConf?.actionName && !isLoading && ( + <> +
+ {__('Map Fields', 'bit-integrations')} +
+
+
+
+ {__('Form Fields', 'bit-integrations')} +
+
+ {__('Fluent Community Fields', 'bit-integrations')} +
+
+ + {fluentCommunityConf.field_map.map((itm, i) => ( + + ))} + + )} + + ) +} diff --git a/frontend-dev/src/components/AllIntegrations/FluentCommunity/staticData.js b/frontend-dev/src/components/AllIntegrations/FluentCommunity/staticData.js new file mode 100644 index 00000000..e56ffe5e --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/FluentCommunity/staticData.js @@ -0,0 +1,77 @@ +import { __ } from '../../../Utils/i18nwrap' + +// FluentCommunity action definitions +export const actions = [ + { + name: 'add-user', + label: __('Add user to space', 'bit-integrations'), + is_pro: false + }, + { + name: 'remove-user', + label: __('Remove user from space', 'bit-integrations'), + is_pro: true + }, + { + name: 'add-course', + label: __('Add user to course', 'bit-integrations'), + is_pro: true + }, + { + name: 'remove-course', + label: __('Remove user from course', 'bit-integrations'), + is_pro: true + }, + { + name: 'create-post', + label: __('Create new post in feed', 'bit-integrations'), + is_pro: true + }, + { + name: 'create-poll', + label: __('Create poll in feed', 'bit-integrations'), + is_pro: true + }, + { + name: 'verify-user', + label: __('Verify user profile', 'bit-integrations'), + is_pro: true + } +] + +// Field definitions for each action +export const actionFields = { + // All actions except create-post only need email + 'add-user': [{ label: __('Email', 'bit-integrations'), key: 'email', required: true }], + 'remove-user': [{ label: __('Email', 'bit-integrations'), key: 'email', required: true }], + 'add-course': [{ label: __('Email', 'bit-integrations'), key: 'email', required: true }], + 'remove-course': [{ label: __('Email', 'bit-integrations'), key: 'email', required: true }], + // create-post needs email, post title, and post message + 'create-post': [ + { label: __('Email', 'bit-integrations'), key: 'email', required: true }, + { label: __('Post Title', 'bit-integrations'), key: 'post_title', required: true }, + { label: __('Post Message', 'bit-integrations'), key: 'post_message', required: true } + ], + // create-poll needs email, post title, post message, and poll options + 'create-poll': [ + { label: __('Email', 'bit-integrations'), key: 'email', required: true }, + { label: __('Post Title', 'bit-integrations'), key: 'post_title', required: true }, + { label: __('Post Message', 'bit-integrations'), key: 'post_message', required: true }, + { label: __('Poll Options', 'bit-integrations'), key: 'poll_options', required: true } + ], + // verify-user only needs email + 'verify-user': [{ label: __('Email', 'bit-integrations'), key: 'email', required: true }] +} + +// Member roles for add-user action +export const memberRoles = [ + { id: 'member', title: __('Member', 'bit-integrations') }, + { id: 'moderator', title: __('Moderator', 'bit-integrations') }, + { id: 'admin', title: __('Admin', 'bit-integrations') } +] + +// Poll types for create-poll action +export const pollTypes = [ + { id: 'single_choice', title: __('Single Choice', 'bit-integrations') }, + { id: 'multiple_choice', title: __('Multiple Choice', 'bit-integrations') } +] diff --git a/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx b/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx index 5d203d9a..64d8ef97 100644 --- a/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx +++ b/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx @@ -46,6 +46,7 @@ const IntegromatAuthorization = lazy(() => import('./IntegrationHelpers/WebHook/ const IntegratelyAuthorization = lazy(() => import('./IntegrationHelpers/WebHook/WebHooksIntegration')) const TelegramAuthorization = lazy(() => import('./Telegram/TelegramAuthorization')) const FluentCrmAuthorization = lazy(() => import('./FluentCRM/FluentCrmAuthorization')) +const FluentCommunityAuthorization = lazy(() => import('./FluentCommunity/FluentCommunityAuthorization')) const EnchargeAuthorization = lazy(() => import('./Encharge/EnchargeAuthorization')) const GetgistAuthorization = lazy(() => import('./Getgist/GetgistAuthorization')) const ElasticEmailAuthorization = lazy(() => import('./ElasticEmail/ElasticEmailAuthorization')) @@ -330,6 +331,8 @@ export default function IntegInfo() { return case 'Fluent CRM': return + case 'Fluent Community': + return case 'Encharge': return case 'Getgist': diff --git a/frontend-dev/src/components/AllIntegrations/NewInteg.jsx b/frontend-dev/src/components/AllIntegrations/NewInteg.jsx index c06d03d3..63bf42d5 100644 --- a/frontend-dev/src/components/AllIntegrations/NewInteg.jsx +++ b/frontend-dev/src/components/AllIntegrations/NewInteg.jsx @@ -42,6 +42,7 @@ const Integromat = lazy(() => import('./Integromat/Integromat')) const Integrately = lazy(() => import('./Integrately/Integrately')) const Telegram = lazy(() => import('./Telegram/Telegram')) const FluentCrm = lazy(() => import('./FluentCRM/FluentCrm')) +const FluentCommunity = lazy(() => import('./FluentCommunity/FluentCommunity')) const Encharge = lazy(() => import('./Encharge/Encharge')) const Post = lazy(() => import('./PostCreation/Post')) const Registration = lazy(() => import('./Registration/Registration')) @@ -427,6 +428,15 @@ export default function NewInteg({ allIntegURL }) { setFlow={setFlow} /> ) + case 'Fluent Community': + return ( + + ) case 'Autonami': return ( _integrationID = $integrationID; + } + + /** + * Fluent community plugin is exists + * + * @return void + */ + public static function checkedExistsFluentCommunity() + { + if (!is_plugin_active('fluent-community/fluent-community.php')) { + wp_send_json_error(wp_sprintf(__('%s is not active or not installed', 'bit-integrations'), 'Fluent Community'), 400); + } else { + return true; + } + } + + /** + * Fetch Community spaces + * + * @return Fluent Community spaces + */ + public static function fluentCommunityLists() + { + self::checkedExistsFluentCommunity(); + + // Check if FluentCommunity plugin exists and get spaces + if (class_exists('\FluentCommunity\App\Models\Space')) { + $spaces = \FluentCommunity\App\Models\Space::get(); + $fluentCommunityList = []; + foreach ($spaces as $space) { + $fluentCommunityList[$space->title] = (object) [ + 'id' => $space->id, + 'title' => $space->title + ]; + } + } else { + // Fallback to FluentCRM lists if FluentCommunity not available + $lists = Lists::get(); + $fluentCommunityList = []; + foreach ($lists as $list) { + $fluentCommunityList[$list->title] = (object) [ + 'id' => $list->id, + 'title' => $list->title + ]; + } + } + + $response['fluentCommunityList'] = $fluentCommunityList; + wp_send_json_success($response, 200); + } + + /** + * Fetch Community courses + * + * @return Fluent Community courses + */ + public static function fluentCommunityCourses() + { + self::checkedExistsFluentCommunity(); + + $fluentCommunityCourses = []; + + // Use FluentCommunity's Utility::getCourses() method + if (class_exists('\FluentCommunity\App\Functions\Utility')) { + $courses = \FluentCommunity\App\Functions\Utility::getCourses(); + + // Handle different data formats + if (\is_string($courses)) { + // JSON string format + $coursesArray = json_decode($courses, true); + if (\is_array($coursesArray)) { + foreach ($coursesArray as $course) { + if (isset($course['title'], $course['id'])) { + $fluentCommunityCourses[$course['title']] = (object) [ + 'id' => $course['id'], + 'title' => $course['title'] + ]; + } + } + } + } elseif (\is_object($courses) && method_exists($courses, 'toArray')) { + // Collection object - convert to array first + $coursesArray = $courses->toArray(); + foreach ($coursesArray as $course) { + if (isset($course['title'], $course['id'])) { + $fluentCommunityCourses[$course['title']] = (object) [ + 'id' => $course['id'], + 'title' => $course['title'] + ]; + } + } + } elseif (\is_array($courses)) { + // Array format - could be Eloquent models or regular arrays + foreach ($courses as $course) { + if (\is_object($course) && method_exists($course, 'getAttributes')) { + // Eloquent model + if (isset($course->title, $course->id)) { + $fluentCommunityCourses[$course->title] = (object) [ + 'id' => $course->id, + 'title' => $course->title + ]; + } else { + $attributes = $course->getAttributes(); + if (isset($attributes['title'], $attributes['id'])) { + $fluentCommunityCourses[$attributes['title']] = (object) [ + 'id' => $attributes['id'], + 'title' => $attributes['title'] + ]; + } + } + } elseif (\is_array($course) && isset($course['title'], $course['id'])) { + // Regular array format + $fluentCommunityCourses[$course['title']] = (object) [ + 'id' => $course['id'], + 'title' => $course['title'] + ]; + } + } + } + } + + $response['fluentCommunityCourses'] = $fluentCommunityCourses; + wp_send_json_success($response, 200); + } + + public static function fluentCommunityUsers() + { + self::checkedExistsFluentCommunity(); + + $fluentCommunityUsers = []; + + // Get WordPress users + $users = get_users([ + 'number' => 100, // Limit to 100 users + 'orderby' => 'display_name', + 'order' => 'ASC' + ]); + + foreach ($users as $user) { + $fluentCommunityUsers[$user->display_name] = (object) [ + 'id' => $user->ID, + 'display_name' => $user->display_name, + 'user_email' => $user->user_email + ]; + } + + $response['fluentCommunityUsers'] = $fluentCommunityUsers; + wp_send_json_success($response, 200); + } + + /** + * Get user ID by email + * + * @param string $email User email + * + * @return int User ID + */ + public static function getUserByEmail($email) + { + $user = get_user_by('email', $email); + + if ($user) { + return $user->ID; + } + } + + /** + * @return true Fluent community are exists + */ + public static function fluentCommunityAuthorize() + { + if (self::checkedExistsFluentCommunity()) { + wp_send_json_success(true); + } else { + wp_send_json_error( + __( + 'Please! Install Fluent Community', + 'bit-integrations' + ), + 400 + ); + } + } + + public function execute($integrationData, $fieldValues) + { + $integrationDetails = $integrationData->flow_details; + + $fieldMap = $integrationDetails->field_map; + $defaultDataConf = $integrationDetails->default; + $list_id = isset($integrationDetails->list_id) ? $integrationDetails->list_id : null; + $tags = $integrationDetails->tags; + $actions = $integrationDetails->actions; + $actionName = $integrationDetails->actionName; + + if (empty($fieldMap)) { + return new WP_Error('REQ_FIELD_EMPTY', wp_sprintf(__('module, fields are required for %s api', 'bit-integrations'), 'Fluent Community')); + } + + $recordApiHelper = new RecordApiHelper($this->_integrationID); + + $fluentCommunityApiResponse = $recordApiHelper->execute( + $fieldValues, + $fieldMap, + $actions, + $list_id, + $tags, + $actionName + ); + + if (is_wp_error($fluentCommunityApiResponse)) { + return $fluentCommunityApiResponse; + } + + return $fluentCommunityApiResponse; + } +} diff --git a/includes/Actions/FluentCommunity/RecordApiHelper.php b/includes/Actions/FluentCommunity/RecordApiHelper.php new file mode 100644 index 00000000..f7ec4821 --- /dev/null +++ b/includes/Actions/FluentCommunity/RecordApiHelper.php @@ -0,0 +1,249 @@ +_integrationID = $integId; + } + + public function insertRecord($data, $actions) + { + // Get user ID by email + + $userId = FluentCommunityController::getUserByEmail($data['email']); + + if (!$userId) { + return [ + 'success' => false, + 'messages' => __('User not found with this email!', 'bit-integrations') + ]; + } + + try { + $spaceId = $data['space_id']; + $memberRole = $data['member_role'] ?? 'member'; + $by = 'by_automation'; + + // Use FluentCommunity Helper + if (class_exists('\FluentCommunity\App\Services\Helper')) { + \FluentCommunity\App\Services\Helper::addToSpace($spaceId, $userId, $memberRole, $by); + + $response = [ + 'success' => true, + 'messages' => __('User added to space successfully!', 'bit-integrations'), + 'space_id' => $spaceId, + 'user_id' => $userId, + 'role' => $memberRole, + ]; + } else { + $response = [ + 'success' => false, + 'messages' => __('FluentCommunity Helper not available!', 'bit-integrations') + ]; + } + } catch (Exception $e) { + $response = [ + 'success' => false, + 'messages' => $e->getMessage() + ]; + } + + return $response; + } + + public function removeUser($data) + { + // Use pro hook if available + $response = apply_filters('btcbi_fluent_community_remove_user', $data); + + if ($response === $data) { + // Pro feature not available + return [ + 'success' => false, + 'messages' => __('This feature is available in Pro version only!', 'bit-integrations') + ]; + } + + return $response; + } + + public function addCourse($data) + { + // Use pro hook if available + $response = apply_filters('btcbi_fluent_community_add_course', $data); + + if ($response === $data) { + // Pro feature not available + return [ + 'success' => false, + 'messages' => __('This feature is available in Pro version only!', 'bit-integrations') + ]; + } + + return $response; + } + + public function removeCourse($data) + { + // Use pro hook if available + $response = apply_filters('btcbi_fluent_community_remove_course', $data); + + if ($response === $data) { + // Pro feature not available + return [ + 'success' => false, + 'messages' => __('This feature is available in Pro version only!', 'bit-integrations') + ]; + } + + return $response; + } + + public function createPost($data) + { + // Use pro hook if available + $response = apply_filters('btcbi_fluent_community_create_post', $data); + + if ($response === $data) { + // Pro feature not available + return [ + 'success' => false, + 'messages' => __('This feature is available in Pro version only!', 'bit-integrations') + ]; + } + + return $response; + } + + public function createPoll($data) + { + // Use pro hook if available + $response = apply_filters('btcbi_fluent_community_create_poll', $data); + + if ($response === $data) { + // Pro feature not available + return [ + 'success' => false, + 'messages' => __('This feature is available in Pro version only!', 'bit-integrations') + ]; + } + + return $response; + } + + public function verifyUser($data) + { + // Use pro hook if available + $response = apply_filters('btcbi_fluent_community_verify_user', $data); + + if ($response === $data) { + // Pro feature not available + return [ + 'success' => false, + 'messages' => __('This feature is available in Pro version only!', 'bit-integrations') + ]; + } + + return $response; + } + + public function execute($fieldValues, $fieldMap, $actions, $list_id, $tags, $actionName) + { + $fieldData = apply_filters('fluent_community_assign_company', [], (array) $actions); + + foreach ($fieldMap as $fieldKey => $fieldPair) { + if (!empty($fieldPair->fluentCommunityField)) { + if ($fieldPair->formField === 'custom' && isset($fieldPair->customValue)) { + $fieldData[$fieldPair->fluentCommunityField] = $fieldPair->customValue; + } else { + $fieldData[$fieldPair->fluentCommunityField] = $fieldValues[$fieldPair->formField]; + } + } + } + + // For FluentCommunity, use space_id instead of list_id + if (!\is_null($list_id)) { + $fieldData['space_id'] = $list_id; + } + + // Add member role if provided + if (isset($actions->member_role)) { + $fieldData['member_role'] = $actions->member_role; + } + + // Add course_id if provided + if (isset($actions->course_id)) { + $fieldData['course_id'] = $actions->course_id; + } + + // Add post_space_id and post_user_id if provided + if (isset($actions->post_space_id)) { + $fieldData['post_space_id'] = $actions->post_space_id; + } + if (isset($actions->post_user_id)) { + $fieldData['post_user_id'] = $actions->post_user_id; + } + + // Add poll_space_id and poll_options if provided + if (isset($actions->poll_space_id)) { + $fieldData['poll_space_id'] = $actions->poll_space_id; + } + if (isset($actions->poll_options)) { + $fieldData['poll_options'] = $actions->poll_options; + } + + switch ($actionName) { + case 'add-user': + $recordApiResponse = $this->insertRecord($fieldData, $actions); + + break; + case 'remove-user': + $recordApiResponse = $this->removeUser($fieldData); + + break; + case 'add-course': + $recordApiResponse = $this->addCourse($fieldData); + + break; + case 'remove-course': + $recordApiResponse = $this->removeCourse($fieldData); + + break; + case 'create-post': + $recordApiResponse = $this->createPost($fieldData); + + break; + case 'create-poll': + $recordApiResponse = $this->createPoll($fieldData); + + break; + case 'verify-user': + $recordApiResponse = $this->verifyUser($fieldData); + + break; + } + + if ($recordApiResponse['success']) { + LogHandler::save($this->_integrationID, ['type' => 'record', 'type_name' => $actionName], 'success', $recordApiResponse); + } else { + LogHandler::save($this->_integrationID, ['type' => 'record', 'type_name' => $actionName], 'error', $recordApiResponse); + } + + return $recordApiResponse; + } +} diff --git a/includes/Actions/FluentCommunity/Routes.php b/includes/Actions/FluentCommunity/Routes.php new file mode 100644 index 00000000..fcba9dff --- /dev/null +++ b/includes/Actions/FluentCommunity/Routes.php @@ -0,0 +1,13 @@ +