plugin updates
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user