From 65677d307bdec449adc4e49b75f1e66471761762 Mon Sep 17 00:00:00 2001 From: NiloyDas789 <79927385+NiloyDas789@users.noreply.github.com> Date: Sat, 21 Dec 2024 11:20:26 +0600 Subject: [PATCH] Feat: Oneclick Auth & Reauth added in ZohoCRM --- .../GlobalIntegrationHelper.js | 27 +- .../GoogleSheet/GoogleSheetAuthorization.jsx | 4 +- .../GoogleSheet/GoogleSheetCommonFunc.js | 3 +- .../components/AllIntegrations/IntegInfo.jsx | 16 +- .../AllIntegrations/ZohoCRM/EditZohoCRM.jsx | 95 +++-- .../AllIntegrations/ZohoCRM/ZohoCRM.jsx | 24 +- .../ZohoCRM/ZohoCRMAuthorization.jsx | 384 ++++++++++++------ .../ZohoCRM/ZohoCRMCommonFunc.js | 235 +++++++++-- .../AllIntegrations/ZohoCRM/ZohoCRMInfo.jsx | 53 +++ .../AuthorizationAccountList.jsx | 3 +- .../SelectAuthorizationType.jsx | 2 +- frontend-dev/src/pages/AuthResponse.jsx | 32 +- frontend-dev/src/resource/img/avatar.webp | Bin 0 -> 1270 bytes includes/Actions/Mailup/MailupController.php | 1 - includes/Actions/ZohoCRM/RecordApiHelper.php | 8 +- includes/Actions/ZohoCRM/Routes.php | 1 + .../Actions/ZohoCRM/ZohoCRMController.php | 27 +- includes/Flow/Flow.php | 8 +- includes/controller/AuthDataController.php | 4 +- 19 files changed, 668 insertions(+), 259 deletions(-) create mode 100644 frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMInfo.jsx create mode 100644 frontend-dev/src/resource/img/avatar.webp diff --git a/frontend-dev/src/components/AllIntegrations/GlobalIntegrationHelper.js b/frontend-dev/src/components/AllIntegrations/GlobalIntegrationHelper.js index 287d755a8..ee890980a 100644 --- a/frontend-dev/src/components/AllIntegrations/GlobalIntegrationHelper.js +++ b/frontend-dev/src/components/AllIntegrations/GlobalIntegrationHelper.js @@ -34,17 +34,28 @@ export const handleCustomValue = (event, index, conftTmp, setConf) => { setConf({ ...newConf }) } + export const handleAuthData = async (actionName, tokenDetails, userInfo, setAuthData) => { - const requestParams = {}; - requestParams.actionName = actionName - requestParams.tokenDetails = tokenDetails - requestParams.userInfo = userInfo - await bitsFetch(requestParams, 'store/authData').then((resp) => { + const requestParams = { + actionName: actionName, + tokenDetails: tokenDetails, + userInfo: userInfo, + }; + + try { + const resp = await bitsFetch(requestParams, 'store/authData'); + if (resp.success) { if (resp.data.data.length > 0) { setAuthData(resp.data.data); } - // setSnackbar({ show: true, msg: 'Authorization Data Fetched Successfully' }) + } else if (resp.success === false && resp.data?.error) { + throw new Error(resp.data.error); + } else { + throw new Error('Unknown error occurred while storing auth data.'); } - }) -} + } catch (error) { + console.error('handleAuthData error:', error.message); + throw error; + } +}; \ No newline at end of file diff --git a/frontend-dev/src/components/AllIntegrations/GoogleSheet/GoogleSheetAuthorization.jsx b/frontend-dev/src/components/AllIntegrations/GoogleSheet/GoogleSheetAuthorization.jsx index bddba0c77..73375ad9c 100644 --- a/frontend-dev/src/components/AllIntegrations/GoogleSheet/GoogleSheetAuthorization.jsx +++ b/frontend-dev/src/components/AllIntegrations/GoogleSheet/GoogleSheetAuthorization.jsx @@ -99,7 +99,7 @@ export default function GoogleSheetAuthorization({ const handleVerificationCode = async (authInfo) => { - await tokenHelper(authInfo, sheetConf, setSheetConf, selectedAuthType, authData, setAuthData, setIsLoading, setSnackbar); + await tokenHelper(authInfo, sheetConf, setSheetConf, selectedAuthType, setAuthData, setIsLoading, setSnackbar); setAuthInfo(undefined) getAuthData() } @@ -227,7 +227,7 @@ export default function GoogleSheetAuthorization({ )}
- diff --git a/frontend-dev/src/components/AllIntegrations/GoogleSheet/GoogleSheetCommonFunc.js b/frontend-dev/src/components/AllIntegrations/GoogleSheet/GoogleSheetCommonFunc.js index cf95fe84e..93c189e1a 100644 --- a/frontend-dev/src/components/AllIntegrations/GoogleSheet/GoogleSheetCommonFunc.js +++ b/frontend-dev/src/components/AllIntegrations/GoogleSheet/GoogleSheetCommonFunc.js @@ -259,10 +259,9 @@ export const handleAuthorize = (confTmp, selectedAuthType, setError, setIsLoadin } }, 500) } - } -export const tokenHelper = async (authInfo, confTmp, setConf, selectedAuthType, authData, setAuthData, setIsLoading, setSnackbar) => { +export const tokenHelper = async (authInfo, confTmp, setConf, selectedAuthType, setAuthData, setIsLoading, setSnackbar) => { if (!selectedAuthType) { return } diff --git a/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx b/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx index f6fe411f3..e647d45b1 100644 --- a/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx +++ b/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx @@ -28,6 +28,7 @@ const ZohoMarketingHubAuthorization = lazy( ) const ZohoRecruitAuthorization = lazy(() => import('./ZohoRecruit/ZohoRecruitAuthorization')) const GoogleSheetInfo = lazy(() => import('./GoogleSheet/GoogleSheetInfo')) +const ZohoCRMInfo = lazy(() => import('./ZohoCRM/ZohoCRMInfo')) const MailChimpAuthorization = lazy(() => import('./MailChimp/MailChimpAuthorization')) const MailPoetAuthorization = lazy(() => import('./MailPoet/MailPoetAuthorization')) const SendinblueAuthorization = lazy(() => import('./SendinBlue/SendinBlueAuthorization')) @@ -226,15 +227,6 @@ export default function IntegInfo() { const IntegrationInfo = () => { switch (integrationConf.type) { - case 'Zoho CRM': - return ( - - ) case 'Autonami': return case 'Dropbox': @@ -304,6 +296,12 @@ export default function IntegInfo() { isInfo /> ) + case 'Zoho CRM': + return ( + + ) case 'Mail Chimp': return ( { if (!checkMappedFields(crmConf)) { @@ -41,50 +43,69 @@ function EditZohoCRM({ allIntegURL }) { setSnackbar }) } - return ( -
- -
- {__('Integration Name:', 'bit-integrations')} - handleInput(e, tab, crmConf, setCrmConf)} - name="name" - value={crmConf.name} - type="text" - placeholder={__('Integration Name...', 'bit-integrations')} - /> -
-
- - - - handleInput(e, tab, crmConf, setCrmConf, setIsLoading, setSnackbar)} + if (step == 1) { + return ( + + ) + } + + if (step == 2) { + return ( +
+ + +
+ {__('Integration Name:', 'bit-integrations')} + handleInput(e, tab, crmConf, setCrmConf)} + name="name" + value={crmConf.name} + type="text" + placeholder={__('Integration Name...', 'bit-integrations')} + /> +
+
+ + + + handleInput(e, tab, crmConf, setCrmConf, setIsLoading, setSnackbar)} + crmConf={crmConf} + setCrmConf={setCrmConf} + isLoading={isLoading} + setIsLoading={setIsLoading} + setSnackbar={setSnackbar} + /> + + +
+
+ ) + } - -
-
- ) } export default EditZohoCRM diff --git a/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRM.jsx b/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRM.jsx index d244b907a..4a4bca741 100644 --- a/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRM.jsx +++ b/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRM.jsx @@ -10,6 +10,7 @@ import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import ZohoCRMAuthorization from './ZohoCRMAuthorization' import { checkMappedFields, handleInput } from './ZohoCRMCommonFunc' import ZohoCRMIntegLayout from './ZohoCRMIntegLayout' +import bitsFetch from '../../../Utils/bitsFetch' function ZohoCRM({ formFields, setFlow, flow, allIntegURL }) { const navigate = useNavigate() @@ -32,9 +33,28 @@ function ZohoCRM({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) + useEffect(() => { - window.opener && setGrantTokenResponse('zohoCRM') - }, []) + const fetchCredentials = async () => { + if (crmConf.oneClickAuthCredentials === undefined) { + const actionName = "zohoCrm"; + + try { + const userInfoResponse = await fetch('https://auth-apps.bitapps.pro/apps/' + actionName); + const userInfo = await userInfoResponse.json(); + setCrmConf((prevConf) => { + return { ...prevConf, oneClickAuthCredentials: userInfo }; + }); + } catch (error) { + console.error('Error fetching Credentials:', error); + } + } + }; + + fetchCredentials(); + }, []); + + const saveConfig = () => { saveActionConf({ diff --git a/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMAuthorization.jsx b/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMAuthorization.jsx index bbde46bfe..ac924b943 100644 --- a/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMAuthorization.jsx +++ b/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMAuthorization.jsx @@ -1,13 +1,17 @@ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' +import { useEffect, useState } from 'react' +import { useRecoilState, useRecoilValue } from 'recoil' import { __ } from '../../../Utils/i18nwrap' import CopyText from '../../Utilities/CopyText' import LoaderSm from '../../Loaders/LoaderSm' -import { refreshModules } from './ZohoCRMCommonFunc' -import { handleAuthorize } from '../IntegrationHelpers/IntegrationHelpers' -import { $btcbi } from '../../../GlobalStates' +import { handleAuthorize, refreshModules, tokenHelper } from './ZohoCRMCommonFunc' +import { $btcbi, authInfoAtom } from '../../../GlobalStates' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import TutorialLink from '../../Utilities/TutorialLink' +import SelectAuthorizationType from '../../OneClickRadioComponents/SelectAuthorizationType' +import Loader from '../../Loaders/Loader' +import BackIcn from '../../../Icons/BackIcn' +import AuthorizationAccountList from '../../OneClickRadioComponents/AuthorizationAccountList' +import bitsFetch from '../../../Utils/bitsFetch' export default function ZohoCRMAuthorization({ formID, @@ -19,21 +23,38 @@ export default function ZohoCRMAuthorization({ setIsLoading, setSnackbar, redirectLocation, - isInfo + isEdit }) { + const [isAuthorized, setisAuthorized] = useState(false) const [error, setError] = useState({ dataCenter: '', clientId: '', clientSecret: '' }) const btcbi = useRecoilValue($btcbi) const { zohoCRM } = tutorialLinks + const [authData, setAuthData] = useState([]) + const [authInfo, setAuthInfo] = useRecoilState(authInfoAtom); + const [selectedAuthType, setSelectedAuthType] = useState('') + const [selectedUserId, setSelectedUserId] = useState(null) const scopes = 'ZohoCRM.modules.ALL,ZohoCRM.settings.ALL,ZohoCRM.users.Read,zohocrm.files.CREATE' const nextPage = () => { + console.log('crmConf', crmConf) + const selectedAuth = authData.find((item) => item.id === selectedUserId) + setCrmConf((prevConf) => ({ + ...prevConf, + tokenDetails: selectedAuth ? selectedAuth.tokenDetails : '', + authId: selectedAuth ? selectedAuth.id : '', + dataCenter: (selectedAuth?.tokenDetails?.selectedAuthType ?? crmConf?.tokenDetails) === 'One Click Authorization' ? 'com' : '', + })) + setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 }, 300) setstep(2) - !crmConf.module && refreshModules(formID, crmConf, setCrmConf, setIsLoading, setSnackbar) + !crmConf.module && refreshModules(formID, { + ...crmConf, tokenDetails: selectedAuth ? selectedAuth.tokenDetails : '', authId: selectedAuth ? selectedAuth.id : '', + dataCenter: selectedAuth.tokenDetails.selectedAuthType === 'One Click Authorization' ? 'com' : '' + }, setCrmConf, setIsLoading, setSnackbar) } const handleInput = (e) => { const newConf = { ...crmConf } @@ -44,6 +65,94 @@ export default function ZohoCRMAuthorization({ setCrmConf(newConf) } + //Commented for one click authorization + + const handleChange = (option) => { + setSelectedAuthType(option) + setisAuthorized(false) + + setCrmConf((prevConf) => ({ + ...prevConf, + selectedAuthType: option, + ...(option === "One Click Authorization" && process.env.NODE_ENV !== 'development' + ? { + clientId: '', + clientSecret: '', + } + : {}), + })) + + if (option === "One Click Authorization") { + processAuth(option); + } + setIsLoading(false); + }; + + + const processAuth = (option) => { + handleAuthorize('zohoCRM', + 'zcrm', + option, + scopes, + crmConf, + setCrmConf, + setError, + setisAuthorized, + setIsLoading, + setSnackbar, + btcbi); + } + + const getAuthData = () => { + setIsLoading(true) + const queryParams = { + actionName: crmConf.type + } + + bitsFetch(null, 'auth/get', queryParams, 'GET').then((res) => { + if (res.success && res.data.data.length > 0) { + setAuthData(res.data.data); + } + setIsLoading(false) + }) + } + + useEffect(() => { + if (step === 1) { + getAuthData() + } + }, []) + + + + const handleVerificationCode = async (authInfo) => { + await tokenHelper(authInfo, crmConf, setCrmConf, selectedAuthType, setAuthData, setIsLoading, setSnackbar); + setAuthInfo(undefined) + getAuthData() + } + + useEffect(() => { + if (!authInfo || Object.keys(authInfo).length === 0) return; + handleVerificationCode(authInfo); + }, [authInfo]); + + + + useEffect(() => { + + if (step === 1 && isEdit) { + + const authIdExists = authData.find(auth => auth.id === crmConf.authId); + + if (authIdExists) { + setSelectedUserId(crmConf.authId) + } else { + setSelectedUserId(null) + } + } + }, [authData]) + + console.log('authData.length === 0 && !isEdit && (crmConf.tokenDetails == null)', authData.length === 0 && !isEdit && !(crmConf.tokenDetails)) return (
} {zohoCRM?.docLink && } -
- {__('Integration Name:', 'bit-integrations')} +
+

Choose channel

+
- - -
- {__('Data Center:', 'bit-integrations')} -
- -
{error.dataCenter}
- -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Zoho API Console', 'bit-integrations')} - - - -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
- -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
- - {!isInfo && ( - <> -
- - +
+ )} + {isLoading && selectedAuthType !== 'Custom Authorization' && ( + )} + {authData.length > 0 && + <> +

Choose your connected account

+ + + } + + {(isAuthorized && selectedAuthType === "One Click Authorization") && + ( + )} +
+
) } diff --git a/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMCommonFunc.js b/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMCommonFunc.js index b7f9b6287..c7b0a9ab3 100644 --- a/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMCommonFunc.js +++ b/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMCommonFunc.js @@ -1,5 +1,6 @@ import { __, sprintf } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' +import { handleAuthData } from '../GlobalIntegrationHelper' export const handleInput = ( e, @@ -108,7 +109,7 @@ export const layoutChange = (recordTab, formID, crmConf, setCrmConf, setIsLoadin newConf.upload_field_map = newConf?.default?.layouts?.[module]?.[layout]?.requiredFileUploadFields && - Object.keys(newConf.default.layouts[module][layout].requiredFileUploadFields).length > 0 + Object.keys(newConf.default.layouts[module][layout].requiredFileUploadFields).length > 0 ? generateMappedField(recordTab, newConf, true) : [{ formField: '', zohoFormField: '' }] } else { @@ -121,7 +122,7 @@ export const layoutChange = (recordTab, formID, crmConf, setCrmConf, setIsLoadin newConf.relatedlists[recordTab - 1].upload_field_map = newConf?.default?.layouts?.[module]?.[layout]?.requiredFileUploadFields && - Object.keys(newConf.default.layouts[module][layout].requiredFileUploadFields).length > 0 + Object.keys(newConf.default.layouts[module][layout].requiredFileUploadFields).length > 0 ? generateMappedField(recordTab, newConf, true) : [{ formField: '', zohoFormField: '' }] } @@ -134,12 +135,14 @@ export const layoutChange = (recordTab, formID, crmConf, setCrmConf, setIsLoadin export const refreshModules = (formID, crmConf, setCrmConf, setIsLoading, setSnackbar) => { setIsLoading(true) + + const isCustomAuth = !crmConf.tokenDetails?.selectedAuthType || crmConf.tokenDetails.selectedAuthType === 'Custom Authorization' const refreshModulesRequestParams = { formID, id: crmConf.id, - dataCenter: crmConf.dataCenter, - clientId: crmConf.clientId, - clientSecret: crmConf.clientSecret, + dataCenter: isCustomAuth ? crmConf.tokenDetails.dataCenter : 'com', + clientId: isCustomAuth ? crmConf.clientId : crmConf.oneClickAuthCredentials.clientId, + clientSecret: isCustomAuth ? crmConf.clientSecret : crmConf.oneClickAuthCredentials.clientSecret, tokenDetails: crmConf.tokenDetails } bitsFetch(refreshModulesRequestParams, 'zcrm_refresh_modules') @@ -190,13 +193,15 @@ export const refreshLayouts = ( if (!module) { return } + + const isCustomAuth = !crmConf.tokenDetails?.selectedAuthType || crmConf.tokenDetails.selectedAuthType === 'Custom Authorization' setIsLoading(true) const refreshLayoutsRequestParams = { formID, module, - dataCenter: newConf.dataCenter, - clientId: newConf.clientId, - clientSecret: newConf.clientSecret, + dataCenter: isCustomAuth ? crmConf.tokenDetails.dataCenter : 'com', + clientId: isCustomAuth ? crmConf.clientId : crmConf.oneClickAuthCredentials.clientId, + clientSecret: isCustomAuth ? crmConf.clientSecret : crmConf.oneClickAuthCredentials.clientSecret, tokenDetails: newConf.tokenDetails } bitsFetch(refreshLayoutsRequestParams, 'zcrm_refresh_layouts') @@ -261,13 +266,15 @@ export const refreshRelatedList = (formID, crmConf, setCrmConf, setIsLoading, se if (!crmConf.module) { return } + + const isCustomAuth = !crmConf.tokenDetails?.selectedAuthType || crmConf.tokenDetails.selectedAuthType === 'Custom Authorization' setIsLoading(true) const relatedListRequestParams = { formID, module: crmConf.module, - dataCenter: crmConf.dataCenter, - clientId: crmConf.clientId, - clientSecret: crmConf.clientSecret, + dataCenter: isCustomAuth ? crmConf.tokenDetails.dataCenter : 'com', + clientId: isCustomAuth ? crmConf.clientId : crmConf.oneClickAuthCredentials.clientId, + clientSecret: isCustomAuth ? crmConf.clientSecret : crmConf.oneClickAuthCredentials.clientSecret, tokenDetails: crmConf.tokenDetails } bitsFetch(relatedListRequestParams, 'zcrm_get_related_lists') @@ -304,13 +311,15 @@ export const refreshRelatedList = (formID, crmConf, setCrmConf, setIsLoading, se export const refreshTags = (recordTab, formID, crmConf, setCrmConf, setIsLoading, setSnackbar) => { const module = recordTab === 0 ? crmConf.module : crmConf.relatedlists[recordTab - 1].module if (!module) return + + const isCustomAuth = !crmConf.tokenDetails?.selectedAuthType || crmConf.tokenDetails.selectedAuthType === 'Custom Authorization' setIsLoading(true) const refreshTagsParams = { formID, module, - dataCenter: crmConf.dataCenter, - clientId: crmConf.clientId, - clientSecret: crmConf.clientSecret, + dataCenter: isCustomAuth ? crmConf.tokenDetails.dataCenter : 'com', + clientId: isCustomAuth ? crmConf.clientId : crmConf.oneClickAuthCredentials.clientId, + clientSecret: isCustomAuth ? crmConf.clientSecret : crmConf.oneClickAuthCredentials.clientSecret, tokenDetails: crmConf.tokenDetails } bitsFetch(refreshTagsParams, 'zcrm_get_tags') @@ -346,11 +355,13 @@ export const refreshTags = (recordTab, formID, crmConf, setCrmConf, setIsLoading export const refreshOwners = (formID, crmConf, setCrmConf, setIsLoading, setSnackbar) => { setIsLoading(true) + + const isCustomAuth = !crmConf.tokenDetails?.selectedAuthType || crmConf.tokenDetails.selectedAuthType === 'Custom Authorization' const getOwnersParams = { formID, - dataCenter: crmConf.dataCenter, - clientId: crmConf.clientId, - clientSecret: crmConf.clientSecret, + dataCenter: isCustomAuth ? crmConf.tokenDetails.dataCenter : 'com', + clientId: isCustomAuth ? crmConf.clientId : crmConf.oneClickAuthCredentials.clientId, + clientSecret: isCustomAuth ? crmConf.clientSecret : crmConf.oneClickAuthCredentials.clientSecret, tokenDetails: crmConf.tokenDetails } bitsFetch(getOwnersParams, 'zcrm_get_users') @@ -383,12 +394,14 @@ export const refreshAssigmentRules = ( ) => { const module = recordTab === 0 ? crmConf.module : crmConf.relatedlists[recordTab - 1].module if (!module) return + + const isCustomAuth = !crmConf.tokenDetails?.selectedAuthType || crmConf.tokenDetails.selectedAuthType === 'Custom Authorization' setIsLoading(true) const getAssigmentRulesParams = { module, - dataCenter: crmConf.dataCenter, - clientId: crmConf.clientId, - clientSecret: crmConf.clientSecret, + dataCenter: isCustomAuth ? crmConf.tokenDetails.dataCenter : 'com', + clientId: isCustomAuth ? crmConf.clientId : crmConf.oneClickAuthCredentials.clientId, + clientSecret: isCustomAuth ? crmConf.clientSecret : crmConf.oneClickAuthCredentials.clientSecret, tokenDetails: crmConf.tokenDetails } bitsFetch(getAssigmentRulesParams, 'zcrm_get_assignment_rules') @@ -422,39 +435,39 @@ export const generateMappedField = (recordTab, crmConf, uploadFields) => { if (uploadFields) { return crmConf.default.layouts[module][layout].requiredFileUploadFields.length > 0 ? crmConf.default.layouts[module][layout].requiredFileUploadFields.map((field) => ({ - formField: '', - zohoFormField: field - })) + formField: '', + zohoFormField: field + })) : [{ formField: '', zohoFormField: '' }] } return crmConf.default.layouts[module][layout].required.length > 0 ? crmConf.default.layouts[module][layout].required.map((field) => ({ - formField: '', - zohoFormField: field - })) + formField: '', + zohoFormField: field + })) : [{ formField: '', zohoFormField: '' }] } export const checkMappedFields = (crmConf) => { const mappedFields = crmConf?.field_map ? crmConf.field_map.filter( - (mappedField) => - !mappedField.formField && - mappedField.zohoFormField && - crmConf?.default?.layouts?.[crmConf.module]?.[crmConf.layout]?.required.indexOf( - mappedField.zohoFormField - ) !== -1 - ) + (mappedField) => + !mappedField.formField && + mappedField.zohoFormField && + crmConf?.default?.layouts?.[crmConf.module]?.[crmConf.layout]?.required.indexOf( + mappedField.zohoFormField + ) !== -1 + ) : [] const mappedUploadFields = crmConf?.upload_field_map ? crmConf.upload_field_map.filter( - (mappedField) => - !mappedField.formField && - mappedField.zohoFormField && - crmConf.default.layouts[crmConf.module][crmConf.layout].requiredFileUploadFields.indexOf( - mappedField.zohoFormField - ) !== -1 - ) + (mappedField) => + !mappedField.formField && + mappedField.zohoFormField && + crmConf.default.layouts[crmConf.module][crmConf.layout].requiredFileUploadFields.indexOf( + mappedField.zohoFormField + ) !== -1 + ) : [] const mappedRelatedFields = crmConf.relatedlists.map((relatedlist) => relatedlist.field_map.filter( @@ -478,3 +491,145 @@ export const checkMappedFields = (crmConf) => { return true } + +export const handleAuthorize = ( + integ, + ajaxInteg, + selectedAuthType, + scopes, + confTmp, + setConf, + setError, + setisAuthorized, + setIsLoading, + setSnackbar, + btcbi +) => { + + let clientId = ''; + let dataCenter = ''; + if (selectedAuthType === 'One Click Authorization') { + clientId = confTmp.oneClickAuthCredentials.clientId + dataCenter = 'com' + } else if (selectedAuthType === 'Custom Authorization') { + if (!confTmp.clientId || !confTmp.clientSecret || !confTmp.dataCenter) { + setError({ + dataCenter: !confTmp.dataCenter ? __("Data center can't be empty", 'bit-integrations') : '', + clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', + clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '' + }) + return + } + clientId = confTmp.clientId + dataCenter = confTmp.dataCenter + } + + const redirectURI = 'https://auth-apps.bitapps.pro/redirect/v2'; + const finalRedirectUri = selectedAuthType === 'One Click Authorization' ? redirectURI : `${btcbi.api.base}/redirect` + + setIsLoading(true) + + + const { href, hash } = window.location + const stateUrl = hash ? href.replace(hash, '#/auth-response/') : `${href}#/auth-response/` + const apiEndpoint = `https://accounts.zoho.${dataCenter + }/oauth/v2/auth?scope=${scopes}&response_type=code&client_id=${clientId + }&prompt=Consent&access_type=offline&state=${encodeURIComponent(stateUrl)}/redirect&redirect_uri=${encodeURIComponent(finalRedirectUri)}` + + const authWindow = window.open(apiEndpoint, integ, 'width=400,height=609,toolbar=off') + if (selectedAuthType === 'Custom Authorization') { + const popupURLCheckTimer = setInterval(() => { + if (authWindow.closed) { + clearInterval(popupURLCheckTimer) + setIsLoading(false) + } + }, 500) + } +} + + +export const tokenHelper = async (authInfo, confTmp, setConf, selectedAuthType, setAuthData, setIsLoading, setSnackbar) => { + if (!selectedAuthType) { + return; + } + + const tokenRequestParams = {}; + tokenRequestParams.code = authInfo.code || ''; + const redirectURI = 'https://auth-apps.bitapps.pro/redirect/v2'; + + if (selectedAuthType === 'One Click Authorization') { + tokenRequestParams.clientId = confTmp.oneClickAuthCredentials.clientId; + tokenRequestParams.clientSecret = confTmp.oneClickAuthCredentials.clientSecret; + tokenRequestParams.dataCenter = 'com'; + tokenRequestParams.redirectURI = redirectURI; + tokenRequestParams['accounts-server'] = 'https://accounts.zoho.com'; + } else { + tokenRequestParams.clientId = confTmp.clientId; + tokenRequestParams.clientSecret = confTmp.clientSecret; + tokenRequestParams.dataCenter = confTmp.dataCenter; + tokenRequestParams.redirectURI = `${btcbi.api.base}/redirect`; + tokenRequestParams['accounts-server'] = authInfo['accounts-server']; + } + + setIsLoading(true); + try { + const result = await bitsFetch(tokenRequestParams, 'zcrm_generate_token'); + + if (result && result.success) { + const userInfo = await fetchUserInfo(result.data); + + if (userInfo) { + const newConf = { ...confTmp }; + result.data.selectedAuthType = selectedAuthType; + result.data.dataCenter = selectedAuthType === 'One Click Authorization' ? 'com' : confTmp.dataCenter; + try { + await handleAuthData(newConf.type, result.data, userInfo, setAuthData); + newConf.setisAuthorized = true; + setConf(newConf); + setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }); + } catch (error) { + console.error('Authorization failed:', error.message); + setSnackbar({ show: true, msg: error.message }); // Display backend error + } + } + } else { + const errorMessage = result?.data?.error || __('Authorization failed. Please try again.', 'bit-integrations'); + setSnackbar({ show: true, msg: `${__('Authorization failed Cause:', 'bit-integrations')} ${errorMessage}` }); + } + } catch (error) { + console.error('Error during token generation:', error.message); + setSnackbar({ show: true, msg: __('An unexpected error occurred. Please try again.', 'bit-integrations') }); + } finally { + setIsLoading(false); + } +} + + + +async function fetchUserInfo(tokenResponse) { + const requestParams = { + code: tokenResponse.access_token + } + try { + const result = await bitsFetch(requestParams, 'zcrm_get_user_details'); + if (result.data && result.success) { + return result.data; + } else if ((result && result.data && result.data.data) || (!result.success && typeof result.data === 'string')) { + setSnackbar({ + show: true, + msg: `${__('Authorization failed Cause:', 'bit-integrations')} ${result.data.data || result.data}. ${__('Please try again', 'bit-integrations')}` + }); + } else { + setSnackbar({ + show: true, + msg: __('User Fetch Failed. Please try again', 'bit-integrations') + }); + } + } catch (error) { + console.error('Error fetching user info:', error.message); + setSnackbar({ + show: true, + msg: __('An unexpected error occurred. Please try again.', 'bit-integrations') + }); + } +} \ No newline at end of file diff --git a/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMInfo.jsx b/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMInfo.jsx new file mode 100644 index 000000000..8579432b7 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/ZohoCRM/ZohoCRMInfo.jsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from "react" +import bitsFetch from "../../../Utils/bitsFetch"; +import { __ } from "../../../Utils/i18nwrap"; +import AuthorizationAccountList from "../../OneClickRadioComponents/AuthorizationAccountList"; + + +export default function ZohoCRMInfo({ crmConf, isInfo }) { + + const [authData, setAuthData] = useState([]) + + useEffect(() => { + const queryParams = { + id: crmConf.authId + } + bitsFetch(null, 'auth/getbyId', queryParams, 'GET').then((res) => { + if (res.success) { + if (res.data.data.length > 0) { + setAuthData(res.data.data); + } + } + }) + }, []); + return ( +
+ {crmConf.tokenDetails.selectedAuthType == 'Custom Authorization' && ( +
+

Account Details (Custom Authorization)

+
{__('Client id:', 'bit-integrations')}
+ + +
{__('Client secret:', 'bit-integrations')}
+ + +
+ )} + {(crmConf.tokenDetails.selectedAuthType == 'One Click Authorization' && authData.length !== 0) && ( +
+

Account Details (One Click Authorization)

+ +
+ )} + {(crmConf.tokenDetails.selectedAuthType == 'One Click Authorization' && authData.length === 0) && ( +
+

The Authorized Account Has been Deleted. (One Click Authorization)

+
+ )} + +
+ ) +} \ No newline at end of file diff --git a/frontend-dev/src/components/OneClickRadioComponents/AuthorizationAccountList.jsx b/frontend-dev/src/components/OneClickRadioComponents/AuthorizationAccountList.jsx index d6e23c796..bcaba4da9 100644 --- a/frontend-dev/src/components/OneClickRadioComponents/AuthorizationAccountList.jsx +++ b/frontend-dev/src/components/OneClickRadioComponents/AuthorizationAccountList.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import bitsFetch from '../../Utils/bitsFetch'; import TrashIcn from '../../Icons/TrashIcn'; +import Avatar from '../../resource/img/avatar.webp' const AuthorizationAccountList = ({ authData, setAuthData, selectedUserId, setSelectedUserId, setIsLoading, isEdit, isInfo }) => { const [showConfirm, setShowConfirm] = useState(null); @@ -71,7 +72,7 @@ const AuthorizationAccountList = ({ authData, setAuthData, selectedUserId, setSe className="radio-input" />
- User Avatar + User Avatar
{user?.userInfo?.user?.displayName}
{user?.userInfo?.user?.emailAddress}
diff --git a/frontend-dev/src/components/OneClickRadioComponents/SelectAuthorizationType.jsx b/frontend-dev/src/components/OneClickRadioComponents/SelectAuthorizationType.jsx index f2ddc1d20..ea438a3f8 100644 --- a/frontend-dev/src/components/OneClickRadioComponents/SelectAuthorizationType.jsx +++ b/frontend-dev/src/components/OneClickRadioComponents/SelectAuthorizationType.jsx @@ -13,7 +13,7 @@ const SelectAuthorizationType = ({ name, options, selectedAuthType, handleChange onClick={() => handleChange(option)} className="radio-input" /> - {option} + {option}{option === 'One Click Authorization' ? ' (Beta )' : ' (Recomended )'} ))}
diff --git a/frontend-dev/src/pages/AuthResponse.jsx b/frontend-dev/src/pages/AuthResponse.jsx index 89eb13d05..36f346080 100644 --- a/frontend-dev/src/pages/AuthResponse.jsx +++ b/frontend-dev/src/pages/AuthResponse.jsx @@ -1,23 +1,29 @@ -import { useEffect } from 'react' -import { useSetRecoilState } from 'recoil' -import { authInfoAtom } from '../GlobalStates' +import { useEffect } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { authInfoAtom } from '../GlobalStates'; // popup window: render when redirected from oauth to bit-integration with code export default function AuthResponse() { - const setAuthInfo = useSetRecoilState(authInfoAtom) + const setAuthInfo = useSetRecoilState(authInfoAtom); useEffect(() => { - const urlParams = new URLSearchParams(window.location.hash) - const code = urlParams.get('code') + const searchParams = new URLSearchParams(window.location.hash.slice(1)); + const authResponse = {}; - if (code) { - setAuthInfo({ code: code }); + for (const [key, value] of searchParams) { + if (value !== null && value.trim() !== '') { + authResponse[key] = value; + } + } - setTimeout(() => { - window.close() - }, 100) + if (Object.keys(authResponse).length > 0) { + setAuthInfo(authResponse); } - }, []) - return

Auth Response Captured

+ setTimeout(() => { + window.close(); + }, 100); + }, []); + + return

