Merge pull request #3597 from virtuacoplenny/lenny/handle-calendar-signed-out
fix(calendar): show error message if authorization fails on event fetch
This commit is contained in:
commit
f89f3f144f
|
@ -29,13 +29,19 @@
|
||||||
background: #0074E0;
|
background: #0074E0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
display: flex;
|
display: inline-block;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.calendar-action-buttons {
|
||||||
|
.button {
|
||||||
|
margin: 0px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
background: rgba(255,255,255,0.50);
|
background: rgba(255,255,255,0.50);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
|
@ -662,6 +662,11 @@
|
||||||
"addMeetingURL": "Add a meeting link",
|
"addMeetingURL": "Add a meeting link",
|
||||||
"confirmAddLink": "Do you want to add a Jitsi link to this event?",
|
"confirmAddLink": "Do you want to add a Jitsi link to this event?",
|
||||||
"confirmAddLinkTitle": "Calendar",
|
"confirmAddLinkTitle": "Calendar",
|
||||||
|
"error": {
|
||||||
|
"appConfiguration": "Calendar integration is not properly configured.",
|
||||||
|
"generic": "An error has occurred. Please check your calendar settings or try refreshing the calendar.",
|
||||||
|
"notSignedIn": "An error occurred while authenticating to see calendar events. Please check your calendar settings and try logging in again."
|
||||||
|
},
|
||||||
"join": "Join",
|
"join": "Join",
|
||||||
"joinTooltip": "Join the meeting",
|
"joinTooltip": "Join the meeting",
|
||||||
"nextMeeting": "next meeting",
|
"nextMeeting": "next meeting",
|
||||||
|
|
|
@ -33,6 +33,17 @@ export const REFRESH_CALENDAR = Symbol('REFRESH_CALENDAR');
|
||||||
*/
|
*/
|
||||||
export const SET_CALENDAR_AUTHORIZATION = Symbol('SET_CALENDAR_AUTHORIZATION');
|
export const SET_CALENDAR_AUTHORIZATION = Symbol('SET_CALENDAR_AUTHORIZATION');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to update the last error that occurred while trying to authenticate
|
||||||
|
* with or fetch data from the calendar integration.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_CALENDAR_ERROR,
|
||||||
|
* error: ?Object
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_CALENDAR_ERROR = Symbol('SET_CALENDAR_ERROR');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action to update the current calendar entry list in the store.
|
* Action to update the current calendar entry list in the store.
|
||||||
*
|
*
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { createCalendarConnectedEvent, sendAnalytics } from '../analytics';
|
||||||
import {
|
import {
|
||||||
CLEAR_CALENDAR_INTEGRATION,
|
CLEAR_CALENDAR_INTEGRATION,
|
||||||
SET_CALENDAR_AUTH_STATE,
|
SET_CALENDAR_AUTH_STATE,
|
||||||
|
SET_CALENDAR_ERROR,
|
||||||
SET_CALENDAR_INTEGRATION,
|
SET_CALENDAR_INTEGRATION,
|
||||||
SET_CALENDAR_PROFILE_EMAIL,
|
SET_CALENDAR_PROFILE_EMAIL,
|
||||||
SET_LOADING_CALENDAR_EVENTS
|
SET_LOADING_CALENDAR_EVENTS
|
||||||
|
@ -119,6 +120,22 @@ export function setCalendarAPIAuthState(newState: ?Object) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an action to update the calendar error state in redux.
|
||||||
|
*
|
||||||
|
* @param {Object} error - An object with error details.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_CALENDAR_ERROR,
|
||||||
|
* error: Object
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setCalendarError(error: ?Object) {
|
||||||
|
return {
|
||||||
|
type: SET_CALENDAR_ERROR,
|
||||||
|
error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an action to update the current calendar profile email state in redux.
|
* Sends an action to update the current calendar profile email state in redux.
|
||||||
*
|
*
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
} from '../../analytics';
|
} from '../../analytics';
|
||||||
|
|
||||||
import { refreshCalendar } from '../actions';
|
import { refreshCalendar } from '../actions';
|
||||||
|
import { ERRORS } from '../constants';
|
||||||
import { isCalendarEnabled } from '../functions';
|
import { isCalendarEnabled } from '../functions';
|
||||||
|
|
||||||
import CalendarListContent from './CalendarListContent';
|
import CalendarListContent from './CalendarListContent';
|
||||||
|
@ -24,6 +25,12 @@ declare var interfaceConfig: Object;
|
||||||
*/
|
*/
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The error object containing details about any error that has occurred
|
||||||
|
* while interacting with calendar integration.
|
||||||
|
*/
|
||||||
|
_calendarError: ?Object,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not a calendar may be connected for fetching calendar events.
|
* Whether or not a calendar may be connected for fetching calendar events.
|
||||||
*/
|
*/
|
||||||
|
@ -87,6 +94,54 @@ class CalendarList extends AbstractPage<Props> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a component for showing the error message related to calendar
|
||||||
|
* sync.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {React$Component}
|
||||||
|
*/
|
||||||
|
_getErrorMessage() {
|
||||||
|
const { _calendarError = {}, t } = this.props;
|
||||||
|
|
||||||
|
let errorMessageKey = 'calendarSync.error.generic';
|
||||||
|
let showRefreshButton = true;
|
||||||
|
let showSettingsButton = true;
|
||||||
|
|
||||||
|
if (_calendarError.error === ERRORS.GOOGLE_APP_MISCONFIGURED) {
|
||||||
|
errorMessageKey = 'calendarSync.error.appConfiguration';
|
||||||
|
showRefreshButton = false;
|
||||||
|
showSettingsButton = false;
|
||||||
|
} else if (_calendarError.error === ERRORS.AUTH_FAILED) {
|
||||||
|
errorMessageKey = 'calendarSync.error.notSignedIn';
|
||||||
|
showRefreshButton = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className = 'meetings-list-empty'>
|
||||||
|
<p className = 'description'>
|
||||||
|
{ t(errorMessageKey) }
|
||||||
|
</p>
|
||||||
|
<div className = 'calendar-action-buttons'>
|
||||||
|
{ showSettingsButton
|
||||||
|
&& <div
|
||||||
|
className = 'button'
|
||||||
|
onClick = { this._onOpenSettings }>
|
||||||
|
{ t('calendarSync.permissionButton') }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{ showRefreshButton
|
||||||
|
&& <div
|
||||||
|
className = 'button'
|
||||||
|
onClick = { this._onRefreshEvents }>
|
||||||
|
{ t('calendarSync.refresh') }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_getRenderListEmptyComponent: () => Object;
|
_getRenderListEmptyComponent: () => Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,12 +152,21 @@ class CalendarList extends AbstractPage<Props> {
|
||||||
* @returns {React$Component}
|
* @returns {React$Component}
|
||||||
*/
|
*/
|
||||||
_getRenderListEmptyComponent() {
|
_getRenderListEmptyComponent() {
|
||||||
const { _hasIntegrationSelected, _hasLoadedEvents, t } = this.props;
|
const {
|
||||||
|
_calendarError,
|
||||||
|
_hasIntegrationSelected,
|
||||||
|
_hasLoadedEvents,
|
||||||
|
t
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if (_hasIntegrationSelected && _hasLoadedEvents) {
|
if (_calendarError) {
|
||||||
|
return this._getErrorMessage();
|
||||||
|
} else if (_hasIntegrationSelected && _hasLoadedEvents) {
|
||||||
return (
|
return (
|
||||||
<div className = 'meetings-list-empty'>
|
<div className = 'meetings-list-empty'>
|
||||||
<div>{ t('calendarSync.noEvents') }</div>
|
<p className = 'description'>
|
||||||
|
{ t('calendarSync.noEvents') }
|
||||||
|
</p>
|
||||||
<div
|
<div
|
||||||
className = 'button'
|
className = 'button'
|
||||||
onClick = { this._onRefreshEvents }>
|
onClick = { this._onRefreshEvents }>
|
||||||
|
@ -172,18 +236,21 @@ class CalendarList extends AbstractPage<Props> {
|
||||||
* @param {Object} state - The Redux state.
|
* @param {Object} state - The Redux state.
|
||||||
* @private
|
* @private
|
||||||
* @returns {{
|
* @returns {{
|
||||||
|
* _calendarError: Object,
|
||||||
* _hasIntegrationSelected: boolean,
|
* _hasIntegrationSelected: boolean,
|
||||||
* _hasLoadedEvents: boolean
|
* _hasLoadedEvents: boolean
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state) {
|
||||||
const {
|
const {
|
||||||
|
error,
|
||||||
events,
|
events,
|
||||||
integrationType,
|
integrationType,
|
||||||
isLoadingEvents
|
isLoadingEvents
|
||||||
} = state['features/calendar-sync'];
|
} = state['features/calendar-sync'];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
_calendarError: error,
|
||||||
_hasIntegrationSelected: Boolean(integrationType),
|
_hasIntegrationSelected: Boolean(integrationType),
|
||||||
_hasLoadedEvents: Boolean(events) || !isLoadingEvents
|
_hasLoadedEvents: Boolean(events) || !isLoadingEvents
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,17 @@ export const CALENDAR_TYPE = {
|
||||||
MICROSOFT: 'microsoft'
|
MICROSOFT: 'microsoft'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration of known errors that can occur while interacting with the
|
||||||
|
* calendar integration.
|
||||||
|
*
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const ERRORS = {
|
||||||
|
AUTH_FAILED: 'sign_in_failed',
|
||||||
|
GOOGLE_APP_MISCONFIGURED: 'idpiframe_initialization_failed'
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of days to fetch.
|
* The number of days to fetch.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { setLoadingCalendarEvents } from './actions';
|
import {
|
||||||
|
clearCalendarIntegration,
|
||||||
|
setCalendarError,
|
||||||
|
setLoadingCalendarEvents
|
||||||
|
} from './actions';
|
||||||
export * from './functions.any';
|
export * from './functions.any';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CALENDAR_TYPE,
|
CALENDAR_TYPE,
|
||||||
|
ERRORS,
|
||||||
FETCH_END_DAYS,
|
FETCH_END_DAYS,
|
||||||
FETCH_START_DAYS
|
FETCH_START_DAYS
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
@ -66,7 +71,9 @@ export function _fetchCalendarEntries(
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject('Not authorized, please sign in!');
|
return Promise.reject({
|
||||||
|
error: ERRORS.AUTH_FAILED
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.then(() => dispatch(integration.getCalendarEntries(
|
.then(() => dispatch(integration.getCalendarEntries(
|
||||||
FETCH_START_DAYS, FETCH_END_DAYS)))
|
FETCH_START_DAYS, FETCH_END_DAYS)))
|
||||||
|
@ -74,8 +81,17 @@ export function _fetchCalendarEntries(
|
||||||
dispatch,
|
dispatch,
|
||||||
getState
|
getState
|
||||||
}, events))
|
}, events))
|
||||||
.catch(error =>
|
.then(() => {
|
||||||
logger.error('Error fetching calendar.', error))
|
dispatch(setCalendarError());
|
||||||
|
}, error => {
|
||||||
|
logger.error('Error fetching calendar.', error);
|
||||||
|
|
||||||
|
if (error.error === ERRORS.AUTH_FAILED) {
|
||||||
|
dispatch(clearCalendarIntegration());
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(setCalendarError(error));
|
||||||
|
})
|
||||||
.then(() => dispatch(setLoadingCalendarEvents(false)));
|
.then(() => dispatch(setLoadingCalendarEvents(false)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
CLEAR_CALENDAR_INTEGRATION,
|
CLEAR_CALENDAR_INTEGRATION,
|
||||||
SET_CALENDAR_AUTH_STATE,
|
SET_CALENDAR_AUTH_STATE,
|
||||||
SET_CALENDAR_AUTHORIZATION,
|
SET_CALENDAR_AUTHORIZATION,
|
||||||
|
SET_CALENDAR_ERROR,
|
||||||
SET_CALENDAR_EVENTS,
|
SET_CALENDAR_EVENTS,
|
||||||
SET_CALENDAR_INTEGRATION,
|
SET_CALENDAR_INTEGRATION,
|
||||||
SET_CALENDAR_PROFILE_EMAIL,
|
SET_CALENDAR_PROFILE_EMAIL,
|
||||||
|
@ -86,6 +87,9 @@ isCalendarEnabled()
|
||||||
case SET_CALENDAR_AUTHORIZATION:
|
case SET_CALENDAR_AUTHORIZATION:
|
||||||
return set(state, 'authorization', action.authorization);
|
return set(state, 'authorization', action.authorization);
|
||||||
|
|
||||||
|
case SET_CALENDAR_ERROR:
|
||||||
|
return set(state, 'error', action.error);
|
||||||
|
|
||||||
case SET_CALENDAR_EVENTS:
|
case SET_CALENDAR_EVENTS:
|
||||||
return set(state, 'events', action.events);
|
return set(state, 'events', action.events);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue