plugin updates

This commit is contained in:
Tony Volpe
2024-06-17 15:33:26 -04:00
parent 3751a5a1a6
commit e4e274a9a7
2674 changed files with 0 additions and 507851 deletions

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>;
}

View File

@@ -1,2 +0,0 @@
export const OTHER_USER_CALENDAR_MISSING = 'OTHER_USER_CALENDAR_MISSING';
export const CURRENT_USER_CALENDAR_MISSING = 'CURRENT_USER_CALENDAR_MISSING';

View File

@@ -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 };
}

View File

@@ -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;
}
}
}

View File

@@ -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,
};
}

View File

@@ -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;
}