Auth Response Captured

; } diff --git a/frontend-dev/src/resource/img/avatar.webp b/frontend-dev/src/resource/img/avatar.webp new file mode 100644 index 0000000000000000000000000000000000000000..6f4e2adb7be318f167c6196aa30dd95262d9896e GIT binary patch literal 1270 zcmWIYbaQ*h!oU#j>J$(bU=hK^z`$St#QI?5>FgYEf)U7NU_1dLS%6Zxc_l?b?oJ93 zkx>fl4;UDM5DXHNiwhtutRzs?w`&l!mvynKz%R&|l3JV$RPq6cRf>R?1KlJ7WQ(Lj z*cu>q5`=97VwaZ`lmOKPfY?PLLC!#S3XrXlj>JwvVkZ|AfzZ zZ((5Ab{-*y2$O=uqC~K9z}R45N=sv4__UmXfj5$YLHGg#16MRy4oKi|n*vZu2I#JQ zAWUb-VMt|AVDMzfV@PJu1G0>Ok!r|b28>}J24-N?>L1`1uzzeZDL2&e`MvLU@7CPu zZ2u4&_U_#s6N&A)(jBk%zkF-|=4hje=d#e|@AtO9{T+X58*g)t^7CIT_RZlx7)tin znc5mRXH^}#VEk{HS?j`=T$e+VC0gg*EH(MV&+@_8LgMe`+)SyNSFb3rAFdDcWTwelT&e z_AUJ9YwhpzDE)eWVB(!^mNL#=T6sE^%^gs1rJUDyO7^O#d6KU(gapChV+Gr@kWes~ zF63^gT`d>-Oya9_<>}Nfzt=2t4K3aLq_Aw+J*73 z2w(mBM(F&+DGzOr-JT$@+UGL=AIZnb9w$T8kFp+%JmGOg=xBD&&+iTEZO(WH&CLtVvbgP*R#n} z21H+=lUlvciFV{J*vmi>F@c5Ccd!QiZ?JmbQ#n^DD=-eoZN zO3H*SI`S~fKY6vz&g(j#E!=r#hED%i{84%?chBTwTF)eA>8@=Jh?JDhDX(Aw0ODuF AU;qFB literal 0 HcmV?d00001 diff --git a/includes/Actions/Mailup/MailupController.php b/includes/Actions/Mailup/MailupController.php index cb99aed06..39fc14045 100644 --- a/includes/Actions/Mailup/MailupController.php +++ b/includes/Actions/Mailup/MailupController.php @@ -62,7 +62,6 @@ public static function getAllField($requestParams) 'required' => true ] ]; - error_log(print_r($apiResponse, true)); if (!property_exists($apiResponse, 'Items')) { wp_send_json_error('List fetching failed', 400); diff --git a/includes/Actions/ZohoCRM/RecordApiHelper.php b/includes/Actions/ZohoCRM/RecordApiHelper.php index 61abc42a7..b9d8eb577 100644 --- a/includes/Actions/ZohoCRM/RecordApiHelper.php +++ b/includes/Actions/ZohoCRM/RecordApiHelper.php @@ -6,12 +6,12 @@ namespace BitCode\FI\Actions\ZohoCRM; -use stdClass; -use WP_Error; -use BitCode\FI\Log\LogHandler; use BitCode\FI\Core\Util\Common; -use BitCode\FI\Core\Util\HttpHelper; use BitCode\FI\Core\Util\DateTimeHelper; +use BitCode\FI\Core\Util\HttpHelper; +use BitCode\FI\Log\LogHandler; +use stdClass; +use WP_Error; /** * Provide functionality for Record insert,upsert diff --git a/includes/Actions/ZohoCRM/Routes.php b/includes/Actions/ZohoCRM/Routes.php index 8ca7ad92b..17798bc72 100644 --- a/includes/Actions/ZohoCRM/Routes.php +++ b/includes/Actions/ZohoCRM/Routes.php @@ -14,6 +14,7 @@ Route::post('zcrm_generate_token', [ZohoCRMController::class, 'generateTokens']); Route::post('zcrm_refresh_modules', [ZohoCRMController::class, 'refreshModulesAjaxHelper']); Route::post('zcrm_refresh_layouts', [ZohoCRMController::class, 'refreshLayoutsAjaxHelper']); +Route::post('zcrm_get_user_details', [ZohoCRMController::class, 'getUserDetails']); // Rapidmail // Route::post('rapidmail_authorization', [RapidmailController::class, 'checkAuthorization']); diff --git a/includes/Actions/ZohoCRM/ZohoCRMController.php b/includes/Actions/ZohoCRM/ZohoCRMController.php index 19f0792da..829e710c9 100644 --- a/includes/Actions/ZohoCRM/ZohoCRMController.php +++ b/includes/Actions/ZohoCRM/ZohoCRMController.php @@ -6,11 +6,11 @@ namespace BitCode\FI\Actions\ZohoCRM; +use BitCode\FI\Core\Util\HttpHelper; +use BitCode\FI\Flow\FlowController; +use BitCode\FI\Log\LogHandler; use stdClass; use WP_Error; -use BitCode\FI\Log\LogHandler; -use BitCode\FI\Flow\FlowController; -use BitCode\FI\Core\Util\HttpHelper; /** * Provide functionality for ZohoCrm integration @@ -58,6 +58,7 @@ public static function generateTokens($requestsParams) 'code' => $requestsParams->code ]; $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); + if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { wp_send_json_error( empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, @@ -178,7 +179,6 @@ public static function refreshLayoutsAjaxHelper($queryParams) $authorizationHeader['Authorization'] = "Zoho-oauthtoken {$queryParams->tokenDetails->access_token}"; $requiredParams['module'] = $queryParams->module; $layoutsMetaResponse = HttpHelper::get($layoutsMetaApiEndpoint, $requiredParams, $authorizationHeader); - error_log(print_r($layoutsMetaResponse, true)); if (!is_wp_error($layoutsMetaResponse) && (empty($layoutsMetaResponse->status) || (!empty($layoutsMetaResponse->status) && $layoutsMetaResponse->status !== 'error'))) { $retriveLayoutsData = $layoutsMetaResponse->layouts; $layouts = []; @@ -640,6 +640,25 @@ public function execute($integrationData, $fieldValues) return $zcrmApiResponse; } + public static function getUserDetails($requestParams) + { + if (empty($requestParams->code) || $requestParams->code === null) { + return; + } + $apiEndpoint = 'https://www.zohoapis.com/crm/v2/users?type=AllUsers'; + $authorizationHeader['Authorization'] = 'Bearer ' . $requestParams->code; + $authorizationHeader['Content-Type'] = 'application/json'; + + $response = HttpHelper::get($apiEndpoint, null, $authorizationHeader); + + $user['user'] = [ + 'displayName' => $response->users[0]->full_name, + 'emailAddress' => $response->users[0]->email, + ]; + + wp_send_json_success($user, 200); + } + /** * Helps to refresh zoho crm access_token * diff --git a/includes/Flow/Flow.php b/includes/Flow/Flow.php index 197f2a706..36b283bb6 100644 --- a/includes/Flow/Flow.php +++ b/includes/Flow/Flow.php @@ -2,15 +2,15 @@ namespace BitCode\FI\Flow; -use WP_Error; -use BitCode\FI\Log\LogHandler; +use BitCode\FI\Core\Util\Capabilities; use BitCode\FI\Core\Util\Common; +use BitCode\FI\Core\Util\CustomFuncValidator; use BitCode\FI\Core\Util\IpTool; use BitCode\FI\Core\Util\SmartTags; -use BitCode\FI\Core\Util\Capabilities; use BitCode\FI\Core\Util\StoreInCache; +use BitCode\FI\Log\LogHandler; use BitCode\FI\Triggers\TriggerController; -use BitCode\FI\Core\Util\CustomFuncValidator; +use WP_Error; /** * Provides details of available integration and helps to diff --git a/includes/controller/AuthDataController.php b/includes/controller/AuthDataController.php index 107a8a460..702b781ce 100644 --- a/includes/controller/AuthDataController.php +++ b/includes/controller/AuthDataController.php @@ -39,7 +39,7 @@ public function saveAuthData($requestParams) return $this->getAuthData($sanitizedActionName); } - wp_send_json_success(['error' => 'Email address exists.']); + wp_send_json_error(['error' => 'Email address already exists in the list.']); } public function getAuthData($request) @@ -62,7 +62,7 @@ public function getAuthData($request) ); if (is_wp_error($result)) { - wp_send_json_success(['data' => []]); + wp_send_json_error(['data' => []]); exit; }