plugin updates
This commit is contained in:
@@ -1,82 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
import Raven from '../lib/Raven';
|
||||
import { restNonce, restUrl } from '../constants/leadinConfig';
|
||||
import { addQueryObjectToUrl } from '../utils/queryParams';
|
||||
|
||||
function makeRequest(
|
||||
method: string,
|
||||
path: string,
|
||||
data: any = {},
|
||||
queryParams = {}
|
||||
): Promise<any> {
|
||||
// eslint-disable-next-line compat/compat
|
||||
const restApiUrl = new URL(`${restUrl}leadin/v1${path}`);
|
||||
addQueryObjectToUrl(restApiUrl, queryParams);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const payload: { [key: string]: any } = {
|
||||
url: restApiUrl.toString(),
|
||||
method,
|
||||
contentType: 'application/json',
|
||||
beforeSend: (xhr: any) => xhr.setRequestHeader('X-WP-Nonce', restNonce),
|
||||
success: resolve,
|
||||
error: (response: any) => {
|
||||
Raven.captureMessage(
|
||||
`HTTP Request to ${restApiUrl} failed with error ${response.status}: ${response.responseText}`,
|
||||
{
|
||||
fingerprint: [
|
||||
'{{ default }}',
|
||||
path,
|
||||
response.status,
|
||||
response.responseText,
|
||||
],
|
||||
}
|
||||
);
|
||||
reject(response);
|
||||
},
|
||||
};
|
||||
|
||||
if (method !== 'get') {
|
||||
payload.data = JSON.stringify(data);
|
||||
}
|
||||
|
||||
$.ajax(payload);
|
||||
});
|
||||
}
|
||||
|
||||
export function healthcheckRestApi() {
|
||||
return makeRequest('get', '/healthcheck');
|
||||
}
|
||||
|
||||
export function disableInternalTracking(value: boolean) {
|
||||
return makeRequest('put', '/internal-tracking', value ? '1' : '0');
|
||||
}
|
||||
|
||||
export function fetchDisableInternalTracking() {
|
||||
return makeRequest('get', '/internal-tracking').then(message => ({
|
||||
message,
|
||||
}));
|
||||
}
|
||||
|
||||
export function updateHublet(hublet: string) {
|
||||
return makeRequest('put', '/hublet', { hublet });
|
||||
}
|
||||
|
||||
export function skipReview() {
|
||||
return makeRequest('post', '/skip-review');
|
||||
}
|
||||
|
||||
export function trackConsent(canTrack: boolean) {
|
||||
return makeRequest('post', '/track-consent', { canTrack }).then(message => ({
|
||||
message,
|
||||
}));
|
||||
}
|
||||
|
||||
export function setBusinessUnitId(businessUnitId: number) {
|
||||
return makeRequest('put', '/business-unit', { businessUnitId });
|
||||
}
|
||||
|
||||
export function getBusinessUnitId() {
|
||||
return makeRequest('get', '/business-unit');
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
const REGISTRATION_FORM = 'REGISTRATION_FORM';
|
||||
const CONTACT_US_FORM = 'CONTACT_US_FORM';
|
||||
const NEWSLETTER_FORM = 'NEWSLETTER_FORM';
|
||||
const SUPPORT_FORM = 'SUPPORT_FORM';
|
||||
const EVENT_FORM = 'EVENT_FORM';
|
||||
|
||||
export type FormType =
|
||||
| typeof REGISTRATION_FORM
|
||||
| typeof CONTACT_US_FORM
|
||||
| typeof NEWSLETTER_FORM
|
||||
| typeof SUPPORT_FORM
|
||||
| typeof EVENT_FORM;
|
||||
|
||||
export const DEFAULT_OPTIONS = {
|
||||
label: __('Templates', 'leadin'),
|
||||
options: [
|
||||
{ label: __('Registration Form', 'leadin'), value: REGISTRATION_FORM },
|
||||
{ label: __('Contact us Form', 'leadin'), value: CONTACT_US_FORM },
|
||||
{ label: __('Newsletter sign-up Form', 'leadin'), value: NEWSLETTER_FORM },
|
||||
{ label: __('Support Form', 'leadin'), value: SUPPORT_FORM },
|
||||
{ label: __('Event Registration Form', 'leadin'), value: EVENT_FORM },
|
||||
],
|
||||
};
|
||||
|
||||
export function isDefaultForm(value: FormType) {
|
||||
return (
|
||||
value === REGISTRATION_FORM ||
|
||||
value === CONTACT_US_FORM ||
|
||||
value === NEWSLETTER_FORM ||
|
||||
value === SUPPORT_FORM ||
|
||||
value === EVENT_FORM
|
||||
);
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
interface KeyStringObject {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export type ContentEmbedDetails = {
|
||||
activated: boolean;
|
||||
installed: boolean;
|
||||
canActivate: boolean;
|
||||
canInstall: boolean;
|
||||
nonce: string;
|
||||
};
|
||||
|
||||
export interface LeadinConfig {
|
||||
accountName: string;
|
||||
adminUrl: string;
|
||||
activationTime: string;
|
||||
connectionStatus?: 'Connected' | 'NotConnected';
|
||||
deviceId: string;
|
||||
didDisconnect: '1' | '0';
|
||||
env: string;
|
||||
formsScript: string;
|
||||
meetingsScript: string;
|
||||
formsScriptPayload: string;
|
||||
hublet: string;
|
||||
hubspotBaseUrl: string;
|
||||
hubspotNonce: string;
|
||||
iframeUrl: string;
|
||||
impactLink?: string;
|
||||
lastAuthorizeTime: string;
|
||||
lastDeauthorizeTime: string;
|
||||
lastDisconnectTime: string;
|
||||
leadinPluginVersion: string;
|
||||
leadinQueryParams: KeyStringObject;
|
||||
loginUrl: string;
|
||||
locale: string;
|
||||
phpVersion: string;
|
||||
pluginPath: string;
|
||||
plugins: KeyStringObject;
|
||||
portalDomain: string;
|
||||
portalEmail: string;
|
||||
portalId: number;
|
||||
redirectNonce: string;
|
||||
restNonce: string;
|
||||
restUrl: string;
|
||||
reviewSkippedDate: string;
|
||||
refreshToken?: string;
|
||||
theme: string;
|
||||
trackConsent?: boolean | string;
|
||||
wpVersion: string;
|
||||
contentEmbed: ContentEmbedDetails;
|
||||
requiresContentEmbedScope?: boolean;
|
||||
refreshTokenError?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
accountName,
|
||||
adminUrl,
|
||||
activationTime,
|
||||
connectionStatus,
|
||||
deviceId,
|
||||
didDisconnect,
|
||||
env,
|
||||
formsScript,
|
||||
meetingsScript,
|
||||
formsScriptPayload,
|
||||
hublet,
|
||||
hubspotBaseUrl,
|
||||
hubspotNonce,
|
||||
iframeUrl,
|
||||
impactLink,
|
||||
lastAuthorizeTime,
|
||||
lastDeauthorizeTime,
|
||||
lastDisconnectTime,
|
||||
leadinPluginVersion,
|
||||
leadinQueryParams,
|
||||
locale,
|
||||
loginUrl,
|
||||
phpVersion,
|
||||
pluginPath,
|
||||
plugins,
|
||||
portalDomain,
|
||||
portalEmail,
|
||||
portalId,
|
||||
redirectNonce,
|
||||
restNonce,
|
||||
restUrl,
|
||||
refreshToken,
|
||||
reviewSkippedDate,
|
||||
theme,
|
||||
trackConsent,
|
||||
wpVersion,
|
||||
contentEmbed,
|
||||
requiresContentEmbedScope,
|
||||
refreshTokenError,
|
||||
}: //@ts-expect-error global
|
||||
LeadinConfig = window.leadinConfig;
|
||||
|
||||
export {
|
||||
accountName,
|
||||
adminUrl,
|
||||
activationTime,
|
||||
connectionStatus,
|
||||
deviceId,
|
||||
didDisconnect,
|
||||
env,
|
||||
formsScript,
|
||||
meetingsScript,
|
||||
formsScriptPayload,
|
||||
hublet,
|
||||
hubspotBaseUrl,
|
||||
hubspotNonce,
|
||||
iframeUrl,
|
||||
impactLink,
|
||||
lastAuthorizeTime,
|
||||
lastDeauthorizeTime,
|
||||
lastDisconnectTime,
|
||||
leadinPluginVersion,
|
||||
leadinQueryParams,
|
||||
loginUrl,
|
||||
locale,
|
||||
phpVersion,
|
||||
pluginPath,
|
||||
plugins,
|
||||
portalDomain,
|
||||
portalEmail,
|
||||
portalId,
|
||||
redirectNonce,
|
||||
restNonce,
|
||||
restUrl,
|
||||
refreshToken,
|
||||
reviewSkippedDate,
|
||||
theme,
|
||||
trackConsent,
|
||||
wpVersion,
|
||||
contentEmbed,
|
||||
requiresContentEmbedScope,
|
||||
refreshTokenError,
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
export const domElements = {
|
||||
iframe: '#leadin-iframe',
|
||||
subMenu: '.toplevel_page_leadin > ul',
|
||||
subMenuLinks: '.toplevel_page_leadin > ul a',
|
||||
subMenuButtons: '.toplevel_page_leadin > ul > li',
|
||||
deactivatePluginButton: '[data-slug="leadin"] .deactivate a',
|
||||
deactivateFeedbackForm: 'form.leadin-deactivate-form',
|
||||
deactivateFeedbackSubmit: 'button#leadin-feedback-submit',
|
||||
deactivateFeedbackSkip: 'button#leadin-feedback-skip',
|
||||
thickboxModalClose: '.leadin-modal-close',
|
||||
thickboxModalWindow: 'div#TB_window.thickbox-loading',
|
||||
thickboxModalContent: 'div#TB_ajaxContent.TB_modal',
|
||||
reviewBannerContainer: '#leadin-review-banner',
|
||||
reviewBannerLeaveReviewLink: 'a#leave-review-button',
|
||||
reviewBannerDismissButton: 'a#dismiss-review-banner-button',
|
||||
leadinIframeContainer: 'leadin-iframe-container',
|
||||
leadinIframe: 'leadin-iframe',
|
||||
leadinIframeFallbackContainer: 'leadin-iframe-fallback-container',
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from 'react';
|
||||
import ElementorBanner from './ElementorBanner';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
export default function ConnectPluginBanner() {
|
||||
return (
|
||||
<ElementorBanner>
|
||||
<b
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: __(
|
||||
'The HubSpot plugin is not connected right now To use HubSpot tools on your WordPress site, %1$sconnect the plugin now%2$s'
|
||||
)
|
||||
.replace(
|
||||
'%1$s',
|
||||
'<a class="leadin-banner__link" href="admin.php?page=leadin&bannerClick=true">'
|
||||
)
|
||||
.replace('%2$s', '</a>'),
|
||||
}}
|
||||
></b>
|
||||
</ElementorBanner>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface IElementorBannerProps {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export default function ElementorBanner({
|
||||
type = 'warning',
|
||||
children,
|
||||
}: React.PropsWithChildren<IElementorBannerProps>) {
|
||||
return (
|
||||
<div className="elementor-control-content">
|
||||
<div
|
||||
className={`elementor-control-raw-html elementor-panel-alert elementor-panel-alert-${type}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import React from 'react';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-bottom: 8px;
|
||||
`;
|
||||
|
||||
export default function ElementorButton({
|
||||
children,
|
||||
...params
|
||||
}: React.PropsWithChildren<React.ButtonHTMLAttributes<HTMLButtonElement>>) {
|
||||
return (
|
||||
<Container className="elementor-button-wrapper">
|
||||
<button
|
||||
className="elementor-button elementor-button-default"
|
||||
type="button"
|
||||
{...params}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
interface IElementorWrapperProps {
|
||||
pluginPath?: string;
|
||||
}
|
||||
|
||||
export default styled.div<IElementorWrapperProps>`
|
||||
background-image: ${props =>
|
||||
`url(${props.pluginPath}/plugin/assets/images/hubspot.svg)`};
|
||||
background-color: #f5f8fa;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 25px;
|
||||
background-size: 120px;
|
||||
color: #33475b;
|
||||
font-family: 'Lexend Deca', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
|
||||
padding: ${(props: any) => props.padding || '90px 20% 25px'};
|
||||
|
||||
p {
|
||||
font-size: inherit !important;
|
||||
line-height: 24px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
`;
|
||||
@@ -1,86 +0,0 @@
|
||||
import React, { useState, useEffect, Fragment } from 'react';
|
||||
import { portalId, refreshToken } from '../../constants/leadinConfig';
|
||||
import ElementorBanner from '../Common/ElementorBanner';
|
||||
import UISpinner from '../../shared/UIComponents/UISpinner';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
BackgroudAppContext,
|
||||
useBackgroundAppContext,
|
||||
} from '../../iframe/useBackgroundApp';
|
||||
import useForms from './hooks/useForms';
|
||||
import { getOrCreateBackgroundApp } from '../../utils/backgroundAppUtils';
|
||||
|
||||
interface IElementorFormSelectProps {
|
||||
formId: string;
|
||||
setAttributes: Function;
|
||||
}
|
||||
|
||||
function ElementorFormSelect({
|
||||
formId,
|
||||
setAttributes,
|
||||
}: IElementorFormSelectProps) {
|
||||
const { hasError, forms, loading } = useForms();
|
||||
|
||||
return loading ? (
|
||||
<div>
|
||||
<UISpinner />
|
||||
</div>
|
||||
) : hasError ? (
|
||||
<ElementorBanner type="danger">
|
||||
{__('Please refresh your forms or try again in a few minutes', 'leadin')}
|
||||
</ElementorBanner>
|
||||
) : (
|
||||
<select
|
||||
value={formId}
|
||||
onChange={event => {
|
||||
const selectedForm = forms.find(
|
||||
form => form.value === event.target.value
|
||||
);
|
||||
if (selectedForm) {
|
||||
setAttributes({
|
||||
portalId,
|
||||
formId: selectedForm.value,
|
||||
formName: selectedForm.label,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" disabled={true} selected={true}>
|
||||
{__('Search for a form', 'leadin')}
|
||||
</option>
|
||||
{forms.map(form => (
|
||||
<option key={form.value} value={form.value}>
|
||||
{form.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
function ElementorFormSelectWrapper(props: IElementorFormSelectProps) {
|
||||
const isBackgroundAppReady = useBackgroundAppContext();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{!isBackgroundAppReady ? (
|
||||
<div>
|
||||
<UISpinner />
|
||||
</div>
|
||||
) : (
|
||||
<ElementorFormSelect {...props} />
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ElementorFormSelectContainer(
|
||||
props: IElementorFormSelectProps
|
||||
) {
|
||||
return (
|
||||
<BackgroudAppContext.Provider
|
||||
value={refreshToken && getOrCreateBackgroundApp(refreshToken)}
|
||||
>
|
||||
<ElementorFormSelectWrapper {...props} />
|
||||
</BackgroudAppContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { connectionStatus } from '../../constants/leadinConfig';
|
||||
import ConnectPluginBanner from '../Common/ConnectPluginBanner';
|
||||
import ElementorFormSelect from './ElementorFormSelect';
|
||||
import { IFormAttributes } from './registerFormWidget';
|
||||
|
||||
const ConnectionStatus = {
|
||||
Connected: 'Connected',
|
||||
NotConnected: 'NotConnected',
|
||||
};
|
||||
|
||||
export default function FormControlController(
|
||||
attributes: IFormAttributes,
|
||||
setValue: Function
|
||||
) {
|
||||
return () => {
|
||||
const render = () => {
|
||||
if (connectionStatus === ConnectionStatus.Connected) {
|
||||
return (
|
||||
<ElementorFormSelect
|
||||
formId={attributes.formId}
|
||||
setAttributes={setValue}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <ConnectPluginBanner />;
|
||||
}
|
||||
};
|
||||
return <Fragment>{render()}</Fragment>;
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { connectionStatus } from '../../constants/leadinConfig';
|
||||
import ErrorHandler from '../../shared/Common/ErrorHandler';
|
||||
import FormEdit from '../../shared/Form/FormEdit';
|
||||
import ConnectionStatus from '../../shared/enums/connectionStatus';
|
||||
import { IFormAttributes } from './registerFormWidget';
|
||||
|
||||
export default function FormWidgetController(
|
||||
attributes: IFormAttributes,
|
||||
setValue: Function
|
||||
) {
|
||||
return () => {
|
||||
const render = () => {
|
||||
if (connectionStatus === ConnectionStatus.Connected) {
|
||||
return (
|
||||
<FormEdit
|
||||
attributes={attributes}
|
||||
isSelected={true}
|
||||
setAttributes={setValue}
|
||||
preview={false}
|
||||
origin="elementor"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <ErrorHandler status={401} />;
|
||||
}
|
||||
};
|
||||
return <Fragment>{render()}</Fragment>;
|
||||
};
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import LoadState, { LoadStateType } from '../../../shared/enums/loadState';
|
||||
import { ProxyMessages } from '../../../iframe/integratedMessages';
|
||||
import { usePostAsyncBackgroundMessage } from '../../../iframe/useBackgroundApp';
|
||||
import { IForm } from '../../../shared/types';
|
||||
|
||||
interface FormOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export default function useForms() {
|
||||
const proxy = usePostAsyncBackgroundMessage();
|
||||
const [loadState, setLoadState] = useState<LoadStateType>(
|
||||
LoadState.NotLoaded
|
||||
);
|
||||
const [hasError, setError] = useState(null);
|
||||
const [forms, setForms] = useState<FormOption[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadState === LoadState.NotLoaded) {
|
||||
proxy({
|
||||
key: ProxyMessages.FetchForms,
|
||||
payload: {
|
||||
search: '',
|
||||
},
|
||||
})
|
||||
.then(data => {
|
||||
setForms(
|
||||
data.map((form: IForm) => ({
|
||||
label: form.name,
|
||||
value: form.guid,
|
||||
}))
|
||||
);
|
||||
setLoadState(LoadState.Loaded);
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error);
|
||||
setLoadState(LoadState.Failed);
|
||||
});
|
||||
}
|
||||
}, [loadState]);
|
||||
|
||||
return { forms, loading: loadState === LoadState.Loading, hasError };
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
import FormControlController from './FormControlController';
|
||||
import FormWidgetController from './FormWidgetController';
|
||||
|
||||
export interface IFormAttributes {
|
||||
formId: string;
|
||||
formName: string;
|
||||
portalId: string;
|
||||
}
|
||||
|
||||
export default class registerFormWidget {
|
||||
widgetContainer: Element;
|
||||
attributes: IFormAttributes;
|
||||
controlContainer: Element;
|
||||
setValue: Function;
|
||||
|
||||
constructor(controlContainer: any, widgetContainer: any, setValue: Function) {
|
||||
const attributes = widgetContainer.dataset.attributes
|
||||
? JSON.parse(widgetContainer.dataset.attributes)
|
||||
: {};
|
||||
|
||||
this.widgetContainer = widgetContainer;
|
||||
this.controlContainer = controlContainer;
|
||||
this.setValue = setValue;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
render() {
|
||||
ReactDOM.render(
|
||||
FormWidgetController(this.attributes, this.setValue)(),
|
||||
this.widgetContainer
|
||||
);
|
||||
|
||||
ReactDOM.render(
|
||||
FormControlController(this.attributes, this.setValue)(),
|
||||
this.controlContainer
|
||||
);
|
||||
}
|
||||
|
||||
done() {
|
||||
ReactDOM.unmountComponentAtNode(this.widgetContainer);
|
||||
ReactDOM.unmountComponentAtNode(this.controlContainer);
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import ElementorBanner from '../Common/ElementorBanner';
|
||||
import UISpinner from '../../shared/UIComponents/UISpinner';
|
||||
import ElementorMeetingWarning from './ElementorMeetingWarning';
|
||||
import useMeetings, {
|
||||
useSelectedMeetingCalendar,
|
||||
} from '../../shared/Meeting/hooks/useMeetings';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import Raven from 'raven-js';
|
||||
import {
|
||||
BackgroudAppContext,
|
||||
useBackgroundAppContext,
|
||||
} from '../../iframe/useBackgroundApp';
|
||||
import { refreshToken } from '../../constants/leadinConfig';
|
||||
import { getOrCreateBackgroundApp } from '../../utils/backgroundAppUtils';
|
||||
|
||||
interface IElementorMeetingSelectProps {
|
||||
url: string;
|
||||
setAttributes: Function;
|
||||
}
|
||||
|
||||
function ElementorMeetingSelect({
|
||||
url,
|
||||
setAttributes,
|
||||
}: IElementorMeetingSelectProps) {
|
||||
const {
|
||||
mappedMeetings: meetings,
|
||||
loading,
|
||||
error,
|
||||
reload,
|
||||
connectCalendar,
|
||||
} = useMeetings();
|
||||
const selectedMeetingCalendar = useSelectedMeetingCalendar(url);
|
||||
const [localUrl, setLocalUrl] = useState(url);
|
||||
|
||||
const handleConnectCalendar = () => {
|
||||
return connectCalendar()
|
||||
.then(() => {
|
||||
reload();
|
||||
})
|
||||
.catch(error => {
|
||||
Raven.captureMessage('Unable to connect calendar', {
|
||||
extra: { error },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{loading ? (
|
||||
<div>
|
||||
<UISpinner />
|
||||
</div>
|
||||
) : error ? (
|
||||
<ElementorBanner type="danger">
|
||||
{__(
|
||||
'Please refresh your meetings or try again in a few minutes',
|
||||
'leadin'
|
||||
)}
|
||||
</ElementorBanner>
|
||||
) : (
|
||||
<Fragment>
|
||||
{selectedMeetingCalendar && (
|
||||
<ElementorMeetingWarning
|
||||
status={selectedMeetingCalendar}
|
||||
onConnectCalendar={connectCalendar}
|
||||
/>
|
||||
)}
|
||||
{meetings.length > 1 && (
|
||||
<select
|
||||
value={localUrl}
|
||||
onChange={event => {
|
||||
const newUrl = event.target.value;
|
||||
setLocalUrl(newUrl);
|
||||
setAttributes({
|
||||
url: newUrl,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<option value="" disabled={true} selected={true}>
|
||||
{__('Select a meeting', 'leadin')}
|
||||
</option>
|
||||
{meetings.map(item => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
function ElementorMeetingSelectWrapper(props: IElementorMeetingSelectProps) {
|
||||
const isBackgroundAppReady = useBackgroundAppContext();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{!isBackgroundAppReady ? (
|
||||
<div>
|
||||
<UISpinner />
|
||||
</div>
|
||||
) : (
|
||||
<ElementorMeetingSelect {...props} />
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ElementorMeetingsSelectContainer(
|
||||
props: IElementorMeetingSelectProps
|
||||
) {
|
||||
return (
|
||||
<BackgroudAppContext.Provider
|
||||
value={refreshToken && getOrCreateBackgroundApp(refreshToken)}
|
||||
>
|
||||
<ElementorMeetingSelectWrapper {...props} />
|
||||
</BackgroudAppContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { CURRENT_USER_CALENDAR_MISSING } from '../../shared/Meeting/constants';
|
||||
import ElementorButton from '../Common/ElementorButton';
|
||||
import ElementorBanner from '../Common/ElementorBanner';
|
||||
import { styled } from '@linaria/react';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
const Container = styled.div`
|
||||
padding-bottom: 8px;
|
||||
`;
|
||||
|
||||
interface IMeetingWarningPros {
|
||||
onConnectCalendar: React.MouseEventHandler<HTMLButtonElement>;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export default function MeetingWarning({
|
||||
onConnectCalendar,
|
||||
status,
|
||||
}: IMeetingWarningPros) {
|
||||
const isMeetingOwner = status === CURRENT_USER_CALENDAR_MISSING;
|
||||
const titleText = isMeetingOwner
|
||||
? __('Your calendar is not connected', 'leadin')
|
||||
: __('Calendar is not connected', 'leadin');
|
||||
const titleMessage = isMeetingOwner
|
||||
? __(
|
||||
'Please connect your calendar to activate your scheduling pages',
|
||||
'leadin'
|
||||
)
|
||||
: __(
|
||||
'Make sure that everybody in this meeting has connected their calendar from the Meetings page in HubSpot',
|
||||
'leadin'
|
||||
);
|
||||
return (
|
||||
<Fragment>
|
||||
<Container>
|
||||
<ElementorBanner type="warning">
|
||||
<b>{titleText}</b>
|
||||
<br />
|
||||
{titleMessage}
|
||||
</ElementorBanner>
|
||||
</Container>
|
||||
{isMeetingOwner && (
|
||||
<ElementorButton
|
||||
id="meetings-connect-calendar"
|
||||
onClick={onConnectCalendar}
|
||||
>
|
||||
{__('Connect calendar', 'leadin')}
|
||||
</ElementorButton>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { connectionStatus } from '../../constants/leadinConfig';
|
||||
import ConnectPluginBanner from '../Common/ConnectPluginBanner';
|
||||
import ElementorMeetingSelect from './ElementorMeetingSelect';
|
||||
import { IMeetingAttributes } from './registerMeetingWidget';
|
||||
|
||||
const ConnectionStatus = {
|
||||
Connected: 'Connected',
|
||||
NotConnected: 'NotConnected',
|
||||
};
|
||||
|
||||
export default function MeetingControlController(
|
||||
attributes: IMeetingAttributes,
|
||||
setValue: Function
|
||||
) {
|
||||
return () => {
|
||||
const render = () => {
|
||||
if (connectionStatus === ConnectionStatus.Connected) {
|
||||
return (
|
||||
<ElementorMeetingSelect
|
||||
url={attributes.url}
|
||||
setAttributes={setValue}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <ConnectPluginBanner />;
|
||||
}
|
||||
};
|
||||
return <Fragment>{render()}</Fragment>;
|
||||
};
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { connectionStatus } from '../../constants/leadinConfig';
|
||||
import ErrorHandler from '../../shared/Common/ErrorHandler';
|
||||
import MeetingsEdit from '../../shared/Meeting/MeetingEdit';
|
||||
import { IMeetingAttributes } from './registerMeetingWidget';
|
||||
|
||||
const ConnectionStatus = {
|
||||
Connected: 'Connected',
|
||||
NotConnected: 'NotConnected',
|
||||
};
|
||||
|
||||
export default function MeetingWidgetController(
|
||||
attributes: IMeetingAttributes,
|
||||
setValue: Function
|
||||
) {
|
||||
return () => {
|
||||
const render = () => {
|
||||
if (connectionStatus === ConnectionStatus.Connected) {
|
||||
return (
|
||||
<MeetingsEdit
|
||||
attributes={attributes}
|
||||
isSelected={true}
|
||||
setAttributes={setValue}
|
||||
preview={false}
|
||||
origin="elementor"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <ErrorHandler status={401} />;
|
||||
}
|
||||
};
|
||||
return <Fragment>{render()}</Fragment>;
|
||||
};
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
import MeetingControlController from './MeetingControlController';
|
||||
import MeetingWidgetController from './MeetingWidgetController';
|
||||
|
||||
export interface IMeetingAttributes {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export default class registerMeetingsWidget {
|
||||
widgetContainer: Element;
|
||||
controlContainer: Element;
|
||||
setValue: Function;
|
||||
attributes: IMeetingAttributes;
|
||||
|
||||
constructor(controlContainer: any, widgetContainer: any, setValue: Function) {
|
||||
const attributes = widgetContainer.dataset.attributes
|
||||
? JSON.parse(widgetContainer.dataset.attributes)
|
||||
: {};
|
||||
|
||||
this.widgetContainer = widgetContainer;
|
||||
this.controlContainer = controlContainer;
|
||||
this.setValue = setValue;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
render() {
|
||||
ReactDOM.render(
|
||||
MeetingWidgetController(this.attributes, this.setValue)(),
|
||||
this.widgetContainer
|
||||
);
|
||||
|
||||
ReactDOM.render(
|
||||
MeetingControlController(this.attributes, this.setValue)(),
|
||||
this.controlContainer
|
||||
);
|
||||
}
|
||||
|
||||
done() {
|
||||
ReactDOM.unmountComponentAtNode(this.widgetContainer);
|
||||
ReactDOM.unmountComponentAtNode(this.controlContainer);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
export default function elementorWidget(
|
||||
elementor: any,
|
||||
options: any,
|
||||
callback: Function,
|
||||
done = () => {}
|
||||
) {
|
||||
return elementor.modules.controls.BaseData.extend({
|
||||
onReady() {
|
||||
const self = this;
|
||||
const controlContainer = this.ui.contentEditable.prevObject[0].querySelector(
|
||||
options.controlSelector
|
||||
);
|
||||
let widgetContainer = this.options.element.$el[0].querySelector(
|
||||
options.containerSelector
|
||||
);
|
||||
if (widgetContainer) {
|
||||
callback(controlContainer, widgetContainer, (args: any) =>
|
||||
self.setValue(args)
|
||||
);
|
||||
} else {
|
||||
//@ts-expect-error global
|
||||
window.elementorFrontend.hooks.addAction(
|
||||
`frontend/element_ready/${options.widgetName}.default`,
|
||||
(element: HTMLElement[]) => {
|
||||
widgetContainer = element[0].querySelector(
|
||||
options.containerSelector
|
||||
);
|
||||
callback(controlContainer, widgetContainer, (args: any) =>
|
||||
self.setValue(args)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
saveValue(props: any) {
|
||||
this.setValue(props);
|
||||
},
|
||||
onBeforeDestroy() {
|
||||
//@ts-expect-error global
|
||||
window.elementorFrontend.hooks.removeAction(
|
||||
`frontend/element_ready/${options.widgetName}.default`
|
||||
);
|
||||
done();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { initAppOnReady } from '../utils/appUtils';
|
||||
import renderIframeApp from '../iframe/renderIframeApp';
|
||||
|
||||
initAppOnReady(renderIframeApp);
|
||||
@@ -1,79 +0,0 @@
|
||||
import elementorWidget from '../elementor/elementorWidget';
|
||||
import registerFormWidget from '../elementor/FormWidget/registerFormWidget';
|
||||
import { initBackgroundApp } from '../utils/backgroundAppUtils';
|
||||
import registerMeetingsWidget from '../elementor/MeetingWidget/registerMeetingWidget';
|
||||
|
||||
const ELEMENTOR_READY_INTERVAL = 500;
|
||||
const MAX_POLL_TIMEOUT = 30000;
|
||||
|
||||
const registerElementorWidgets = () => {
|
||||
initBackgroundApp(() => {
|
||||
let FormWidget: any;
|
||||
let MeetingsWidget: any;
|
||||
|
||||
const leadinSelectFormItemView = elementorWidget(
|
||||
//@ts-expect-error global
|
||||
window.elementor,
|
||||
{
|
||||
widgetName: 'hubspot-form',
|
||||
controlSelector: '.elementor-hbspt-form-selector',
|
||||
containerSelector: '.hubspot-form-edit-mode',
|
||||
},
|
||||
(controlContainer: any, widgetContainer: any, setValue: Function) => {
|
||||
FormWidget = new registerFormWidget(
|
||||
controlContainer,
|
||||
widgetContainer,
|
||||
setValue
|
||||
);
|
||||
FormWidget.render();
|
||||
},
|
||||
() => {
|
||||
FormWidget.done();
|
||||
}
|
||||
);
|
||||
|
||||
const leadinSelectMeetingItemView = elementorWidget(
|
||||
//@ts-expect-error global
|
||||
window.elementor,
|
||||
{
|
||||
widgetName: 'hubspot-meeting',
|
||||
controlSelector: '.elementor-hbspt-meeting-selector',
|
||||
containerSelector: '.hubspot-meeting-edit-mode',
|
||||
},
|
||||
(controlContainer: any, widgetContainer: any, setValue: Function) => {
|
||||
MeetingsWidget = new registerMeetingsWidget(
|
||||
controlContainer,
|
||||
widgetContainer,
|
||||
setValue
|
||||
);
|
||||
MeetingsWidget.render();
|
||||
},
|
||||
() => {
|
||||
MeetingsWidget.done();
|
||||
}
|
||||
);
|
||||
|
||||
//@ts-expect-error global
|
||||
window.elementor.addControlView(
|
||||
'leadinformselect',
|
||||
leadinSelectFormItemView
|
||||
);
|
||||
//@ts-expect-error global
|
||||
window.elementor.addControlView(
|
||||
'leadinmeetingselect',
|
||||
leadinSelectMeetingItemView
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const pollForElementorReady = setInterval(() => {
|
||||
const elementorFrontend = (window as any).elementorFrontend;
|
||||
if (elementorFrontend) {
|
||||
registerElementorWidgets();
|
||||
clearInterval(pollForElementorReady);
|
||||
}
|
||||
}, ELEMENTOR_READY_INTERVAL);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(pollForElementorReady);
|
||||
}, MAX_POLL_TIMEOUT);
|
||||
@@ -1,68 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import Raven from '../lib/Raven';
|
||||
import { domElements } from '../constants/selectors';
|
||||
import ThickBoxModal from '../feedback/ThickBoxModal';
|
||||
import { submitFeedbackForm } from '../feedback/feedbackFormApi';
|
||||
import {
|
||||
getOrCreateBackgroundApp,
|
||||
initBackgroundApp,
|
||||
} from '../utils/backgroundAppUtils';
|
||||
import { refreshToken } from '../constants/leadinConfig';
|
||||
import { ProxyMessages } from '../iframe/integratedMessages';
|
||||
|
||||
function deactivatePlugin() {
|
||||
const href = $(domElements.deactivatePluginButton).attr('href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
}
|
||||
|
||||
function setLoadingState() {
|
||||
$(domElements.deactivateFeedbackSubmit).addClass('loading');
|
||||
}
|
||||
|
||||
function submitAndDeactivate(e: Event) {
|
||||
e.preventDefault();
|
||||
setLoadingState();
|
||||
const feedback = $(domElements.deactivateFeedbackForm)
|
||||
.serializeArray()
|
||||
.find(field => field.name === 'feedback');
|
||||
|
||||
submitFeedbackForm(domElements.deactivateFeedbackForm)
|
||||
.then(() => {
|
||||
if (feedback && refreshToken) {
|
||||
const embedder = getOrCreateBackgroundApp(refreshToken);
|
||||
embedder.postMessage({
|
||||
key: ProxyMessages.TrackPluginDeactivation,
|
||||
payload: {
|
||||
type: feedback.value.trim().replace(/[\s']+/g, '_'),
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Raven.captureException(err);
|
||||
})
|
||||
.finally(() => {
|
||||
deactivatePlugin();
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
// eslint-disable-next-line no-new
|
||||
new ThickBoxModal(
|
||||
domElements.deactivatePluginButton,
|
||||
'leadin-feedback-container',
|
||||
'leadin-feedback-window',
|
||||
'leadin-feedback-content'
|
||||
);
|
||||
|
||||
$(domElements.deactivateFeedbackForm)
|
||||
.off('submit')
|
||||
.on('submit', submitAndDeactivate);
|
||||
$(domElements.deactivateFeedbackSkip)
|
||||
.off('click')
|
||||
.on('click', deactivatePlugin);
|
||||
}
|
||||
|
||||
initBackgroundApp(init);
|
||||
@@ -1,10 +0,0 @@
|
||||
import registerFormBlock from '../gutenberg/FormBlock/registerFormBlock';
|
||||
import { registerHubspotSidebar } from '../gutenberg/Sidebar/contentType';
|
||||
import registerMeetingBlock from '../gutenberg/MeetingsBlock/registerMeetingBlock';
|
||||
import { initBackgroundApp } from '../utils/backgroundAppUtils';
|
||||
|
||||
initBackgroundApp([
|
||||
registerFormBlock,
|
||||
registerMeetingBlock,
|
||||
registerHubspotSidebar,
|
||||
]);
|
||||
@@ -1,64 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import {
|
||||
getOrCreateBackgroundApp,
|
||||
initBackgroundApp,
|
||||
} from '../utils/backgroundAppUtils';
|
||||
import { domElements } from '../constants/selectors';
|
||||
import { refreshToken, activationTime } from '../constants/leadinConfig';
|
||||
import { ProxyMessages } from '../iframe/integratedMessages';
|
||||
|
||||
const REVIEW_BANNER_INTRO_PERIOD_DAYS = 15;
|
||||
|
||||
const userIsAfterIntroductoryPeriod = () => {
|
||||
const activationDate = new Date(+activationTime * 1000);
|
||||
const currentDate = new Date();
|
||||
const timeElapsed = new Date(
|
||||
currentDate.getTime() - activationDate.getTime()
|
||||
);
|
||||
|
||||
return timeElapsed.getUTCDate() - 1 >= REVIEW_BANNER_INTRO_PERIOD_DAYS;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds some methods to window when review banner is
|
||||
* displayed to monitor events
|
||||
*/
|
||||
export function initMonitorReviewBanner() {
|
||||
if (refreshToken) {
|
||||
const embedder = getOrCreateBackgroundApp(refreshToken);
|
||||
const container = $(domElements.reviewBannerContainer);
|
||||
if (container && userIsAfterIntroductoryPeriod()) {
|
||||
$(domElements.reviewBannerLeaveReviewLink)
|
||||
.off('click')
|
||||
.on('click', () => {
|
||||
embedder.postMessage({
|
||||
key: ProxyMessages.TrackReviewBannerInteraction,
|
||||
});
|
||||
});
|
||||
|
||||
$(domElements.reviewBannerDismissButton)
|
||||
.off('click')
|
||||
.on('click', () => {
|
||||
embedder.postMessage({
|
||||
key: ProxyMessages.TrackReviewBannerDismissed,
|
||||
});
|
||||
});
|
||||
|
||||
embedder
|
||||
.postAsyncMessage({
|
||||
key: ProxyMessages.FetchContactsCreateSinceActivation,
|
||||
payload: +activationTime * 1000,
|
||||
})
|
||||
.then(({ total }: any) => {
|
||||
if (total >= 5) {
|
||||
container.removeClass('leadin-review-banner--hide');
|
||||
embedder.postMessage({
|
||||
key: ProxyMessages.TrackReviewBannerRender,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initBackgroundApp(initMonitorReviewBanner);
|
||||
@@ -1,50 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import { domElements } from '../constants/selectors';
|
||||
|
||||
export default class ThickBoxModal {
|
||||
openTriggerSelector: string;
|
||||
inlineContentId: string;
|
||||
windowCssClass: string;
|
||||
contentCssClass: string;
|
||||
|
||||
constructor(
|
||||
openTriggerSelector: string,
|
||||
inlineContentId: string,
|
||||
windowCssClass: string,
|
||||
contentCssClass: string
|
||||
) {
|
||||
this.openTriggerSelector = openTriggerSelector;
|
||||
this.inlineContentId = inlineContentId;
|
||||
this.windowCssClass = windowCssClass;
|
||||
this.contentCssClass = contentCssClass;
|
||||
|
||||
$(openTriggerSelector).on('click', this.init.bind(this));
|
||||
}
|
||||
|
||||
close() {
|
||||
//@ts-expect-error global
|
||||
window.tb_remove();
|
||||
}
|
||||
|
||||
init(e: Event) {
|
||||
//@ts-expect-error global
|
||||
window.tb_show(
|
||||
'',
|
||||
`#TB_inline?inlineId=${this.inlineContentId}&modal=true`
|
||||
);
|
||||
// thickbox doesn't respect the width and height url parameters https://core.trac.wordpress.org/ticket/17249
|
||||
// We override thickboxes css with !important in the css
|
||||
$(domElements.thickboxModalWindow).addClass(this.windowCssClass);
|
||||
|
||||
// have to modify the css of the thickbox content container as well
|
||||
$(domElements.thickboxModalContent).addClass(this.contentCssClass);
|
||||
|
||||
// we unbind previous handlers because a thickbox modal is a single global object.
|
||||
// Everytime it is re-opened, it still has old handlers bound
|
||||
$(domElements.thickboxModalClose)
|
||||
.off('click')
|
||||
.on('click', this.close);
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const portalId = '6275621';
|
||||
const formId = '0e8807f8-2ac3-4664-b742-44552bfa09e2';
|
||||
const formSubmissionUrl = `https://api.hsforms.com/submissions/v3/integration/submit/${portalId}/${formId}`;
|
||||
|
||||
export function submitFeedbackForm(formSelector: string) {
|
||||
const formSubmissionPayload = {
|
||||
fields: $(formSelector).serializeArray(),
|
||||
skipValidation: true,
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: formSubmissionUrl,
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(formSubmissionPayload),
|
||||
success: resolve,
|
||||
error: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function CalendarIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_903_1965)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M13.519 2.48009H15.069H15.0697C16.2619 2.48719 17.2262 3.45597 17.2262 4.65016V12.7434C17.223 12.9953 17.1203 13.2226 16.9549 13.3886L12.6148 17.7287C12.4488 17.8941 12.2214 17.9968 11.9689 18H3.29508C2.09637 18 1.125 17.0286 1.125 15.8299V4.65016C1.125 3.45404 2.09314 2.48396 3.28862 2.48009H4.83867V0.930032C4.83867 0.416577 5.25525 0 5.7687 0C6.28216 0 6.69874 0.416577 6.69874 0.930032V2.48009H11.6589V0.930032C11.6589 0.416577 12.0755 0 12.5889 0C13.1024 0 13.519 0.416577 13.519 0.930032V2.48009ZM2.98506 15.8312C2.99863 15.9882 3.12909 16.1115 3.28862 16.1141H11.5814L11.6589 16.0366V13.634C11.6589 12.9494 12.2143 12.394 12.899 12.394H15.2951L15.3726 12.3165V7.4338H2.98506V15.8312ZM4.83868 8.68029H6.07873H6.07937C6.42684 8.68029 6.71037 8.95478 6.72458 9.30032V14.2863C6.72458 14.6428 6.43524 14.9322 6.07873 14.9322H4.83868C4.48217 14.9322 4.19283 14.6428 4.19283 14.2863V9.32615C4.19283 8.96964 4.48217 8.68029 4.83868 8.68029ZM8.53298 8.68029H9.82469H9.82534C10.1728 8.68029 10.4563 8.95478 10.4705 9.30032V14.2863C10.4705 14.6428 10.1812 14.9322 9.82469 14.9322H8.53298C8.17647 14.9322 7.88712 14.6428 7.88712 14.2863V9.32615C7.88712 8.96964 8.17647 8.68029 8.53298 8.68029ZM13.519 8.68029H12.2789C11.9366 8.68029 11.6589 8.95801 11.6589 9.30032V10.5404C11.6589 10.8827 11.9366 11.1604 12.2789 11.1604H13.519C13.8613 11.1604 14.139 10.8827 14.139 10.5404V9.30032C14.139 8.95801 13.8613 8.68029 13.519 8.68029Z"
|
||||
fill="#FF7A59"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_903_1965">
|
||||
<rect width="18" height="18" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function SidebarSprocketIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="20px"
|
||||
height="20px"
|
||||
version="1.1"
|
||||
viewBox="0 0 40 42"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<path
|
||||
d="M28.8989809,30.0402293 C25.817707,30.0402293 23.319363,27.5423949 23.319363,24.461121 C23.319363,21.3798471 25.817707,18.881758 28.8989809,18.881758 C31.98,18.881758 34.4780892,21.3798471 34.4780892,24.461121 C34.4780892,27.5423949 31.98,30.0402293 28.8989809,30.0402293 M30.5692994,13.7199745 L30.5692994,8.75717196 C31.864586,8.14519744 32.7723567,6.8346242 32.7723567,5.31360508 L32.7723567,5.1989554 C32.7723567,3.10010191 31.0546497,1.38264968 28.956051,1.38264968 L28.8414013,1.38264968 C26.7425478,1.38264968 25.0248408,3.10010191 25.0248408,5.1989554 L25.0248408,5.31360508 C25.0248408,6.8346242 25.9328662,8.14519744 27.2281529,8.75717196 L27.2281529,13.7202293 C25.2994904,14.0180637 23.5371974,14.8137325 22.0829299,15.9844331 L8.45643312,5.38417836 C8.54611464,5.0392102 8.6090446,4.6835414 8.60955416,4.310293 C8.61261148,1.93271338 6.68777072,0.00303184713 4.31019108,-2.5477707e-05 C1.93286624,-0.00308280255 0.0029299363,1.92175796 0.000127388535,4.29933756 C-0.0029299363,6.67666244 1.92191083,8.60634396 4.29949044,8.60940128 C5.07426752,8.6104204 5.7912102,8.390293 6.42,8.03284076 L19.8243312,18.4603567 C18.6842038,20.181121 18.0166879,22.2422675 18.0166879,24.461121 C18.0166879,26.7841784 18.7504458,28.9327134 19.9907006,30.7001019 L15.9142675,34.776535 C15.5919745,34.6799745 15.2574522,34.6122038 14.9033121,34.6122038 C12.9499363,34.6122038 11.3659873,36.1961529 11.3659873,38.1497834 C11.3659873,40.103414 12.9499363,41.6871084 14.9033121,41.6871084 C16.8571974,41.6871084 18.4408917,40.103414 18.4408917,38.1497834 C18.4408917,37.7958981 18.3733758,37.461121 18.2765605,37.1390828 L22.3089172,33.1067261 C24.1392357,34.5041784 26.4184713,35.3431592 28.8989809,35.3431592 C34.9089172,35.3431592 39.7810191,30.4710573 39.7810191,24.461121 C39.7810191,19.0203567 35.7840764,14.5255796 30.5692994,13.7199745"
|
||||
id="Fill-1"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function SprocketIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="40px"
|
||||
height="42px"
|
||||
viewBox="0 0 40 42"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<defs>
|
||||
<polygon
|
||||
id="path-1"
|
||||
points="0.000123751494 0 39.7808917 0 39.7808917 41.6871084 0.000123751494 41.6871084"
|
||||
/>
|
||||
</defs>
|
||||
<g
|
||||
id="Page-1"
|
||||
stroke="none"
|
||||
strokeWidth="1"
|
||||
fill="none"
|
||||
fillRule="evenodd"
|
||||
>
|
||||
<g id="HubSpot-Sprocket---Full-Color">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlinkHref="#path-1" />
|
||||
</mask>
|
||||
<g id="path-1" />
|
||||
<path
|
||||
d="M28.8989809,30.0402293 C25.817707,30.0402293 23.319363,27.5423949 23.319363,24.461121 C23.319363,21.3798471 25.817707,18.881758 28.8989809,18.881758 C31.98,18.881758 34.4780892,21.3798471 34.4780892,24.461121 C34.4780892,27.5423949 31.98,30.0402293 28.8989809,30.0402293 M30.5692994,13.7199745 L30.5692994,8.75717196 C31.864586,8.14519744 32.7723567,6.8346242 32.7723567,5.31360508 L32.7723567,5.1989554 C32.7723567,3.10010191 31.0546497,1.38264968 28.956051,1.38264968 L28.8414013,1.38264968 C26.7425478,1.38264968 25.0248408,3.10010191 25.0248408,5.1989554 L25.0248408,5.31360508 C25.0248408,6.8346242 25.9328662,8.14519744 27.2281529,8.75717196 L27.2281529,13.7202293 C25.2994904,14.0180637 23.5371974,14.8137325 22.0829299,15.9844331 L8.45643312,5.38417836 C8.54611464,5.0392102 8.6090446,4.6835414 8.60955416,4.310293 C8.61261148,1.93271338 6.68777072,0.00303184713 4.31019108,-2.5477707e-05 C1.93286624,-0.00308280255 0.0029299363,1.92175796 0.000127388535,4.29933756 C-0.0029299363,6.67666244 1.92191083,8.60634396 4.29949044,8.60940128 C5.07426752,8.6104204 5.7912102,8.390293 6.42,8.03284076 L19.8243312,18.4603567 C18.6842038,20.181121 18.0166879,22.2422675 18.0166879,24.461121 C18.0166879,26.7841784 18.7504458,28.9327134 19.9907006,30.7001019 L15.9142675,34.776535 C15.5919745,34.6799745 15.2574522,34.6122038 14.9033121,34.6122038 C12.9499363,34.6122038 11.3659873,36.1961529 11.3659873,38.1497834 C11.3659873,40.103414 12.9499363,41.6871084 14.9033121,41.6871084 C16.8571974,41.6871084 18.4408917,40.103414 18.4408917,38.1497834 C18.4408917,37.7958981 18.3733758,37.461121 18.2765605,37.1390828 L22.3089172,33.1067261 C24.1392357,34.5041784 26.4184713,35.3431592 28.8989809,35.3431592 C34.9089172,35.3431592 39.7810191,30.4710573 39.7810191,24.461121 C39.7810191,19.0203567 35.7840764,14.5255796 30.5692994,13.7199745"
|
||||
id="Fill-1"
|
||||
fill="#F3785B"
|
||||
fillRule="nonzero"
|
||||
mask="url(#mask-2)"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from 'react';
|
||||
import { RawHTML } from '@wordpress/element';
|
||||
import { IFormBlockAttributes } from './registerFormBlock';
|
||||
|
||||
export default function FormSaveBlock({ attributes }: IFormBlockAttributes) {
|
||||
const { portalId, formId } = attributes;
|
||||
|
||||
if (portalId && formId) {
|
||||
return (
|
||||
<RawHTML className="wp-block-leadin-hubspot-form-block">
|
||||
{`[hubspot portal="${portalId}" id="${formId}" type="form"]`}
|
||||
</RawHTML>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { pluginPath } from '../../constants/leadinConfig';
|
||||
import UIImage from '../UIComponents/UIImage';
|
||||
|
||||
export default function FormGutenbergPreview() {
|
||||
return (
|
||||
<Fragment>
|
||||
<UIImage
|
||||
alt="Create a new Hubspot Form"
|
||||
src={`${pluginPath}/public/assets/images/hubspot-form.png`}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import React from 'react';
|
||||
import * as WpBlocksApi from '@wordpress/blocks';
|
||||
import SprocketIcon from '../Common/SprocketIcon';
|
||||
import FormBlockSave from './FormBlockSave';
|
||||
import { connectionStatus } from '../../constants/leadinConfig';
|
||||
import FormGutenbergPreview from './FormGutenbergPreview';
|
||||
import ErrorHandler from '../../shared/Common/ErrorHandler';
|
||||
import FormEdit from '../../shared/Form/FormEdit';
|
||||
import ConnectionStatus from '../../shared/enums/connectionStatus';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { isFullSiteEditor } from '../../utils/withMetaData';
|
||||
|
||||
export interface IFormBlockAttributes {
|
||||
attributes: {
|
||||
portalId: string;
|
||||
formId: string;
|
||||
preview?: boolean;
|
||||
formName: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IFormBlockProps extends IFormBlockAttributes {
|
||||
setAttributes: Function;
|
||||
isSelected: boolean;
|
||||
context?: any;
|
||||
}
|
||||
|
||||
export default function registerFormBlock() {
|
||||
const editComponent = (props: IFormBlockProps) => {
|
||||
if (props.attributes.preview) {
|
||||
return <FormGutenbergPreview />;
|
||||
} else if (connectionStatus === ConnectionStatus.Connected) {
|
||||
return <FormEdit {...props} origin="gutenberg" preview={true} />;
|
||||
} else {
|
||||
return <ErrorHandler status={401} />;
|
||||
}
|
||||
};
|
||||
|
||||
// We do not support the full site editor: https://issues.hubspotcentral.com/browse/WP-1033
|
||||
if (!WpBlocksApi || isFullSiteEditor()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WpBlocksApi.registerBlockType('leadin/hubspot-form-block', {
|
||||
title: __('HubSpot Form', 'leadin'),
|
||||
description: __('Select and embed a HubSpot form', 'leadin'),
|
||||
icon: SprocketIcon,
|
||||
category: 'leadin-blocks',
|
||||
attributes: {
|
||||
portalId: {
|
||||
type: 'string',
|
||||
default: '',
|
||||
} as WpBlocksApi.BlockAttribute<string>,
|
||||
formId: {
|
||||
type: 'string',
|
||||
} as WpBlocksApi.BlockAttribute<string>,
|
||||
formName: {
|
||||
type: 'string',
|
||||
} as WpBlocksApi.BlockAttribute<string>,
|
||||
preview: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
} as WpBlocksApi.BlockAttribute<boolean>,
|
||||
},
|
||||
example: {
|
||||
attributes: {
|
||||
preview: true,
|
||||
},
|
||||
},
|
||||
edit: editComponent,
|
||||
save: props => <FormBlockSave {...props} />,
|
||||
});
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { pluginPath } from '../../constants/leadinConfig';
|
||||
import UIImage from '../UIComponents/UIImage';
|
||||
|
||||
export default function MeetingGutenbergPreview() {
|
||||
return (
|
||||
<Fragment>
|
||||
<UIImage
|
||||
alt="Create a new Hubspot Meeting"
|
||||
width="100%"
|
||||
src={`${pluginPath}/public/assets/images/hubspot-meetings.png`}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from 'react';
|
||||
import { RawHTML } from '@wordpress/element';
|
||||
import { IMeetingBlockAttributes } from './registerMeetingBlock';
|
||||
|
||||
export default function MeetingSaveBlock({
|
||||
attributes,
|
||||
}: IMeetingBlockAttributes) {
|
||||
const { url } = attributes;
|
||||
|
||||
if (url) {
|
||||
return (
|
||||
<RawHTML className="wp-block-leadin-hubspot-meeting-block">{`[hubspot url="${url}" type="meeting"]`}</RawHTML>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import React from 'react';
|
||||
import * as WpBlocksApi from '@wordpress/blocks';
|
||||
import CalendarIcon from '../Common/CalendarIcon';
|
||||
import { connectionStatus } from '../../constants/leadinConfig';
|
||||
import MeetingGutenbergPreview from './MeetingGutenbergPreview';
|
||||
import MeetingSaveBlock from './MeetingSaveBlock';
|
||||
import MeetingEdit from '../../shared/Meeting/MeetingEdit';
|
||||
import ErrorHandler from '../../shared/Common/ErrorHandler';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { isFullSiteEditor } from '../../utils/withMetaData';
|
||||
|
||||
const ConnectionStatus = {
|
||||
Connected: 'Connected',
|
||||
NotConnected: 'NotConnected',
|
||||
};
|
||||
|
||||
export interface IMeetingBlockAttributes {
|
||||
attributes: {
|
||||
url: string;
|
||||
preview?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IMeetingBlockProps extends IMeetingBlockAttributes {
|
||||
setAttributes: Function;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
export default function registerMeetingBlock() {
|
||||
const editComponent = (props: IMeetingBlockProps) => {
|
||||
if (props.attributes.preview) {
|
||||
return <MeetingGutenbergPreview />;
|
||||
} else if (connectionStatus === ConnectionStatus.Connected) {
|
||||
return <MeetingEdit {...props} preview={true} origin="gutenberg" />;
|
||||
} else {
|
||||
return <ErrorHandler status={401} />;
|
||||
}
|
||||
};
|
||||
|
||||
// We do not support the full site editor: https://issues.hubspotcentral.com/browse/WP-1033
|
||||
if (!WpBlocksApi || isFullSiteEditor()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WpBlocksApi.registerBlockType('leadin/hubspot-meeting-block', {
|
||||
title: __('Hubspot Meetings Scheduler', 'leadin'),
|
||||
description: __(
|
||||
'Schedule meetings faster and forget the back-and-forth emails Your calendar stays full, and you stay productive',
|
||||
'leadin'
|
||||
),
|
||||
icon: CalendarIcon,
|
||||
category: 'leadin-blocks',
|
||||
attributes: {
|
||||
url: {
|
||||
type: 'string',
|
||||
default: '',
|
||||
} as WpBlocksApi.BlockAttribute<string>,
|
||||
preview: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
} as WpBlocksApi.BlockAttribute<boolean>,
|
||||
},
|
||||
example: {
|
||||
attributes: {
|
||||
preview: true,
|
||||
},
|
||||
},
|
||||
edit: editComponent,
|
||||
save: props => <MeetingSaveBlock {...props} />,
|
||||
});
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import React from 'react';
|
||||
import * as WpPluginsLib from '@wordpress/plugins';
|
||||
import { PluginSidebar } from '@wordpress/edit-post';
|
||||
import { PanelBody, Icon } from '@wordpress/components';
|
||||
import { withSelect } from '@wordpress/data';
|
||||
import UISidebarSelectControl from '../UIComponents/UISidebarSelectControl';
|
||||
import SidebarSprocketIcon from '../Common/SidebarSprocketIcon';
|
||||
import styled from 'styled-components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { BackgroudAppContext } from '../../iframe/useBackgroundApp';
|
||||
import { refreshToken } from '../../constants/leadinConfig';
|
||||
import { getOrCreateBackgroundApp } from '../../utils/backgroundAppUtils';
|
||||
|
||||
export function registerHubspotSidebar() {
|
||||
const ContentTypeLabelStyle = styled.div`
|
||||
white-space: normal;
|
||||
text-transform: none;
|
||||
`;
|
||||
|
||||
const ContentTypeLabel = (
|
||||
<ContentTypeLabelStyle>
|
||||
{__(
|
||||
'Select the content type HubSpot Analytics uses to track this page',
|
||||
'leadin'
|
||||
)}
|
||||
</ContentTypeLabelStyle>
|
||||
);
|
||||
|
||||
const LeadinPluginSidebar = ({ postType }: { postType: string }) =>
|
||||
postType ? (
|
||||
<PluginSidebar
|
||||
name="leadin"
|
||||
title="HubSpot"
|
||||
icon={
|
||||
<Icon
|
||||
className="hs-plugin-sidebar-sprocket"
|
||||
icon={SidebarSprocketIcon()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<PanelBody title={__('HubSpot Analytics', 'leadin')} initialOpen={true}>
|
||||
<BackgroudAppContext.Provider
|
||||
value={refreshToken && getOrCreateBackgroundApp(refreshToken)}
|
||||
>
|
||||
<UISidebarSelectControl
|
||||
metaKey="content-type"
|
||||
className="select-content-type"
|
||||
label={ContentTypeLabel}
|
||||
options={[
|
||||
{ label: __('Detect Automatically', 'leadin'), value: '' },
|
||||
{ label: __('Blog Post', 'leadin'), value: 'blog-post' },
|
||||
{
|
||||
label: __('Knowledge Article', 'leadin'),
|
||||
value: 'knowledge-article',
|
||||
},
|
||||
{ label: __('Landing Page', 'leadin'), value: 'landing-page' },
|
||||
{ label: __('Listing Page', 'leadin'), value: 'listing-page' },
|
||||
{
|
||||
label: __('Standard Page', 'leadin'),
|
||||
value: 'standard-page',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</BackgroudAppContext.Provider>
|
||||
</PanelBody>
|
||||
</PluginSidebar>
|
||||
) : null;
|
||||
const LeadinPluginSidebarWrapper = withSelect((select: Function) => {
|
||||
const data = select('core/editor');
|
||||
return {
|
||||
postType:
|
||||
data &&
|
||||
data.getCurrentPostType() &&
|
||||
data.getEditedPostAttribute('meta'),
|
||||
};
|
||||
})(LeadinPluginSidebar);
|
||||
if (WpPluginsLib) {
|
||||
WpPluginsLib.registerPlugin('leadin', {
|
||||
render: LeadinPluginSidebarWrapper,
|
||||
icon: SidebarSprocketIcon,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
export default styled.img`
|
||||
height: ${props => (props.height ? props.height : 'auto')};
|
||||
width: ${props => (props.width ? props.width : 'auto')};
|
||||
`;
|
||||
@@ -1,51 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SelectControl } from '@wordpress/components';
|
||||
import withMetaData from '../../utils/withMetaData';
|
||||
import {
|
||||
useBackgroundAppContext,
|
||||
usePostBackgroundMessage,
|
||||
} from '../../iframe/useBackgroundApp';
|
||||
import { ProxyMessages } from '../../iframe/integratedMessages';
|
||||
|
||||
interface IOption {
|
||||
label: string;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface IUISidebarSelectControlProps {
|
||||
metaValue?: string;
|
||||
metaKey: string;
|
||||
setMetaValue?: Function;
|
||||
options: IOption[];
|
||||
className: string;
|
||||
label: JSX.Element;
|
||||
}
|
||||
|
||||
const UISidebarSelectControl = (props: IUISidebarSelectControlProps) => {
|
||||
const isBackgroundAppReady = useBackgroundAppContext();
|
||||
|
||||
const monitorSidebarMetaChange = usePostBackgroundMessage();
|
||||
|
||||
return (
|
||||
<SelectControl
|
||||
value={props.metaValue}
|
||||
onChange={content => {
|
||||
if (props.setMetaValue) {
|
||||
props.setMetaValue(content);
|
||||
}
|
||||
isBackgroundAppReady &&
|
||||
monitorSidebarMetaChange({
|
||||
key: ProxyMessages.TrackSidebarMetaChange,
|
||||
payload: {
|
||||
metaKey: props.metaKey,
|
||||
},
|
||||
});
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withMetaData(UISidebarSelectControl);
|
||||
@@ -1,54 +0,0 @@
|
||||
import React from 'react';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
const IframeErrorContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 120px;
|
||||
font-family: 'Lexend Deca', Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
font-size: 0.875rem;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-smoothing: antialiased;
|
||||
line-height: 1.5rem;
|
||||
`;
|
||||
|
||||
const ErrorHeader = styled.h1`
|
||||
text-shadow: 0 0 1px transparent;
|
||||
margin-bottom: 1.25rem;
|
||||
color: #33475b;
|
||||
font-size: 1.25rem;
|
||||
`;
|
||||
|
||||
export const IframeErrorPage = () => (
|
||||
<IframeErrorContainer>
|
||||
<img
|
||||
alt="Cannot find page"
|
||||
width="175"
|
||||
src="//static.hsappstatic.net/ui-images/static-1.14/optimized/errors/map.svg"
|
||||
/>
|
||||
<ErrorHeader>
|
||||
{__(
|
||||
'The HubSpot for WordPress plugin is not able to load pages',
|
||||
'leadin'
|
||||
)}
|
||||
</ErrorHeader>
|
||||
<p>
|
||||
{__(
|
||||
'Try disabling your browser extensions and ad blockers, then refresh the page',
|
||||
'leadin'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{__(
|
||||
'Or open the HubSpot for WordPress plugin in a different browser',
|
||||
'leadin'
|
||||
)}
|
||||
</p>
|
||||
</IframeErrorContainer>
|
||||
);
|
||||
@@ -1,15 +0,0 @@
|
||||
export enum App {
|
||||
Forms,
|
||||
LiveChat,
|
||||
Plugin,
|
||||
PluginSettings,
|
||||
Background,
|
||||
}
|
||||
|
||||
export const AppIframe = {
|
||||
[App.Forms]: 'integrated-form-app',
|
||||
[App.LiveChat]: 'integrated-livechat-app',
|
||||
[App.Plugin]: 'integrated-plugin-app',
|
||||
[App.PluginSettings]: 'integrated-plugin-app',
|
||||
[App.Background]: 'integrated-plugin-proxy',
|
||||
} as const;
|
||||
@@ -1,11 +0,0 @@
|
||||
export const CoreMessages = {
|
||||
HandshakeReceive: 'INTEGRATED_APP_EMBEDDER_HANDSHAKE_RECEIVED',
|
||||
SendRefreshToken: 'INTEGRATED_APP_EMBEDDER_SEND_REFRESH_TOKEN',
|
||||
ReloadParentFrame: 'INTEGRATED_APP_EMBEDDER_RELOAD_PARENT_FRAME',
|
||||
RedirectParentFrame: 'INTEGRATED_APP_EMBEDDER_REDIRECT_PARENT_FRAME',
|
||||
SendLocale: 'INTEGRATED_APP_EMBEDDER_SEND_LOCALE',
|
||||
SendDeviceId: 'INTEGRATED_APP_EMBEDDER_SEND_DEVICE_ID',
|
||||
SendIntegratedAppConfig: 'INTEGRATED_APP_EMBEDDER_CONFIG',
|
||||
} as const;
|
||||
|
||||
export type CoreMessageType = typeof CoreMessages[keyof typeof CoreMessages];
|
||||
@@ -1,5 +0,0 @@
|
||||
export const FormMessages = {
|
||||
CreateFormAppNavigation: 'CREATE_FORM_APP_NAVIGATION',
|
||||
} as const;
|
||||
|
||||
export type FormMessageType = typeof FormMessages[keyof typeof FormMessages];
|
||||
@@ -1,18 +0,0 @@
|
||||
import * as Core from './core/CoreMessages';
|
||||
import * as Forms from './forms/FormsMessages';
|
||||
import * as LiveChat from './livechat/LiveChatMessages';
|
||||
import * as Plugin from './plugin/PluginMessages';
|
||||
import * as Proxy from './proxy/ProxyMessages';
|
||||
|
||||
export type MessageType =
|
||||
| Core.CoreMessageType
|
||||
| Forms.FormMessageType
|
||||
| LiveChat.LiveChatMessageType
|
||||
| Plugin.PluginMessageType
|
||||
| Proxy.ProxyMessageType;
|
||||
|
||||
export * from './core/CoreMessages';
|
||||
export * from './forms/FormsMessages';
|
||||
export * from './livechat/LiveChatMessages';
|
||||
export * from './plugin/PluginMessages';
|
||||
export * from './proxy/ProxyMessages';
|
||||
@@ -1,5 +0,0 @@
|
||||
export const LiveChatMessages = {
|
||||
CreateLiveChatAppNavigation: 'CREATE_LIVE_CHAT_APP_NAVIGATION',
|
||||
} as const;
|
||||
|
||||
export type LiveChatMessageType = typeof LiveChatMessages[keyof typeof LiveChatMessages];
|
||||
@@ -1,27 +0,0 @@
|
||||
export const PluginMessages = {
|
||||
PluginSettingsNavigation: 'PLUGIN_SETTINGS_NAVIGATION',
|
||||
PluginLeadinConfig: 'PLUGIN_LEADIN_CONFIG',
|
||||
TrackConsent: 'INTEGRATED_APP_EMBEDDER_TRACK_CONSENT',
|
||||
InternalTrackingFetchRequest: 'INTEGRATED_TRACKING_FETCH_REQUEST',
|
||||
InternalTrackingFetchResponse: 'INTEGRATED_TRACKING_FETCH_RESPONSE',
|
||||
InternalTrackingFetchError: 'INTEGRATED_TRACKING_FETCH_ERROR',
|
||||
InternalTrackingChangeRequest: 'INTEGRATED_TRACKING_CHANGE_REQUEST',
|
||||
InternalTrackingChangeError: 'INTEGRATED_TRACKING_CHANGE_ERROR',
|
||||
BusinessUnitFetchRequest: 'BUSINESS_UNIT_FETCH_REQUEST',
|
||||
BusinessUnitFetchResponse: 'BUSINESS_UNIT_FETCH_FETCH_RESPONSE',
|
||||
BusinessUnitFetchError: 'BUSINESS_UNIT_FETCH_FETCH_ERROR',
|
||||
BusinessUnitChangeRequest: 'BUSINESS_UNIT_CHANGE_REQUEST',
|
||||
BusinessUnitChangeError: 'BUSINESS_UNIT_CHANGE_ERROR',
|
||||
SkipReviewRequest: 'SKIP_REVIEW_REQUEST',
|
||||
SkipReviewResponse: 'SKIP_REVIEW_RESPONSE',
|
||||
SkipReviewError: 'SKIP_REVIEW_ERROR',
|
||||
RemoveParentQueryParam: 'REMOVE_PARENT_QUERY_PARAM',
|
||||
ContentEmbedInstallRequest: 'CONTENT_EMBED_INSTALL_REQUEST',
|
||||
ContentEmbedInstallResponse: 'CONTENT_EMBED_INSTALL_RESPONSE',
|
||||
ContentEmbedInstallError: 'CONTENT_EMBED_INSTALL_ERROR',
|
||||
ContentEmbedActivationRequest: 'CONTENT_EMBED_ACTIVATION_REQUEST',
|
||||
ContentEmbedActivationResponse: 'CONTENT_EMBED_ACTIVATION_RESPONSE',
|
||||
ContentEmbedActivationError: 'CONTENT_EMBED_ACTIVATION_ERROR',
|
||||
} as const;
|
||||
|
||||
export type PluginMessageType = typeof PluginMessages[keyof typeof PluginMessages];
|
||||
@@ -1,21 +0,0 @@
|
||||
export const ProxyMessages = {
|
||||
FetchForms: 'FETCH_FORMS',
|
||||
FetchForm: 'FETCH_FORM',
|
||||
CreateFormFromTemplate: 'CREATE_FORM_FROM_TEMPLATE',
|
||||
FetchAuth: 'FETCH_AUTH',
|
||||
FetchMeetingsAndUsers: 'FETCH_MEETINGS_AND_USERS',
|
||||
FetchContactsCreateSinceActivation: 'FETCH_CONTACTS_CREATED_SINCE_ACTIVATION',
|
||||
FetchOrCreateMeetingUser: 'FETCH_OR_CREATE_MEETING_USER',
|
||||
ConnectMeetingsCalendar: 'CONNECT_MEETINGS_CALENDAR',
|
||||
TrackFormPreviewRender: 'TRACK_FORM_PREVIEW_RENDER',
|
||||
TrackFormCreatedFromTemplate: 'TRACK_FORM_CREATED_FROM_TEMPLATE',
|
||||
TrackFormCreationFailed: 'TRACK_FORM_CREATION_FAILED',
|
||||
TrackMeetingPreviewRender: 'TRACK_MEETING_PREVIEW_RENDER',
|
||||
TrackSidebarMetaChange: 'TRACK_SIDEBAR_META_CHANGE',
|
||||
TrackReviewBannerRender: 'TRACK_REVIEW_BANNER_RENDER',
|
||||
TrackReviewBannerInteraction: 'TRACK_REVIEW_BANNER_INTERACTION',
|
||||
TrackReviewBannerDismissed: 'TRACK_REVIEW_BANNER_DISMISSED',
|
||||
TrackPluginDeactivation: 'TRACK_PLUGIN_DEACTIVATION',
|
||||
} as const;
|
||||
|
||||
export type ProxyMessageType = typeof ProxyMessages[keyof typeof ProxyMessages];
|
||||
@@ -1,161 +0,0 @@
|
||||
import { MessageType, PluginMessages } from '../iframe/integratedMessages';
|
||||
import {
|
||||
fetchDisableInternalTracking,
|
||||
trackConsent,
|
||||
disableInternalTracking,
|
||||
getBusinessUnitId,
|
||||
setBusinessUnitId,
|
||||
skipReview,
|
||||
} from '../api/wordpressApiClient';
|
||||
import { removeQueryParamFromLocation } from '../utils/queryParams';
|
||||
import { startActivation, startInstall } from '../utils/contentEmbedInstaller';
|
||||
|
||||
export type Message = { key: MessageType; payload?: any };
|
||||
|
||||
const messageMapper: Map<MessageType, Function> = new Map([
|
||||
[
|
||||
PluginMessages.TrackConsent,
|
||||
(message: Message) => {
|
||||
trackConsent(message.payload);
|
||||
},
|
||||
],
|
||||
[
|
||||
PluginMessages.InternalTrackingChangeRequest,
|
||||
(message: Message, embedder: any) => {
|
||||
disableInternalTracking(message.payload)
|
||||
.then(() => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.InternalTrackingFetchResponse,
|
||||
payload: message.payload,
|
||||
});
|
||||
})
|
||||
.catch(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.InternalTrackingChangeError,
|
||||
payload,
|
||||
});
|
||||
});
|
||||
},
|
||||
],
|
||||
[
|
||||
PluginMessages.InternalTrackingFetchRequest,
|
||||
(__message: Message, embedder: any) => {
|
||||
fetchDisableInternalTracking()
|
||||
.then(({ message: payload }) => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.InternalTrackingFetchResponse,
|
||||
payload,
|
||||
});
|
||||
})
|
||||
.catch(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.InternalTrackingFetchError,
|
||||
payload,
|
||||
});
|
||||
});
|
||||
},
|
||||
],
|
||||
[
|
||||
PluginMessages.BusinessUnitFetchRequest,
|
||||
(__message: Message, embedder: any) => {
|
||||
getBusinessUnitId()
|
||||
.then(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.BusinessUnitFetchResponse,
|
||||
payload,
|
||||
});
|
||||
})
|
||||
.catch(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.BusinessUnitFetchError,
|
||||
payload,
|
||||
});
|
||||
});
|
||||
},
|
||||
],
|
||||
[
|
||||
PluginMessages.BusinessUnitChangeRequest,
|
||||
(message: Message, embedder: any) => {
|
||||
setBusinessUnitId(message.payload)
|
||||
.then(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.BusinessUnitFetchResponse,
|
||||
payload,
|
||||
});
|
||||
})
|
||||
.catch(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.BusinessUnitChangeError,
|
||||
payload,
|
||||
});
|
||||
});
|
||||
},
|
||||
],
|
||||
[
|
||||
PluginMessages.SkipReviewRequest,
|
||||
(__message: Message, embedder: any) => {
|
||||
skipReview()
|
||||
.then(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.SkipReviewResponse,
|
||||
payload,
|
||||
});
|
||||
})
|
||||
.catch(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.SkipReviewError,
|
||||
payload,
|
||||
});
|
||||
});
|
||||
},
|
||||
],
|
||||
[
|
||||
PluginMessages.RemoveParentQueryParam,
|
||||
(message: Message) => {
|
||||
removeQueryParamFromLocation(message.payload);
|
||||
},
|
||||
],
|
||||
[
|
||||
PluginMessages.ContentEmbedInstallRequest,
|
||||
(message: Message, embedder: any) => {
|
||||
startInstall(message.payload.nonce)
|
||||
.then(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.ContentEmbedInstallResponse,
|
||||
payload: payload,
|
||||
});
|
||||
})
|
||||
.catch(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.ContentEmbedInstallError,
|
||||
payload,
|
||||
});
|
||||
});
|
||||
},
|
||||
],
|
||||
[
|
||||
PluginMessages.ContentEmbedActivationRequest,
|
||||
(message: Message, embedder: any) => {
|
||||
startActivation(message.payload.activateAjaxUrl)
|
||||
.then(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.ContentEmbedActivationResponse,
|
||||
payload: payload,
|
||||
});
|
||||
})
|
||||
.catch(payload => {
|
||||
embedder.postMessage({
|
||||
key: PluginMessages.ContentEmbedActivationError,
|
||||
payload,
|
||||
});
|
||||
});
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
export const messageMiddleware = (embedder: any) => (message: Message) => {
|
||||
const next = messageMapper.get(message.key);
|
||||
if (next) {
|
||||
next(message, embedder);
|
||||
}
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { domElements } from '../constants/selectors';
|
||||
import useAppEmbedder from './useAppEmbedder';
|
||||
import { App } from './constants';
|
||||
import { IframeErrorPage } from './IframeErrorPage';
|
||||
|
||||
interface PortalProps extends React.PropsWithChildren {
|
||||
app: App;
|
||||
createRoute: boolean;
|
||||
}
|
||||
|
||||
const IntegratedIframePortal = (props: PortalProps) => {
|
||||
const container = document.getElementById(domElements.leadinIframeContainer);
|
||||
const iframeNotRendered = useAppEmbedder(
|
||||
props.app,
|
||||
props.createRoute,
|
||||
container
|
||||
);
|
||||
|
||||
if (container && !iframeNotRendered) {
|
||||
return ReactDOM.createPortal(props.children, container);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{(!container || iframeNotRendered) && <IframeErrorPage />}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const renderIframeApp = () => {
|
||||
const iframeFallbackContainer = document.getElementById(
|
||||
domElements.leadinIframeContainer
|
||||
);
|
||||
|
||||
let app: App;
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const page = queryParams.get('page');
|
||||
const createRoute = queryParams.get('leadin_route[0]') === 'create';
|
||||
|
||||
switch (page) {
|
||||
case 'leadin_forms':
|
||||
app = App.Forms;
|
||||
break;
|
||||
case 'leadin_chatflows':
|
||||
app = App.LiveChat;
|
||||
break;
|
||||
case 'leadin_settings':
|
||||
app = App.PluginSettings;
|
||||
break;
|
||||
case 'leadin_user_guide':
|
||||
default:
|
||||
app = App.Plugin;
|
||||
break;
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<IntegratedIframePortal app={app} createRoute={createRoute} />,
|
||||
iframeFallbackContainer
|
||||
);
|
||||
};
|
||||
|
||||
export default renderIframeApp;
|
||||
@@ -1,233 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import Raven from '../lib/Raven';
|
||||
|
||||
import {
|
||||
accountName,
|
||||
adminUrl,
|
||||
connectionStatus,
|
||||
deviceId,
|
||||
hubspotBaseUrl,
|
||||
leadinQueryParams,
|
||||
locale,
|
||||
plugins,
|
||||
portalDomain,
|
||||
portalEmail,
|
||||
portalId,
|
||||
reviewSkippedDate,
|
||||
refreshToken,
|
||||
impactLink,
|
||||
theme,
|
||||
lastAuthorizeTime,
|
||||
lastDeauthorizeTime,
|
||||
lastDisconnectTime,
|
||||
leadinPluginVersion,
|
||||
phpVersion,
|
||||
wpVersion,
|
||||
contentEmbed,
|
||||
requiresContentEmbedScope,
|
||||
refreshTokenError,
|
||||
LeadinConfig,
|
||||
} from '../constants/leadinConfig';
|
||||
import { App, AppIframe } from './constants';
|
||||
import { messageMiddleware } from './messageMiddleware';
|
||||
import { resizeWindow, useIframeNotRendered } from '../utils/iframe';
|
||||
|
||||
type PartialLeadinConfig = Pick<
|
||||
LeadinConfig,
|
||||
| 'accountName'
|
||||
| 'adminUrl'
|
||||
| 'connectionStatus'
|
||||
| 'deviceId'
|
||||
| 'plugins'
|
||||
| 'portalDomain'
|
||||
| 'portalEmail'
|
||||
| 'portalId'
|
||||
| 'reviewSkippedDate'
|
||||
| 'refreshToken'
|
||||
| 'impactLink'
|
||||
| 'theme'
|
||||
| 'trackConsent'
|
||||
| 'lastAuthorizeTime'
|
||||
| 'lastDeauthorizeTime'
|
||||
| 'lastDisconnectTime'
|
||||
| 'leadinPluginVersion'
|
||||
| 'phpVersion'
|
||||
| 'wpVersion'
|
||||
| 'contentEmbed'
|
||||
| 'requiresContentEmbedScope'
|
||||
| 'refreshTokenError'
|
||||
>;
|
||||
|
||||
type AppIntegrationConfig = Pick<LeadinConfig, 'adminUrl'>;
|
||||
|
||||
const getIntegrationConfig = (): AppIntegrationConfig => {
|
||||
return {
|
||||
adminUrl: leadinQueryParams.adminUrl,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* A modified version of the original leadinConfig that is passed to some integrated apps.
|
||||
*
|
||||
* Important:
|
||||
* Try not to add new fields here.
|
||||
* This config is already too large and broad in scope.
|
||||
* It tightly couples the apps that use it with the WordPress plugin.
|
||||
* Consider instead passing new required fields as new entry to PluginAppOptions or app-specific options.
|
||||
*/
|
||||
type AppLeadinConfig = {
|
||||
admin: string;
|
||||
company: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
irclickid: string;
|
||||
justConnected: string;
|
||||
lastName: string;
|
||||
mpid: string;
|
||||
nonce: string;
|
||||
websiteName: string;
|
||||
} & PartialLeadinConfig;
|
||||
|
||||
const getLeadinConfig = (): AppLeadinConfig => {
|
||||
const utm_query_params = Object.keys(leadinQueryParams)
|
||||
.filter(x => /^utm/.test(x))
|
||||
.reduce(
|
||||
(p: { [key: string]: string }, c: string) => ({
|
||||
[c]: leadinQueryParams[c],
|
||||
...p,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
return {
|
||||
accountName,
|
||||
admin: leadinQueryParams.admin,
|
||||
adminUrl,
|
||||
company: leadinQueryParams.company,
|
||||
connectionStatus,
|
||||
deviceId,
|
||||
email: leadinQueryParams.email,
|
||||
firstName: leadinQueryParams.firstName,
|
||||
irclickid: leadinQueryParams.irclickid,
|
||||
justConnected: leadinQueryParams.justConnected,
|
||||
lastName: leadinQueryParams.lastName,
|
||||
lastAuthorizeTime,
|
||||
lastDeauthorizeTime,
|
||||
lastDisconnectTime,
|
||||
leadinPluginVersion,
|
||||
mpid: leadinQueryParams.mpid,
|
||||
nonce: leadinQueryParams.nonce,
|
||||
phpVersion,
|
||||
plugins,
|
||||
portalDomain,
|
||||
portalEmail,
|
||||
portalId,
|
||||
reviewSkippedDate,
|
||||
theme,
|
||||
trackConsent: leadinQueryParams.trackConsent,
|
||||
websiteName: leadinQueryParams.websiteName,
|
||||
wpVersion,
|
||||
contentEmbed,
|
||||
requiresContentEmbedScope,
|
||||
refreshTokenError,
|
||||
...utm_query_params,
|
||||
};
|
||||
};
|
||||
|
||||
const getAppOptions = (app: App, createRoute = false) => {
|
||||
const {
|
||||
IntegratedAppOptions,
|
||||
FormsAppOptions,
|
||||
LiveChatAppOptions,
|
||||
PluginAppOptions,
|
||||
}: any = window;
|
||||
let options;
|
||||
switch (app) {
|
||||
case App.Plugin:
|
||||
options = new PluginAppOptions().setLeadinConfig(getLeadinConfig());
|
||||
break;
|
||||
case App.PluginSettings:
|
||||
options = new PluginAppOptions()
|
||||
.setLeadinConfig(getLeadinConfig())
|
||||
.setPluginSettingsInit();
|
||||
break;
|
||||
case App.Forms:
|
||||
options = new FormsAppOptions().setIntegratedAppConfig(
|
||||
getIntegrationConfig()
|
||||
);
|
||||
if (createRoute) {
|
||||
options = options.setCreateFormAppInit();
|
||||
}
|
||||
break;
|
||||
case App.LiveChat:
|
||||
options = new LiveChatAppOptions();
|
||||
if (createRoute) {
|
||||
options = options.setCreateLiveChatAppInit();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
options = new IntegratedAppOptions();
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
export default function useAppEmbedder(
|
||||
app: App,
|
||||
createRoute: boolean,
|
||||
container: HTMLElement | null
|
||||
) {
|
||||
console.info(
|
||||
'HubSpot plugin - starting app embedder for:',
|
||||
AppIframe[app],
|
||||
container
|
||||
);
|
||||
const iframeNotRendered = useIframeNotRendered(AppIframe[app]);
|
||||
|
||||
useEffect(() => {
|
||||
const { IntegratedAppEmbedder }: any = window;
|
||||
|
||||
if (IntegratedAppEmbedder) {
|
||||
const options = getAppOptions(app, createRoute)
|
||||
.setLocale(locale)
|
||||
.setDeviceId(deviceId)
|
||||
.setRefreshToken(refreshToken);
|
||||
|
||||
const embedder = new IntegratedAppEmbedder(
|
||||
AppIframe[app],
|
||||
portalId,
|
||||
hubspotBaseUrl,
|
||||
resizeWindow,
|
||||
refreshToken ? '' : impactLink
|
||||
).setOptions(options);
|
||||
|
||||
embedder.subscribe(messageMiddleware(embedder));
|
||||
embedder.attachTo(container, true);
|
||||
embedder.postStartAppMessage(); // lets the app know all all data has been passed to it
|
||||
|
||||
(window as any).embedder = embedder;
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (iframeNotRendered) {
|
||||
console.error('HubSpot plugin Iframe not rendered', {
|
||||
portalId,
|
||||
container,
|
||||
appName: AppIframe[app],
|
||||
hasIntegratedAppEmbedder: !!(window as any).IntegratedAppEmbedder,
|
||||
});
|
||||
Raven.captureException(new Error('Leadin Iframe not rendered'), {
|
||||
fingerprint: ['USE_APP_EMBEDDER', 'IFRAME_SETUP_ERROR'],
|
||||
extra: {
|
||||
portalId,
|
||||
container,
|
||||
app,
|
||||
hubspotBaseUrl,
|
||||
impactLink,
|
||||
appName: AppIframe[app],
|
||||
hasRefreshToken: !!refreshToken,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return iframeNotRendered;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import {
|
||||
deviceId,
|
||||
hubspotBaseUrl,
|
||||
locale,
|
||||
portalId,
|
||||
} from '../constants/leadinConfig';
|
||||
import { Message } from './messageMiddleware';
|
||||
|
||||
export const BackgroudAppContext = createContext<any>(null);
|
||||
|
||||
export function useBackgroundAppContext() {
|
||||
return useContext(BackgroudAppContext);
|
||||
}
|
||||
|
||||
export function usePostBackgroundMessage() {
|
||||
const app = useBackgroundAppContext();
|
||||
|
||||
return (message: Message) => {
|
||||
app.postMessage(message);
|
||||
};
|
||||
}
|
||||
|
||||
export function usePostAsyncBackgroundMessage(): (
|
||||
message: Message
|
||||
) => Promise<any> {
|
||||
const app = useBackgroundAppContext();
|
||||
return (message: Message) => app.postAsyncMessage(message);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import Raven from 'raven-js';
|
||||
import {
|
||||
hubspotBaseUrl,
|
||||
phpVersion,
|
||||
wpVersion,
|
||||
leadinPluginVersion,
|
||||
portalId,
|
||||
plugins,
|
||||
} from '../constants/leadinConfig';
|
||||
|
||||
export function configureRaven() {
|
||||
if (hubspotBaseUrl.indexOf('app.hubspot.com') === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Raven.config(
|
||||
'https://e9b8f382cdd130c0d415cd977d2be56f@exceptions.hubspot.com/1',
|
||||
{
|
||||
instrument: {
|
||||
tryCatch: false,
|
||||
},
|
||||
release: leadinPluginVersion,
|
||||
}
|
||||
).install();
|
||||
|
||||
Raven.setTagsContext({
|
||||
v: leadinPluginVersion,
|
||||
php: phpVersion,
|
||||
wordpress: wpVersion,
|
||||
});
|
||||
|
||||
Raven.setExtraContext({
|
||||
hub: portalId,
|
||||
plugins: Object.keys(plugins)
|
||||
.map(name => `${name}#${plugins[name]}`)
|
||||
.join(','),
|
||||
});
|
||||
}
|
||||
|
||||
export default Raven;
|
||||
@@ -1,311 +0,0 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
import {
|
||||
CALYPSO,
|
||||
CALYPSO_LIGHT,
|
||||
CALYPSO_MEDIUM,
|
||||
OBSIDIAN,
|
||||
} from '../UIComponents/colors';
|
||||
import UISpinner from '../UIComponents/UISpinner';
|
||||
import LoadState, { LoadStateType } from '../enums/loadState';
|
||||
|
||||
const Container = styled.div`
|
||||
color: ${OBSIDIAN};
|
||||
font-family: 'Lexend Deca', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
interface IControlContainerProps {
|
||||
focused: boolean;
|
||||
}
|
||||
|
||||
const ControlContainer = styled.div<IControlContainerProps>`
|
||||
align-items: center;
|
||||
background-color: hsl(0, 0%, 100%);
|
||||
border-color: hsl(0, 0%, 80%);
|
||||
border-radius: 4px;
|
||||
border-style: solid;
|
||||
border-width: ${props => (props.focused ? '0' : '1px')};
|
||||
cursor: default;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
min-height: 38px;
|
||||
outline: 0 !important;
|
||||
position: relative;
|
||||
transition: all 100ms;
|
||||
box-sizing: border-box;
|
||||
box-shadow: ${props =>
|
||||
props.focused ? `0 0 0 2px ${CALYPSO_MEDIUM}` : 'none'};
|
||||
&:hover {
|
||||
border-color: hsl(0, 0%, 70%);
|
||||
}
|
||||
`;
|
||||
|
||||
const ValueContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
padding: 2px 8px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const Placeholder = styled.div`
|
||||
color: hsl(0, 0%, 50%);
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
`;
|
||||
|
||||
const SingleValue = styled.div`
|
||||
color: hsl(0, 0%, 20%);
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
max-width: calc(100% - 8px);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const IndicatorContainer = styled.div`
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const DropdownIndicator = styled.div`
|
||||
border-top: 8px solid ${CALYPSO};
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
margin: 10px;
|
||||
`;
|
||||
|
||||
const InputContainer = styled.div`
|
||||
margin: 2px;
|
||||
padding-bottom: 2px;
|
||||
padding-top: 2px;
|
||||
visibility: visible;
|
||||
color: hsl(0, 0%, 20%);
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const Input = styled.input`
|
||||
box-sizing: content-box;
|
||||
background: rgba(0, 0, 0, 0) none repeat scroll 0px center;
|
||||
border: 0px none;
|
||||
font-size: inherit;
|
||||
opacity: 1;
|
||||
outline: currentcolor none 0px;
|
||||
padding: 0px;
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
`;
|
||||
|
||||
const InputShadow = styled.div`
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
font-size: inherit;
|
||||
`;
|
||||
|
||||
const MenuContainer = styled.div`
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 8px;
|
||||
z-index: 9999;
|
||||
box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1);
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const MenuList = styled.div`
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 4px;
|
||||
padding-top: 4px;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const MenuGroup = styled.div`
|
||||
padding-bottom: 8px;
|
||||
padding-top: 8px;
|
||||
`;
|
||||
|
||||
const MenuGroupHeader = styled.div`
|
||||
color: #999;
|
||||
cursor: default;
|
||||
font-size: 75%;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.25em;
|
||||
text-transform: uppercase;
|
||||
padding-left: 12px;
|
||||
padding-left: 12px;
|
||||
`;
|
||||
|
||||
interface IMenuItemProps {
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
const MenuItem = styled.div<IMenuItemProps>`
|
||||
display: block;
|
||||
background-color: ${props =>
|
||||
props.selected ? CALYPSO_MEDIUM : 'transparent'};
|
||||
color: ${props => (props.selected ? '#fff' : 'inherit')};
|
||||
cursor: default;
|
||||
font-size: inherit;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
&:hover {
|
||||
background-color: ${props =>
|
||||
props.selected ? CALYPSO_MEDIUM : CALYPSO_LIGHT};
|
||||
}
|
||||
`;
|
||||
|
||||
interface IAsyncSelectProps {
|
||||
placeholder: string;
|
||||
value: any;
|
||||
loadOptions?: Function;
|
||||
defaultOptions?: any[];
|
||||
onChange: Function;
|
||||
}
|
||||
|
||||
export default function AsyncSelect({
|
||||
placeholder,
|
||||
value,
|
||||
loadOptions,
|
||||
onChange,
|
||||
defaultOptions,
|
||||
}: IAsyncSelectProps) {
|
||||
const inputEl = useRef<HTMLInputElement>(null);
|
||||
const inputShadowEl = useRef<HTMLDivElement>(null);
|
||||
const [isFocused, setFocus] = useState(false);
|
||||
const [loadState, setLoadState] = useState<LoadStateType>(
|
||||
LoadState.NotLoaded
|
||||
);
|
||||
const [localValue, setLocalValue] = useState('');
|
||||
const [options, setOptions] = useState(defaultOptions);
|
||||
|
||||
const inputSize = `${
|
||||
inputShadowEl.current ? inputShadowEl.current.clientWidth + 10 : 2
|
||||
}px`;
|
||||
|
||||
useEffect(() => {
|
||||
if (loadOptions && loadState === LoadState.NotLoaded) {
|
||||
loadOptions('', (result: any) => {
|
||||
setOptions(result);
|
||||
setLoadState(LoadState.Idle);
|
||||
});
|
||||
}
|
||||
}, [loadOptions, loadState]);
|
||||
|
||||
const renderItems = (items: any[] = [], parentKey?: number) => {
|
||||
return items.map((item, index) => {
|
||||
if (item.options) {
|
||||
return (
|
||||
<MenuGroup key={`async-select-item-${index}`}>
|
||||
<MenuGroupHeader id={`${index}-heading`}>
|
||||
{item.label}
|
||||
</MenuGroupHeader>
|
||||
<div>{renderItems(item.options, index)}</div>
|
||||
</MenuGroup>
|
||||
);
|
||||
} else {
|
||||
const key = `async-select-item-${
|
||||
parentKey !== undefined ? `${parentKey}-${index}` : index
|
||||
}`;
|
||||
return (
|
||||
<MenuItem
|
||||
key={key}
|
||||
id={key}
|
||||
selected={value && item.value === value.value}
|
||||
onClick={() => {
|
||||
onChange(item);
|
||||
setFocus(false);
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ControlContainer
|
||||
id="leadin-async-selector"
|
||||
focused={isFocused}
|
||||
onClick={() => {
|
||||
if (isFocused) {
|
||||
if (inputEl.current) {
|
||||
inputEl.current.blur();
|
||||
}
|
||||
setFocus(false);
|
||||
setLocalValue('');
|
||||
} else {
|
||||
if (inputEl.current) {
|
||||
inputEl.current.focus();
|
||||
}
|
||||
setFocus(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ValueContainer>
|
||||
{localValue === '' &&
|
||||
(!value ? (
|
||||
<Placeholder>{placeholder}</Placeholder>
|
||||
) : (
|
||||
<SingleValue>{value.label}</SingleValue>
|
||||
))}
|
||||
<InputContainer>
|
||||
<Input
|
||||
ref={inputEl}
|
||||
onFocus={() => {
|
||||
setFocus(true);
|
||||
}}
|
||||
onChange={e => {
|
||||
setLocalValue(e.target.value);
|
||||
setLoadState(LoadState.Loading);
|
||||
loadOptions &&
|
||||
loadOptions(e.target.value, (result: any) => {
|
||||
setOptions(result);
|
||||
setLoadState(LoadState.Idle);
|
||||
});
|
||||
}}
|
||||
value={localValue}
|
||||
width={inputSize}
|
||||
id="asycn-select-input"
|
||||
/>
|
||||
<InputShadow ref={inputShadowEl}>{localValue}</InputShadow>
|
||||
</InputContainer>
|
||||
</ValueContainer>
|
||||
<IndicatorContainer>
|
||||
{loadState === LoadState.Loading && <UISpinner />}
|
||||
<DropdownIndicator />
|
||||
</IndicatorContainer>
|
||||
</ControlContainer>
|
||||
{isFocused && (
|
||||
<MenuContainer>
|
||||
<MenuList>{renderItems(options)}</MenuList>
|
||||
</MenuContainer>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import React from 'react';
|
||||
import UIButton from '../UIComponents/UIButton';
|
||||
import UIContainer from '../UIComponents/UIContainer';
|
||||
import HubspotWrapper from './HubspotWrapper';
|
||||
import { adminUrl, redirectNonce } from '../../constants/leadinConfig';
|
||||
import { pluginPath } from '../../constants/leadinConfig';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
interface IErrorHandlerProps {
|
||||
status: number;
|
||||
resetErrorState?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
errorInfo?: {
|
||||
header: string;
|
||||
message: string;
|
||||
action: string;
|
||||
};
|
||||
}
|
||||
|
||||
function redirectToPlugin() {
|
||||
window.location.href = `${adminUrl}admin.php?page=leadin&leadin_expired=${redirectNonce}`;
|
||||
}
|
||||
|
||||
export default function ErrorHandler({
|
||||
status,
|
||||
resetErrorState,
|
||||
errorInfo = { header: '', message: '', action: '' },
|
||||
}: IErrorHandlerProps) {
|
||||
const isUnauthorized = status === 401 || status === 403;
|
||||
const errorHeader = isUnauthorized
|
||||
? __("Your plugin isn't authorized", 'leadin')
|
||||
: errorInfo.header;
|
||||
const errorMessage = isUnauthorized
|
||||
? __('Reauthorize your plugin to access your free HubSpot tools', 'leadin')
|
||||
: errorInfo.message;
|
||||
|
||||
return (
|
||||
<HubspotWrapper pluginPath={pluginPath}>
|
||||
<UIContainer textAlign="center">
|
||||
<h4>{errorHeader}</h4>
|
||||
<p>
|
||||
<b>{errorMessage}</b>
|
||||
</p>
|
||||
{isUnauthorized ? (
|
||||
<UIButton data-test-id="authorize-button" onClick={redirectToPlugin}>
|
||||
{__('Go to plugin', 'leadin')}
|
||||
</UIButton>
|
||||
) : (
|
||||
<UIButton data-test-id="retry-button" onClick={resetErrorState}>
|
||||
{errorInfo.action}
|
||||
</UIButton>
|
||||
)}
|
||||
</UIContainer>
|
||||
</HubspotWrapper>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
interface IHubspotWrapperProps {
|
||||
pluginPath: string;
|
||||
padding?: string;
|
||||
}
|
||||
|
||||
export default styled.div<IHubspotWrapperProps>`
|
||||
background-image: ${props =>
|
||||
`url(${props.pluginPath}/public/assets/images/hubspot.svg)`};
|
||||
background-color: #f5f8fa;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 25px;
|
||||
background-size: 120px;
|
||||
color: #33475b;
|
||||
font-family: 'Lexend Deca', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
|
||||
padding: ${(props: any) => props.padding || '90px 20% 25px'};
|
||||
|
||||
p {
|
||||
font-size: inherit !important;
|
||||
line-height: 24px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
`;
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
import HubspotWrapper from './HubspotWrapper';
|
||||
import UISpinner from '../UIComponents/UISpinner';
|
||||
import { pluginPath } from '../../constants/leadinConfig';
|
||||
|
||||
export default function LoadingBlock() {
|
||||
return (
|
||||
<HubspotWrapper pluginPath={pluginPath}>
|
||||
<UISpinner size={50} />
|
||||
</HubspotWrapper>
|
||||
);
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import React, { Fragment, useEffect } from 'react';
|
||||
import { portalId, refreshToken } from '../../constants/leadinConfig';
|
||||
import UISpacer from '../UIComponents/UISpacer';
|
||||
import PreviewForm from './PreviewForm';
|
||||
import FormSelect from './FormSelect';
|
||||
import { IFormBlockProps } from '../../gutenberg/FormBlock/registerFormBlock';
|
||||
import {
|
||||
usePostBackgroundMessage,
|
||||
BackgroudAppContext,
|
||||
useBackgroundAppContext,
|
||||
} from '../../iframe/useBackgroundApp';
|
||||
import { ProxyMessages } from '../../iframe/integratedMessages';
|
||||
import LoadingBlock from '../Common/LoadingBlock';
|
||||
import { getOrCreateBackgroundApp } from '../../utils/backgroundAppUtils';
|
||||
|
||||
interface IFormEditProps extends IFormBlockProps {
|
||||
preview: boolean;
|
||||
origin: 'gutenberg' | 'elementor';
|
||||
}
|
||||
|
||||
function FormEdit({
|
||||
attributes,
|
||||
isSelected,
|
||||
setAttributes,
|
||||
preview = true,
|
||||
origin = 'gutenberg',
|
||||
}: IFormEditProps) {
|
||||
const { formId, formName } = attributes;
|
||||
const formSelected = portalId && formId;
|
||||
|
||||
const isBackgroundAppReady = useBackgroundAppContext();
|
||||
const monitorFormPreviewRender = usePostBackgroundMessage();
|
||||
|
||||
const handleChange = (selectedForm: { value: string; label: string }) => {
|
||||
setAttributes({
|
||||
portalId,
|
||||
formId: selectedForm.value,
|
||||
formName: selectedForm.label,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
monitorFormPreviewRender({
|
||||
key: ProxyMessages.TrackFormPreviewRender,
|
||||
payload: {
|
||||
origin,
|
||||
},
|
||||
});
|
||||
}, [origin]);
|
||||
|
||||
return !isBackgroundAppReady ? (
|
||||
<LoadingBlock />
|
||||
) : (
|
||||
<Fragment>
|
||||
{(isSelected || !formSelected) && (
|
||||
<FormSelect
|
||||
formId={formId}
|
||||
formName={formName}
|
||||
handleChange={handleChange}
|
||||
origin={origin}
|
||||
/>
|
||||
)}
|
||||
{formSelected && (
|
||||
<Fragment>
|
||||
{isSelected && <UISpacer />}
|
||||
{preview && <PreviewForm portalId={portalId} formId={formId} />}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FormEditContainer(props: IFormEditProps) {
|
||||
return (
|
||||
<BackgroudAppContext.Provider
|
||||
value={refreshToken && getOrCreateBackgroundApp(refreshToken)}
|
||||
>
|
||||
<FormEdit {...props} />
|
||||
</BackgroudAppContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import React from 'react';
|
||||
import FormSelector from './FormSelector';
|
||||
import LoadingBlock from '../Common/LoadingBlock';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import useForms from './hooks/useForms';
|
||||
import useCreateFormFromTemplate from './hooks/useCreateFormFromTemplate';
|
||||
import { FormType, isDefaultForm } from '../../constants/defaultFormOptions';
|
||||
import ErrorHandler from '../Common/ErrorHandler';
|
||||
|
||||
interface IFormSelectProps {
|
||||
formId: string;
|
||||
formName: string;
|
||||
handleChange: Function;
|
||||
origin: 'gutenberg' | 'elementor';
|
||||
}
|
||||
|
||||
export default function FormSelect({
|
||||
formId,
|
||||
formName,
|
||||
handleChange,
|
||||
origin = 'gutenberg',
|
||||
}: IFormSelectProps) {
|
||||
const { search, formApiError, reset } = useForms();
|
||||
const {
|
||||
createFormByTemplate,
|
||||
reset: createReset,
|
||||
isCreating,
|
||||
hasError,
|
||||
formApiError: createApiError,
|
||||
} = useCreateFormFromTemplate(origin);
|
||||
const value =
|
||||
formId && formName
|
||||
? {
|
||||
label: formName,
|
||||
value: formId,
|
||||
}
|
||||
: null;
|
||||
|
||||
const handleLocalChange = (option: { value: FormType }) => {
|
||||
if (isDefaultForm(option.value)) {
|
||||
createFormByTemplate(option.value).then(({ guid, name }) => {
|
||||
handleChange({
|
||||
value: guid,
|
||||
label: name,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
handleChange(option);
|
||||
}
|
||||
};
|
||||
|
||||
return isCreating ? (
|
||||
<LoadingBlock />
|
||||
) : formApiError || createApiError ? (
|
||||
<ErrorHandler
|
||||
status={formApiError ? formApiError.status : createApiError.status}
|
||||
resetErrorState={() => {
|
||||
if (hasError) {
|
||||
createReset();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}}
|
||||
errorInfo={{
|
||||
header: __('There was a problem retrieving your forms', 'leadin'),
|
||||
message: __(
|
||||
'Please refresh your forms or try again in a few minutes',
|
||||
'leadin'
|
||||
),
|
||||
action: __('Refresh forms', 'leadin'),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormSelector
|
||||
loadOptions={search}
|
||||
onChange={(option: { value: FormType }) => handleLocalChange(option)}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import HubspotWrapper from '../Common/HubspotWrapper';
|
||||
import { pluginPath } from '../../constants/leadinConfig';
|
||||
import AsyncSelect from '../Common/AsyncSelect';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
interface IFormSelectorProps {
|
||||
loadOptions: Function;
|
||||
onChange: Function;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export default function FormSelector({
|
||||
loadOptions,
|
||||
onChange,
|
||||
value,
|
||||
}: IFormSelectorProps) {
|
||||
return (
|
||||
<HubspotWrapper pluginPath={pluginPath}>
|
||||
<p data-test-id="leadin-form-select">
|
||||
<b>
|
||||
{__(
|
||||
'Select an existing form or create a new one from a template',
|
||||
'leadin'
|
||||
)}
|
||||
</b>
|
||||
</p>
|
||||
<AsyncSelect
|
||||
placeholder={__('Search for a form', 'leadin')}
|
||||
value={value}
|
||||
loadOptions={loadOptions}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</HubspotWrapper>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import UIOverlay from '../UIComponents/UIOverlay';
|
||||
import { formsScriptPayload, hublet } from '../../constants/leadinConfig';
|
||||
import useFormScript from './hooks/useFormsScript';
|
||||
|
||||
export default function PreviewForm({
|
||||
portalId,
|
||||
formId,
|
||||
}: {
|
||||
portalId: number;
|
||||
formId: string;
|
||||
}) {
|
||||
const inputEl = useRef<HTMLDivElement>(null);
|
||||
const ready = useFormScript();
|
||||
|
||||
useEffect(() => {
|
||||
if (!ready) {
|
||||
return;
|
||||
}
|
||||
if (inputEl.current) {
|
||||
inputEl.current.innerHTML = '';
|
||||
const embedScript = document.createElement('script');
|
||||
embedScript.innerHTML = `hbspt.forms.create({ portalId: '${portalId}', formId: '${formId}', region: '${hublet}', ${formsScriptPayload} });`;
|
||||
inputEl.current.appendChild(embedScript);
|
||||
}
|
||||
}, [formId, portalId, ready, inputEl]);
|
||||
|
||||
return <UIOverlay ref={inputEl} />;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
usePostAsyncBackgroundMessage,
|
||||
usePostBackgroundMessage,
|
||||
} from '../../../iframe/useBackgroundApp';
|
||||
import { FormType } from '../../../constants/defaultFormOptions';
|
||||
import LoadState, { LoadStateType } from '../../enums/loadState';
|
||||
import { ProxyMessages } from '../../../iframe/integratedMessages';
|
||||
|
||||
export default function useCreateFormFromTemplate(origin = 'gutenberg') {
|
||||
const proxy = usePostAsyncBackgroundMessage();
|
||||
const track = usePostBackgroundMessage();
|
||||
const [loadState, setLoadState] = useState<LoadStateType>(LoadState.Idle);
|
||||
const [formApiError, setFormApiError] = useState<any>(null);
|
||||
|
||||
const createFormByTemplate = (type: FormType) => {
|
||||
setLoadState(LoadState.Loading);
|
||||
track({
|
||||
key: ProxyMessages.TrackFormCreatedFromTemplate,
|
||||
payload: {
|
||||
type,
|
||||
origin,
|
||||
},
|
||||
});
|
||||
|
||||
return proxy({
|
||||
key: ProxyMessages.CreateFormFromTemplate,
|
||||
payload: type,
|
||||
})
|
||||
.then(form => {
|
||||
setLoadState(LoadState.Idle);
|
||||
return form;
|
||||
})
|
||||
.catch(err => {
|
||||
setFormApiError(err);
|
||||
track({
|
||||
key: ProxyMessages.TrackFormCreationFailed,
|
||||
payload: {
|
||||
origin,
|
||||
},
|
||||
});
|
||||
setLoadState(LoadState.Failed);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
isCreating: loadState === LoadState.Loading,
|
||||
hasError: loadState === LoadState.Failed,
|
||||
formApiError,
|
||||
createFormByTemplate,
|
||||
reset: () => setLoadState(LoadState.Idle),
|
||||
};
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { usePostAsyncBackgroundMessage } from '../../../iframe/useBackgroundApp';
|
||||
import { DEFAULT_OPTIONS } from '../../../constants/defaultFormOptions';
|
||||
import { ProxyMessages } from '../../../iframe/integratedMessages';
|
||||
import { IForm } from '../../types';
|
||||
|
||||
export default function useForms() {
|
||||
const proxy = usePostAsyncBackgroundMessage();
|
||||
const [formApiError, setFormApiError] = useState<any>(null);
|
||||
|
||||
const search = debounce(
|
||||
(search: string, callback: Function) => {
|
||||
return proxy({
|
||||
key: ProxyMessages.FetchForms,
|
||||
payload: {
|
||||
search,
|
||||
},
|
||||
})
|
||||
.then(forms => {
|
||||
callback([
|
||||
...forms.map((form: IForm) => ({
|
||||
label: form.name,
|
||||
value: form.guid,
|
||||
})),
|
||||
DEFAULT_OPTIONS,
|
||||
]);
|
||||
})
|
||||
.catch(error => {
|
||||
setFormApiError(error);
|
||||
});
|
||||
},
|
||||
300,
|
||||
{ trailing: true }
|
||||
);
|
||||
|
||||
return {
|
||||
search,
|
||||
formApiError,
|
||||
reset: () => setFormApiError(null),
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { formsScript } from '../../../constants/leadinConfig';
|
||||
import Raven from '../../../lib/Raven';
|
||||
|
||||
let promise: Promise<string | undefined>;
|
||||
|
||||
function loadFormsScript() {
|
||||
if (!promise) {
|
||||
promise = new Promise((resolve, reject) =>
|
||||
$.getScript(formsScript)
|
||||
.done(resolve)
|
||||
.fail(reject)
|
||||
);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
export default function useFormScript() {
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadFormsScript()
|
||||
.then(() => setReady(true))
|
||||
.catch(error => Raven.captureException(error));
|
||||
}, []);
|
||||
|
||||
return ready;
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import React, { Fragment, useEffect } from 'react';
|
||||
import LoadingBlock from '../Common/LoadingBlock';
|
||||
import MeetingSelector from './MeetingSelector';
|
||||
import MeetingWarning from './MeetingWarning';
|
||||
import useMeetings, {
|
||||
useSelectedMeeting,
|
||||
useSelectedMeetingCalendar,
|
||||
} from './hooks/useMeetings';
|
||||
import HubspotWrapper from '../Common/HubspotWrapper';
|
||||
import ErrorHandler from '../Common/ErrorHandler';
|
||||
import { pluginPath } from '../../constants/leadinConfig';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import Raven from 'raven-js';
|
||||
|
||||
interface IMeetingControllerProps {
|
||||
url: string;
|
||||
handleChange: Function;
|
||||
}
|
||||
|
||||
export default function MeetingController({
|
||||
handleChange,
|
||||
url,
|
||||
}: IMeetingControllerProps) {
|
||||
const {
|
||||
mappedMeetings: meetings,
|
||||
loading,
|
||||
error,
|
||||
reload,
|
||||
connectCalendar,
|
||||
} = useMeetings();
|
||||
const selectedMeetingOption = useSelectedMeeting(url);
|
||||
const selectedMeetingCalendar = useSelectedMeetingCalendar(url);
|
||||
|
||||
useEffect(() => {
|
||||
if (!url && meetings.length > 0) {
|
||||
handleChange(meetings[0].value);
|
||||
}
|
||||
}, [meetings, url, handleChange]);
|
||||
|
||||
const handleLocalChange = (option: { value: string }) => {
|
||||
handleChange(option.value);
|
||||
};
|
||||
|
||||
const handleConnectCalendar = () => {
|
||||
return connectCalendar()
|
||||
.then(() => {
|
||||
reload();
|
||||
})
|
||||
.catch(error => {
|
||||
Raven.captureMessage('Unable to connect calendar', {
|
||||
extra: { error },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{loading ? (
|
||||
<LoadingBlock />
|
||||
) : error ? (
|
||||
<ErrorHandler
|
||||
status={(error && error.status) || error}
|
||||
resetErrorState={() => reload()}
|
||||
errorInfo={{
|
||||
header: __(
|
||||
'There was a problem retrieving your meetings',
|
||||
'leadin'
|
||||
),
|
||||
message: __(
|
||||
'Please refresh your meetings or try again in a few minutes',
|
||||
'leadin'
|
||||
),
|
||||
action: __('Refresh meetings', 'leadin'),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<HubspotWrapper padding="90px 32px 24px" pluginPath={pluginPath}>
|
||||
{selectedMeetingCalendar && (
|
||||
<MeetingWarning
|
||||
status={selectedMeetingCalendar}
|
||||
onConnectCalendar={handleConnectCalendar}
|
||||
/>
|
||||
)}
|
||||
{meetings.length > 1 && (
|
||||
<MeetingSelector
|
||||
onChange={handleLocalChange}
|
||||
options={meetings}
|
||||
value={selectedMeetingOption}
|
||||
/>
|
||||
)}
|
||||
</HubspotWrapper>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import React, { Fragment, useEffect } from 'react';
|
||||
import { IMeetingBlockProps } from '../../gutenberg/MeetingsBlock/registerMeetingBlock';
|
||||
import MeetingController from './MeetingController';
|
||||
import PreviewMeeting from './PreviewMeeting';
|
||||
import {
|
||||
BackgroudAppContext,
|
||||
useBackgroundAppContext,
|
||||
usePostBackgroundMessage,
|
||||
} from '../../iframe/useBackgroundApp';
|
||||
import { refreshToken } from '../../constants/leadinConfig';
|
||||
import { ProxyMessages } from '../../iframe/integratedMessages';
|
||||
import LoadingBlock from '../Common/LoadingBlock';
|
||||
import { getOrCreateBackgroundApp } from '../../utils/backgroundAppUtils';
|
||||
|
||||
interface IMeetingEditProps extends IMeetingBlockProps {
|
||||
preview?: boolean;
|
||||
origin?: 'gutenberg' | 'elementor';
|
||||
}
|
||||
|
||||
function MeetingEdit({
|
||||
attributes: { url },
|
||||
isSelected,
|
||||
setAttributes,
|
||||
preview = true,
|
||||
origin = 'gutenberg',
|
||||
}: IMeetingEditProps) {
|
||||
const isBackgroundAppReady = useBackgroundAppContext();
|
||||
const monitorFormPreviewRender = usePostBackgroundMessage();
|
||||
|
||||
const handleChange = (newUrl: string) => {
|
||||
setAttributes({
|
||||
url: newUrl,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
monitorFormPreviewRender({
|
||||
key: ProxyMessages.TrackMeetingPreviewRender,
|
||||
payload: {
|
||||
origin,
|
||||
},
|
||||
});
|
||||
}, [origin]);
|
||||
|
||||
return !isBackgroundAppReady ? (
|
||||
<LoadingBlock />
|
||||
) : (
|
||||
<Fragment>
|
||||
{(isSelected || !url) && (
|
||||
<MeetingController url={url} handleChange={handleChange} />
|
||||
)}
|
||||
{preview && url && <PreviewMeeting url={url} />}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default function MeetingsEditContainer(props: IMeetingEditProps) {
|
||||
return (
|
||||
<BackgroudAppContext.Provider
|
||||
value={refreshToken && getOrCreateBackgroundApp(refreshToken)}
|
||||
>
|
||||
<MeetingEdit {...props} />
|
||||
</BackgroudAppContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import AsyncSelect from '../Common/AsyncSelect';
|
||||
import UISpacer from '../UIComponents/UISpacer';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
interface IMeetingSelectorProps {
|
||||
options: any[];
|
||||
onChange: Function;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export default function MeetingSelector({
|
||||
options,
|
||||
onChange,
|
||||
value,
|
||||
}: IMeetingSelectorProps) {
|
||||
const optionsWrapper = [
|
||||
{
|
||||
label: __('Meeting name', 'leadin'),
|
||||
options,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<UISpacer />
|
||||
<p data-test-id="leadin-meeting-select">
|
||||
<b>{__('Select a meeting scheduling page', 'leadin')}</b>
|
||||
</p>
|
||||
<AsyncSelect
|
||||
defaultOptions={optionsWrapper}
|
||||
onChange={onChange}
|
||||
placeholder={__('Select a meeting', 'leadin')}
|
||||
value={value}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import UIAlert from '../UIComponents/UIAlert';
|
||||
import UIButton from '../UIComponents/UIButton';
|
||||
import { CURRENT_USER_CALENDAR_MISSING } from './constants';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
interface IMeetingWarningProps {
|
||||
status: string;
|
||||
onConnectCalendar: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export default function MeetingWarning({
|
||||
status,
|
||||
onConnectCalendar,
|
||||
}: IMeetingWarningProps) {
|
||||
const isMeetingOwner = status === CURRENT_USER_CALENDAR_MISSING;
|
||||
const titleText = isMeetingOwner
|
||||
? __('Your calendar is not connected', 'leadin')
|
||||
: __('Calendar is not connected', 'leadin');
|
||||
const titleMessage = isMeetingOwner
|
||||
? __(
|
||||
'Please connect your calendar to activate your scheduling pages',
|
||||
'leadin'
|
||||
)
|
||||
: __(
|
||||
'Make sure that everybody in this meeting has connected their calendar from the Meetings page in HubSpot',
|
||||
'leadin'
|
||||
);
|
||||
return (
|
||||
<UIAlert titleText={titleText} titleMessage={titleMessage}>
|
||||
{isMeetingOwner && (
|
||||
<UIButton
|
||||
use="tertiary"
|
||||
id="meetings-connect-calendar"
|
||||
onClick={onConnectCalendar}
|
||||
>
|
||||
{__('Connect calendar', 'leadin')}
|
||||
</UIButton>
|
||||
)}
|
||||
</UIAlert>
|
||||
);
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import useCurrentUserFetch from './hooks/useCurrentUserFetch';
|
||||
import useMeetingsFetch from './hooks/useMeetingsFetch';
|
||||
import LoadState from '../enums/loadState';
|
||||
|
||||
interface IMeetingsContextWrapperState {
|
||||
loading: boolean;
|
||||
error: any;
|
||||
meetings: any[];
|
||||
currentUser: any;
|
||||
meetingUsers: any;
|
||||
selectedMeeting: string;
|
||||
}
|
||||
|
||||
interface IMeetingsContext extends IMeetingsContextWrapperState {
|
||||
reload: Function;
|
||||
}
|
||||
|
||||
interface IMeetingsContextWrapperProps {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const MeetingsContext = React.createContext<IMeetingsContext>({
|
||||
loading: true,
|
||||
error: null,
|
||||
meetings: [],
|
||||
currentUser: null,
|
||||
meetingUsers: {},
|
||||
selectedMeeting: '',
|
||||
reload: () => {},
|
||||
});
|
||||
|
||||
export default function MeetingsContextWrapper({
|
||||
url,
|
||||
children,
|
||||
}: React.PropsWithChildren<IMeetingsContextWrapperProps>) {
|
||||
const [state, setState] = useState<IMeetingsContextWrapperState>({
|
||||
loading: true,
|
||||
error: null,
|
||||
meetings: [],
|
||||
currentUser: null,
|
||||
meetingUsers: {},
|
||||
selectedMeeting: url,
|
||||
});
|
||||
|
||||
const {
|
||||
meetings,
|
||||
meetingUsers,
|
||||
loadMeetingsState,
|
||||
error: errorMeeting,
|
||||
reload: reloadMeetings,
|
||||
} = useMeetingsFetch();
|
||||
|
||||
const {
|
||||
user: currentUser,
|
||||
loadUserState,
|
||||
error: errorUser,
|
||||
reload: reloadUser,
|
||||
} = useCurrentUserFetch();
|
||||
|
||||
const reload = useCallback(() => {
|
||||
reloadUser();
|
||||
reloadMeetings();
|
||||
}, [reloadUser, reloadMeetings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!state.loading &&
|
||||
!state.error &&
|
||||
state.currentUser &&
|
||||
state.meetings.length === 0
|
||||
) {
|
||||
reloadMeetings();
|
||||
}
|
||||
}, [state, reloadMeetings]);
|
||||
|
||||
useEffect(() => {
|
||||
setState(previous => ({
|
||||
...previous,
|
||||
loading:
|
||||
loadUserState === LoadState.Loading ||
|
||||
loadMeetingsState === LoadState.Loading,
|
||||
currentUser,
|
||||
meetings,
|
||||
meetingUsers: meetingUsers.reduce((p, c) => ({ ...p, [c.id]: c }), {}),
|
||||
error: errorMeeting || errorUser,
|
||||
selectedMeeting: url,
|
||||
}));
|
||||
}, [
|
||||
loadUserState,
|
||||
loadMeetingsState,
|
||||
currentUser,
|
||||
meetings,
|
||||
meetingUsers,
|
||||
errorMeeting,
|
||||
errorUser,
|
||||
url,
|
||||
setState,
|
||||
]);
|
||||
|
||||
return (
|
||||
<MeetingsContext.Provider value={{ ...state, reload }}>
|
||||
{children}
|
||||
</MeetingsContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import React, { Fragment, useEffect, useRef } from 'react';
|
||||
import UIOverlay from '../UIComponents/UIOverlay';
|
||||
import useMeetingsScript from './hooks/useMeetingsScript';
|
||||
|
||||
interface IPreviewForm {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export default function PreviewForm({ url }: IPreviewForm) {
|
||||
const ready = useMeetingsScript();
|
||||
const inputEl = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ready) {
|
||||
return;
|
||||
}
|
||||
if (inputEl.current) {
|
||||
inputEl.current.innerHTML = '';
|
||||
const container = document.createElement('div');
|
||||
container.dataset.src = `${url}?embed=true`;
|
||||
container.classList.add('meetings-iframe-container');
|
||||
inputEl.current.appendChild(container);
|
||||
const embedScript = document.createElement('script');
|
||||
embedScript.innerHTML =
|
||||
'hbspt.meetings.create(".meetings-iframe-container");';
|
||||
inputEl.current.appendChild(embedScript);
|
||||
}
|
||||
}, [url, ready, inputEl]);
|
||||
|
||||
return <Fragment>{url && <UIOverlay ref={inputEl}></UIOverlay>}</Fragment>;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export const OTHER_USER_CALENDAR_MISSING = 'OTHER_USER_CALENDAR_MISSING';
|
||||
export const CURRENT_USER_CALENDAR_MISSING = 'CURRENT_USER_CALENDAR_MISSING';
|
||||
@@ -1,45 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { usePostAsyncBackgroundMessage } from '../../../iframe/useBackgroundApp';
|
||||
import LoadState, { LoadStateType } from '../../enums/loadState';
|
||||
import { ProxyMessages } from '../../../iframe/integratedMessages';
|
||||
|
||||
let user: any = null;
|
||||
|
||||
export default function useCurrentUserFetch() {
|
||||
const proxy = usePostAsyncBackgroundMessage();
|
||||
const [loadState, setLoadState] = useState<LoadStateType>(
|
||||
LoadState.NotLoaded
|
||||
);
|
||||
const [error, setError] = useState<null | Error>(null);
|
||||
|
||||
const createUser = () => {
|
||||
if (!user) {
|
||||
setLoadState(LoadState.NotLoaded);
|
||||
}
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
user = null;
|
||||
setLoadState(LoadState.NotLoaded);
|
||||
setError(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (loadState === LoadState.NotLoaded && !user) {
|
||||
setLoadState(LoadState.Loading);
|
||||
proxy({
|
||||
key: ProxyMessages.FetchOrCreateMeetingUser,
|
||||
})
|
||||
.then(data => {
|
||||
user = data;
|
||||
setLoadState(LoadState.Idle);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
setLoadState(LoadState.Failed);
|
||||
});
|
||||
}
|
||||
}, [loadState]);
|
||||
|
||||
return { user, loadUserState: loadState, error, createUser, reload };
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
CURRENT_USER_CALENDAR_MISSING,
|
||||
OTHER_USER_CALENDAR_MISSING,
|
||||
} from '../constants';
|
||||
import useMeetingsFetch, { MeetingUser } from './useMeetingsFetch';
|
||||
import useCurrentUserFetch from './useCurrentUserFetch';
|
||||
import LoadState from '../../enums/loadState';
|
||||
import { usePostAsyncBackgroundMessage } from '../../../iframe/useBackgroundApp';
|
||||
import { ProxyMessages } from '../../../iframe/integratedMessages';
|
||||
|
||||
function getDefaultMeetingName(
|
||||
meeting: any,
|
||||
currentUser: any,
|
||||
meetingUsers: any
|
||||
) {
|
||||
const [meetingOwnerId] = meeting.meetingsUserIds;
|
||||
let result = __('Default', 'leadin');
|
||||
if (
|
||||
currentUser &&
|
||||
meetingOwnerId !== currentUser.id &&
|
||||
meetingUsers[meetingOwnerId]
|
||||
) {
|
||||
const user = meetingUsers[meetingOwnerId];
|
||||
result += ` (${user.userProfile.fullName})`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function hasCalendarObject(user: any) {
|
||||
return (
|
||||
user &&
|
||||
user.meetingsUserBlob &&
|
||||
user.meetingsUserBlob.calendarSettings &&
|
||||
user.meetingsUserBlob.calendarSettings.email
|
||||
);
|
||||
}
|
||||
|
||||
export default function useMeetings() {
|
||||
const proxy = usePostAsyncBackgroundMessage();
|
||||
const {
|
||||
meetings,
|
||||
meetingUsers,
|
||||
error: meetingsError,
|
||||
loadMeetingsState,
|
||||
reload: reloadMeetings,
|
||||
} = useMeetingsFetch();
|
||||
const {
|
||||
user: currentUser,
|
||||
error: userError,
|
||||
loadUserState,
|
||||
reload: reloadUser,
|
||||
} = useCurrentUserFetch();
|
||||
|
||||
const reload = useCallback(() => {
|
||||
reloadUser();
|
||||
reloadMeetings();
|
||||
}, [reloadUser, reloadMeetings]);
|
||||
|
||||
const connectCalendar = () => {
|
||||
return proxy({
|
||||
key: ProxyMessages.ConnectMeetingsCalendar,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
mappedMeetings: meetings.map(meet => ({
|
||||
label:
|
||||
meet.name || getDefaultMeetingName(meet, currentUser, meetingUsers),
|
||||
value: meet.link,
|
||||
})),
|
||||
meetings,
|
||||
meetingUsers,
|
||||
currentUser,
|
||||
error: meetingsError || (userError as any),
|
||||
loading:
|
||||
loadMeetingsState == LoadState.Loading ||
|
||||
loadUserState === LoadState.Loading,
|
||||
reload,
|
||||
connectCalendar,
|
||||
};
|
||||
}
|
||||
|
||||
export function useSelectedMeeting(url: string) {
|
||||
const { mappedMeetings: meetings } = useMeetings();
|
||||
const option = meetings.find(({ value }) => value === url);
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
export function useSelectedMeetingCalendar(url: string) {
|
||||
const { meetings, meetingUsers, currentUser } = useMeetings();
|
||||
|
||||
const meeting = meetings.find(meet => meet.link === url);
|
||||
|
||||
const mappedMeetingUsersId: {
|
||||
[key: number]: MeetingUser;
|
||||
} = meetingUsers.reduce((p, c) => ({ ...p, [c.id]: c }), {});
|
||||
|
||||
if (!meeting) {
|
||||
return null;
|
||||
} else {
|
||||
const { meetingsUserIds } = meeting;
|
||||
if (
|
||||
currentUser &&
|
||||
meetingsUserIds.includes(currentUser.id) &&
|
||||
!hasCalendarObject(currentUser)
|
||||
) {
|
||||
return CURRENT_USER_CALENDAR_MISSING;
|
||||
} else if (
|
||||
meetingsUserIds
|
||||
.map(id => mappedMeetingUsersId[id])
|
||||
.some((user: any) => !hasCalendarObject(user))
|
||||
) {
|
||||
return OTHER_USER_CALENDAR_MISSING;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { usePostAsyncBackgroundMessage } from '../../../iframe/useBackgroundApp';
|
||||
import LoadState, { LoadStateType } from '../../enums/loadState';
|
||||
import { ProxyMessages } from '../../../iframe/integratedMessages';
|
||||
|
||||
export interface Meeting {
|
||||
meetingsUserIds: number[];
|
||||
name: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export interface MeetingUser {
|
||||
id: string;
|
||||
}
|
||||
|
||||
let meetings: Meeting[] = [];
|
||||
let meetingUsers: MeetingUser[] = [];
|
||||
|
||||
export default function useMeetingsFetch() {
|
||||
const proxy = usePostAsyncBackgroundMessage();
|
||||
const [loadState, setLoadState] = useState<LoadStateType>(
|
||||
LoadState.NotLoaded
|
||||
);
|
||||
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const reload = () => {
|
||||
meetings = [];
|
||||
setError(null);
|
||||
setLoadState(LoadState.NotLoaded);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (loadState === LoadState.NotLoaded && meetings.length === 0) {
|
||||
setLoadState(LoadState.Loading);
|
||||
proxy({
|
||||
key: ProxyMessages.FetchMeetingsAndUsers,
|
||||
})
|
||||
.then(data => {
|
||||
setLoadState(LoadState.Loaded);
|
||||
meetings = data && data.meetingLinks;
|
||||
meetingUsers = data && data.meetingUsers;
|
||||
})
|
||||
.catch(e => {
|
||||
setError(e);
|
||||
setLoadState(LoadState.Failed);
|
||||
});
|
||||
}
|
||||
}, [loadState]);
|
||||
|
||||
return {
|
||||
meetings,
|
||||
meetingUsers,
|
||||
loadMeetingsState: loadState,
|
||||
error,
|
||||
reload,
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { meetingsScript } from '../../../constants/leadinConfig';
|
||||
import Raven from '../../../lib/Raven';
|
||||
|
||||
let promise: Promise<any>;
|
||||
|
||||
function loadMeetingsScript() {
|
||||
if (!promise) {
|
||||
promise = new Promise((resolve, reject) =>
|
||||
$.getScript(meetingsScript)
|
||||
.done(resolve)
|
||||
.fail(reject)
|
||||
);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
export default function useMeetingsScript() {
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadMeetingsScript()
|
||||
.then(() => setReady(true))
|
||||
.catch(error => Raven.captureException(error));
|
||||
}, []);
|
||||
|
||||
return ready;
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import React from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { MARIGOLD_LIGHT, MARIGOLD_MEDIUM, OBSIDIAN } from './colors';
|
||||
|
||||
const AlertContainer = styled.div`
|
||||
background-color: ${MARIGOLD_LIGHT};
|
||||
border-color: ${MARIGOLD_MEDIUM};
|
||||
color: ${OBSIDIAN};
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
border-style: solid;
|
||||
border-top-style: solid;
|
||||
border-right-style: solid;
|
||||
border-bottom-style: solid;
|
||||
border-left-style: solid;
|
||||
border-width: 1px;
|
||||
min-height: 60px;
|
||||
padding: 8px 20px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
const Title = styled.p`
|
||||
font-family: 'Lexend Deca';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
color: ${OBSIDIAN};
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
const Message = styled.p`
|
||||
font-family: 'Lexend Deca';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
const MessageContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
interface IUIAlertProps {
|
||||
titleText: string;
|
||||
titleMessage: string;
|
||||
}
|
||||
|
||||
export default function UIAlert({
|
||||
titleText,
|
||||
titleMessage,
|
||||
children,
|
||||
}: React.PropsWithChildren<IUIAlertProps>) {
|
||||
return (
|
||||
<AlertContainer>
|
||||
<MessageContainer>
|
||||
<Title>{titleText}</Title>
|
||||
<Message>{titleMessage}</Message>
|
||||
</MessageContainer>
|
||||
{children}
|
||||
</AlertContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { HEFFALUMP, LORAX, OLAF } from './colors';
|
||||
|
||||
interface IButtonProps {
|
||||
use?: string;
|
||||
}
|
||||
|
||||
export default styled.button<IButtonProps>`
|
||||
background-color:${props => (props.use === 'tertiary' ? HEFFALUMP : LORAX)};
|
||||
border: 3px solid ${props => (props.use === 'tertiary' ? HEFFALUMP : LORAX)};
|
||||
color: ${OLAF}
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
padding: 12px 24px;
|
||||
font-family: 'Lexend Deca', Helvetica, Arial, sans-serif;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
@@ -1,9 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
interface IUIContainerProps {
|
||||
textAlign?: string;
|
||||
}
|
||||
|
||||
export default styled.div<IUIContainerProps>`
|
||||
text-align: ${props => (props.textAlign ? props.textAlign : 'inherit')};
|
||||
`;
|
||||
@@ -1,14 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
export default styled.div`
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
`;
|
||||
@@ -1,5 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
export default styled.div`
|
||||
height: 30px;
|
||||
`;
|
||||
@@ -1,78 +0,0 @@
|
||||
import React from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { CALYPSO_MEDIUM, CALYPSO } from './colors';
|
||||
|
||||
const SpinnerOuter = styled.div`
|
||||
align-items: center;
|
||||
color: #00a4bd;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: '2px';
|
||||
`;
|
||||
|
||||
const SpinnerInner = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
interface IColorProp {
|
||||
color: string;
|
||||
}
|
||||
|
||||
const Circle = styled.circle<IColorProp>`
|
||||
fill: none;
|
||||
stroke: ${props => props.color};
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
transform-origin: center;
|
||||
`;
|
||||
|
||||
const AnimatedCircle = styled.circle<IColorProp>`
|
||||
fill: none;
|
||||
stroke: ${props => props.color};
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
transform-origin: center;
|
||||
animation: dashAnimation 2s ease-in-out infinite,
|
||||
spinAnimation 2s linear infinite;
|
||||
|
||||
@keyframes dashAnimation {
|
||||
0% {
|
||||
stroke-dasharray: 1, 150;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
stroke-dasharray: 90, 150;
|
||||
stroke-dashoffset: -50;
|
||||
}
|
||||
|
||||
100% {
|
||||
stroke-dasharray: 90, 150;
|
||||
stroke-dashoffset: -140;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinAnimation {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
`;
|
||||
|
||||
export default function UISpinner({ size = 20 }) {
|
||||
return (
|
||||
<SpinnerOuter>
|
||||
<SpinnerInner>
|
||||
<svg height={size} width={size} viewBox="0 0 50 50">
|
||||
<Circle color={CALYPSO_MEDIUM} cx="25" cy="25" r="22.5" />
|
||||
<AnimatedCircle color={CALYPSO} cx="25" cy="25" r="22.5" />
|
||||
</svg>
|
||||
</SpinnerInner>
|
||||
</SpinnerOuter>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
export const CALYPSO = '#00a4bd';
|
||||
export const CALYPSO_MEDIUM = '#7fd1de';
|
||||
export const CALYPSO_LIGHT = '#e5f5f8';
|
||||
export const LORAX = '#ff7a59';
|
||||
export const OLAF = '#ffffff';
|
||||
export const HEFFALUMP = '#425b76';
|
||||
export const MARIGOLD_LIGHT = '#fef8f0';
|
||||
export const MARIGOLD_MEDIUM = '#fae0b5';
|
||||
export const OBSIDIAN = '#33475b';
|
||||
@@ -1,6 +0,0 @@
|
||||
const ConnectionStatus = {
|
||||
Connected: 'Connected',
|
||||
NotConnected: 'NotConnected',
|
||||
} as const;
|
||||
|
||||
export default ConnectionStatus;
|
||||
@@ -1,11 +0,0 @@
|
||||
const LoadState = {
|
||||
NotLoaded: 'NotLoaded',
|
||||
Loading: 'Loading',
|
||||
Loaded: 'Loaded',
|
||||
Idle: 'Idle',
|
||||
Failed: 'Failed',
|
||||
} as const;
|
||||
|
||||
export type LoadStateType = typeof LoadState[keyof typeof LoadState];
|
||||
|
||||
export default LoadState;
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface IForm {
|
||||
guid: string;
|
||||
name: string;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import Raven, { configureRaven } from '../lib/Raven';
|
||||
|
||||
export function initApp(initFn: Function) {
|
||||
configureRaven();
|
||||
Raven.context(initFn);
|
||||
}
|
||||
|
||||
export function initAppOnReady(initFn: (...args: any[]) => void) {
|
||||
function main() {
|
||||
$(initFn);
|
||||
}
|
||||
initApp(main);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import {
|
||||
deviceId,
|
||||
hubspotBaseUrl,
|
||||
locale,
|
||||
portalId,
|
||||
} from '../constants/leadinConfig';
|
||||
import { initApp } from './appUtils';
|
||||
|
||||
type CallbackFn = (...args: any[]) => void;
|
||||
|
||||
export function initBackgroundApp(initFn: CallbackFn | CallbackFn[]) {
|
||||
function main() {
|
||||
if (Array.isArray(initFn)) {
|
||||
initFn.forEach(callback => callback());
|
||||
} else {
|
||||
initFn();
|
||||
}
|
||||
}
|
||||
initApp(main);
|
||||
}
|
||||
|
||||
export const getOrCreateBackgroundApp = (refreshToken: string) => {
|
||||
if ((window as any).LeadinBackgroundApp) {
|
||||
return (window as any).LeadinBackgroundApp;
|
||||
}
|
||||
const { IntegratedAppEmbedder, IntegratedAppOptions }: any = window;
|
||||
const options = new IntegratedAppOptions()
|
||||
.setLocale(locale)
|
||||
.setDeviceId(deviceId)
|
||||
.setRefreshToken(refreshToken);
|
||||
|
||||
const embedder = new IntegratedAppEmbedder(
|
||||
'integrated-plugin-proxy',
|
||||
portalId,
|
||||
hubspotBaseUrl,
|
||||
() => {}
|
||||
).setOptions(options);
|
||||
|
||||
embedder.attachTo(document.body, false);
|
||||
embedder.postStartAppMessage(); // lets the app know all all data has been passed to it
|
||||
|
||||
(window as any).LeadinBackgroundApp = embedder;
|
||||
return (window as any).LeadinBackgroundApp;
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
type ContentEmbedInfoResponse = {
|
||||
success: boolean;
|
||||
data?: {
|
||||
// Empty if user doesn't have permissions or plugin already activated
|
||||
activateAjaxUrl?: string;
|
||||
message: string;
|
||||
};
|
||||
};
|
||||
|
||||
export function startInstall(nonce: string) {
|
||||
const formData = new FormData();
|
||||
const ajaxUrl = (window as any).ajaxurl;
|
||||
formData.append('_wpnonce', nonce);
|
||||
formData.append('action', 'content_embed_install');
|
||||
return fetch(ajaxUrl, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
keepalive: true,
|
||||
}).then<ContentEmbedInfoResponse>(res => res.json());
|
||||
}
|
||||
|
||||
export function startActivation(requestUrl: string) {
|
||||
return fetch(requestUrl, {
|
||||
method: 'POST',
|
||||
keepalive: true,
|
||||
}).then<ContentEmbedInfoResponse>(res => res.json());
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const IFRAME_DISPLAY_TIMEOUT = 5000;
|
||||
|
||||
export function useIframeNotRendered(app: string) {
|
||||
const [iframeNotRendered, setIframeNotRendered] = useState(false);
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
const iframe = document.getElementById(app);
|
||||
if (!iframe) {
|
||||
setIframeNotRendered(true);
|
||||
}
|
||||
}, IFRAME_DISPLAY_TIMEOUT);
|
||||
|
||||
return () => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return iframeNotRendered;
|
||||
}
|
||||
|
||||
export const resizeWindow = () => {
|
||||
const adminMenuWrap = document.getElementById('adminmenuwrap');
|
||||
const sideMenuHeight = adminMenuWrap ? adminMenuWrap.offsetHeight : 0;
|
||||
const adminBar = document.getElementById('wpadminbar');
|
||||
const adminBarHeight = (adminBar && adminBar.offsetHeight) || 0;
|
||||
const offset = 4;
|
||||
if (window.innerHeight < sideMenuHeight) {
|
||||
return sideMenuHeight - offset;
|
||||
} else {
|
||||
return window.innerHeight - adminBarHeight - offset;
|
||||
}
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
export function addQueryObjectToUrl(
|
||||
urlObject: URL,
|
||||
queryParams: { [key: string]: any }
|
||||
) {
|
||||
Object.keys(queryParams).forEach(key => {
|
||||
urlObject.searchParams.append(key, queryParams[key]);
|
||||
});
|
||||
}
|
||||
|
||||
export function removeQueryParamFromLocation(key: string) {
|
||||
const location = new URL(window.location.href);
|
||||
location.searchParams.delete(key);
|
||||
window.history.replaceState(null, '', location.href);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { withSelect, withDispatch, select } from '@wordpress/data';
|
||||
|
||||
// from answer here: https://github.com/WordPress/gutenberg/issues/44477#issuecomment-1263026599
|
||||
export const isFullSiteEditor = () => {
|
||||
return select && !!select('core/edit-site');
|
||||
};
|
||||
|
||||
const applyWithSelect = withSelect((select: Function, props: any): any => {
|
||||
return {
|
||||
metaValue: select('core/editor').getEditedPostAttribute('meta')[
|
||||
props.metaKey
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const applyWithDispatch = withDispatch(
|
||||
(dispatch: Function, props: any): any => {
|
||||
return {
|
||||
setMetaValue(value: string) {
|
||||
dispatch('core/editor').editPost({ meta: { [props.metaKey]: value } });
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
function apply<T>(el: T): T {
|
||||
return applyWithSelect(applyWithDispatch(el));
|
||||
}
|
||||
|
||||
export default apply;
|
||||
Reference in New Issue
Block a user