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;
|
||||
border-radius: 4px;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
display: inline-block;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.calendar-action-buttons {
|
||||
.button {
|
||||
margin: 0px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
background: rgba(255,255,255,0.50);
|
||||
box-sizing: border-box;
|
||||
|
|
|
@ -662,6 +662,11 @@
|
|||
"addMeetingURL": "Add a meeting link",
|
||||
"confirmAddLink": "Do you want to add a Jitsi link to this event?",
|
||||
"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",
|
||||
"joinTooltip": "Join the meeting",
|
||||
"nextMeeting": "next meeting",
|
||||
|
|
|
@ -33,6 +33,17 @@ export const REFRESH_CALENDAR = Symbol('REFRESH_CALENDAR');
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -8,6 +8,7 @@ import { createCalendarConnectedEvent, sendAnalytics } from '../analytics';
|
|||
import {
|
||||
CLEAR_CALENDAR_INTEGRATION,
|
||||
SET_CALENDAR_AUTH_STATE,
|
||||
SET_CALENDAR_ERROR,
|
||||
SET_CALENDAR_INTEGRATION,
|
||||
SET_CALENDAR_PROFILE_EMAIL,
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from '../../analytics';
|
||||
|
||||
import { refreshCalendar } from '../actions';
|
||||
import { ERRORS } from '../constants';
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
|
||||
import CalendarListContent from './CalendarListContent';
|
||||
|
@ -24,6 +25,12 @@ declare var interfaceConfig: Object;
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -97,12 +152,21 @@ class CalendarList extends AbstractPage<Props> {
|
|||
* @returns {React$Component}
|
||||
*/
|
||||
_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 (
|
||||
<div className = 'meetings-list-empty'>
|
||||
<div>{ t('calendarSync.noEvents') }</div>
|
||||
<p className = 'description'>
|
||||
{ t('calendarSync.noEvents') }
|
||||
</p>
|
||||
<div
|
||||
className = 'button'
|
||||
onClick = { this._onRefreshEvents }>
|
||||
|
@ -172,18 +236,21 @@ class CalendarList extends AbstractPage<Props> {
|
|||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _calendarError: Object,
|
||||
* _hasIntegrationSelected: boolean,
|
||||
* _hasLoadedEvents: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const {
|
||||
error,
|
||||
events,
|
||||
integrationType,
|
||||
isLoadingEvents
|
||||
} = state['features/calendar-sync'];
|
||||
|
||||
return {
|
||||
_calendarError: error,
|
||||
_hasIntegrationSelected: Boolean(integrationType),
|
||||
_hasLoadedEvents: Boolean(events) || !isLoadingEvents
|
||||
};
|
||||
|
|
|
@ -10,6 +10,17 @@ export const CALENDAR_TYPE = {
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
// @flow
|
||||
|
||||
import { setLoadingCalendarEvents } from './actions';
|
||||
import {
|
||||
clearCalendarIntegration,
|
||||
setCalendarError,
|
||||
setLoadingCalendarEvents
|
||||
} from './actions';
|
||||
export * from './functions.any';
|
||||
|
||||
import {
|
||||
CALENDAR_TYPE,
|
||||
ERRORS,
|
||||
FETCH_END_DAYS,
|
||||
FETCH_START_DAYS
|
||||
} from './constants';
|
||||
|
@ -66,7 +71,9 @@ export function _fetchCalendarEntries(
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject('Not authorized, please sign in!');
|
||||
return Promise.reject({
|
||||
error: ERRORS.AUTH_FAILED
|
||||
});
|
||||
})
|
||||
.then(() => dispatch(integration.getCalendarEntries(
|
||||
FETCH_START_DAYS, FETCH_END_DAYS)))
|
||||
|
@ -74,8 +81,17 @@ export function _fetchCalendarEntries(
|
|||
dispatch,
|
||||
getState
|
||||
}, events))
|
||||
.catch(error =>
|
||||
logger.error('Error fetching calendar.', error))
|
||||
.then(() => {
|
||||
dispatch(setCalendarError());
|
||||
}, error => {
|
||||
logger.error('Error fetching calendar.', error);
|
||||
|
||||
if (error.error === ERRORS.AUTH_FAILED) {
|
||||
dispatch(clearCalendarIntegration());
|
||||
}
|
||||
|
||||
dispatch(setCalendarError(error));
|
||||
})
|
||||
.then(() => dispatch(setLoadingCalendarEvents(false)));
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
CLEAR_CALENDAR_INTEGRATION,
|
||||
SET_CALENDAR_AUTH_STATE,
|
||||
SET_CALENDAR_AUTHORIZATION,
|
||||
SET_CALENDAR_ERROR,
|
||||
SET_CALENDAR_EVENTS,
|
||||
SET_CALENDAR_INTEGRATION,
|
||||
SET_CALENDAR_PROFILE_EMAIL,
|
||||
|
@ -86,6 +87,9 @@ isCalendarEnabled()
|
|||
case SET_CALENDAR_AUTHORIZATION:
|
||||
return set(state, 'authorization', action.authorization);
|
||||
|
||||
case SET_CALENDAR_ERROR:
|
||||
return set(state, 'error', action.error);
|
||||
|
||||
case SET_CALENDAR_EVENTS:
|
||||
return set(state, 'events', action.events);
|
||||
|
||||
|
|
Loading…
Reference in New Issue