diff --git a/client/i18n/locales/chinese-traditional/translations.json b/client/i18n/locales/chinese-traditional/translations.json index 975a640b19..0aeacae34f 100644 --- a/client/i18n/locales/chinese-traditional/translations.json +++ b/client/i18n/locales/chinese-traditional/translations.json @@ -364,6 +364,7 @@ }, "misc": { "offline": "你已離線,學習進度可能不會被保存", + "server-offline": "The server could not be reached and your progress may not be saved. Please contact support if this message persists", "unsubscribed": "你已成功取消訂閱", "keep-coding": "無論你做什麼,都要繼續編程!", "email-signup": "郵件註冊", diff --git a/client/i18n/locales/chinese/translations.json b/client/i18n/locales/chinese/translations.json index 8fa0b8a5c1..3dc748f1b1 100644 --- a/client/i18n/locales/chinese/translations.json +++ b/client/i18n/locales/chinese/translations.json @@ -364,6 +364,7 @@ }, "misc": { "offline": "你已离线,学习进度可能不会被保存", + "server-offline": "The server could not be reached and your progress may not be saved. Please contact support if this message persists", "unsubscribed": "你已成功取消订阅", "keep-coding": "无论你做什么,都要继续编程!", "email-signup": "邮件注册", diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index d782b61f8c..2a94748cc0 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -318,7 +318,7 @@ "nicely-done": "Nicely done. You just completed {{block}}.", "credit-card": "Credit Card", "credit-card-2": "Or donate with a credit card:", - "or-card": "Or donate with card", + "or-card": "Or donate with card", "paypal": "with PayPal:", "need-email": "We need a valid email address to which we can send your donation tax receipt.", "went-wrong": "Something went wrong processing your donation. Your card has not been charged.", @@ -364,6 +364,7 @@ }, "misc": { "offline": "You appear to be offline, your progress may not be saved", + "server-offline": "The server could not be reached and your progress may not be saved. Please contact <0>support if this message persists", "unsubscribed": "You have successfully been unsubscribed", "keep-coding": "Whatever you go on to, keep coding!", "email-signup": "Email Sign Up", diff --git a/client/i18n/locales/espanol/translations.json b/client/i18n/locales/espanol/translations.json index ac15371c42..33321b0d43 100644 --- a/client/i18n/locales/espanol/translations.json +++ b/client/i18n/locales/espanol/translations.json @@ -364,6 +364,7 @@ }, "misc": { "offline": "Parece que no estás conectado, es posible que tu progreso no se guarde", + "server-offline": "The server could not be reached and your progress may not be saved. Please contact support if this message persists", "unsubscribed": "Haz cancelado tu subscripción exitosamente", "keep-coding": "Sea lo que sea que hagas, ¡sigue programando!", "email-signup": "Registrarse con Email", diff --git a/client/i18n/locales/italian/translations.json b/client/i18n/locales/italian/translations.json index 4387131f39..735fa9e735 100644 --- a/client/i18n/locales/italian/translations.json +++ b/client/i18n/locales/italian/translations.json @@ -364,6 +364,7 @@ }, "misc": { "offline": "Sembra che tu sia offline, i tuoi progressi potrebbero non essere salvati", + "server-offline": "The server could not be reached and your progress may not be saved. Please contact support if this message persists", "unsubscribed": "Hai annullato correttamente l'iscrizione", "keep-coding": "Qualsiasi cosa tu abbia intenzione di fare, continua a programmare!", "email-signup": "Registrazione con email", diff --git a/client/i18n/locales/portuguese/translations.json b/client/i18n/locales/portuguese/translations.json index 364231ab57..32d7e00bf8 100644 --- a/client/i18n/locales/portuguese/translations.json +++ b/client/i18n/locales/portuguese/translations.json @@ -364,6 +364,7 @@ }, "misc": { "offline": "Você parece estar off-line, seu progresso pode não ser salvo", + "server-offline": "The server could not be reached and your progress may not be saved. Please contact support if this message persists", "unsubscribed": "Sua assinatura foi cancelada com sucesso", "keep-coding": "Seja o que for, continue programando!", "email-signup": "Inscrição via e-mail", diff --git a/client/src/components/Flash/flash.css b/client/src/components/Flash/flash.css index 6ba712eb88..6bb0f492d6 100644 --- a/client/src/components/Flash/flash.css +++ b/client/src/components/Flash/flash.css @@ -7,7 +7,7 @@ padding-bottom: 3px; position: fixed; width: 100%; - z-index: 100; + z-index: 150; } .flash-message div { diff --git a/client/src/components/OfflineWarning/offline-warning.css b/client/src/components/OfflineWarning/offline-warning.css index 34ec578d28..dc8e9630b2 100644 --- a/client/src/components/OfflineWarning/offline-warning.css +++ b/client/src/components/OfflineWarning/offline-warning.css @@ -3,4 +3,11 @@ display: flex; justify-content: center; align-items: center; + position: fixed; + width: 100%; + z-index: 150; +} + +.offline-warning a { + margin: 0 1ch; } diff --git a/client/src/components/OfflineWarning/offline-warning.tsx b/client/src/components/OfflineWarning/offline-warning.tsx index 3500ed17e3..42d3605a28 100644 --- a/client/src/components/OfflineWarning/offline-warning.tsx +++ b/client/src/components/OfflineWarning/offline-warning.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import './offline-warning.css'; @@ -8,20 +8,30 @@ let id: ReturnType; interface OfflineWarningProps { isOnline: boolean; + isServerOnline: boolean; isSignedIn: boolean; } function OfflineWarning({ isOnline, + isServerOnline, isSignedIn }: OfflineWarningProps): JSX.Element | null { const { t } = useTranslation(); const [showWarning, setShowWarning] = React.useState(false); + let message; - if (!isSignedIn || isOnline) { + if (!isSignedIn || (isOnline && isServerOnline)) { clearTimeout(id); if (showWarning) setShowWarning(false); } else { + message = !isOnline ? ( + t('misc.offline') + ) : ( + + placeholder + + ); timeout(); } @@ -32,7 +42,10 @@ function OfflineWarning({ } return showWarning ? ( -
{t('misc.offline')}
+ <> +
{message}
+
+ ) : null; } diff --git a/client/src/components/layouts/Default.js b/client/src/components/layouts/Default.js index d0fb3363ec..2873285999 100644 --- a/client/src/components/layouts/Default.js +++ b/client/src/components/layouts/Default.js @@ -18,7 +18,9 @@ import { fetchUser, isSignedInSelector, onlineStatusChange, + serverStatusChange, isOnlineSelector, + isServerOnlineSelector, userFetchStateSelector, userSelector, usernameSelector, @@ -55,10 +57,12 @@ const propTypes = { }), hasMessage: PropTypes.bool, isOnline: PropTypes.bool.isRequired, + isServerOnline: PropTypes.bool.isRequired, isSignedIn: PropTypes.bool, onlineStatusChange: PropTypes.func.isRequired, pathname: PropTypes.string.isRequired, removeFlashMessage: PropTypes.func.isRequired, + serverStatusChange: PropTypes.func.isRequired, showFooter: PropTypes.bool, signedInUserName: PropTypes.string, t: PropTypes.func.isRequired, @@ -71,14 +75,16 @@ const mapStateToProps = createSelector( isSignedInSelector, flashMessageSelector, isOnlineSelector, + isServerOnlineSelector, userFetchStateSelector, userSelector, usernameSelector, - (isSignedIn, flashMessage, isOnline, fetchState, user) => ({ + (isSignedIn, flashMessage, isOnline, isServerOnline, fetchState, user) => ({ isSignedIn, flashMessage, hasMessage: !!flashMessage.message, isOnline, + isServerOnline, fetchState, theme: user.theme, user @@ -87,7 +93,13 @@ const mapStateToProps = createSelector( const mapDispatchToProps = dispatch => bindActionCreators( - { fetchUser, removeFlashMessage, onlineStatusChange, executeGA }, + { + fetchUser, + removeFlashMessage, + onlineStatusChange, + serverStatusChange, + executeGA + }, dispatch ); @@ -130,6 +142,7 @@ class DefaultLayout extends Component { fetchState, flashMessage, isOnline, + isServerOnline, isSignedIn, removeFlashMessage, showFooter = true, @@ -201,7 +214,11 @@ class DefaultLayout extends Component {
- + {hasMessage && flashMessage ? ( ) : null} diff --git a/client/src/redux/action-types.js b/client/src/redux/action-types.js index c1b89eb70c..a2e776b715 100644 --- a/client/src/redux/action-types.js +++ b/client/src/redux/action-types.js @@ -12,6 +12,7 @@ export const actionTypes = createTypes( 'preventProgressDonationRequests', 'openDonationModal', 'onlineStatusChange', + 'serverStatusChange', 'resetUserData', 'tryToShowDonationModal', 'executeGA', diff --git a/client/src/redux/failed-updates-epic.js b/client/src/redux/failed-updates-epic.js index 80b905f705..30f2219ae4 100644 --- a/client/src/redux/failed-updates-epic.js +++ b/client/src/redux/failed-updates-epic.js @@ -15,7 +15,11 @@ import { backEndProject } from '../../utils/challenge-types'; import { isGoodXHRStatus } from '../templates/Challenges/utils'; import postUpdate$ from '../templates/Challenges/utils/postUpdate$'; import { actionTypes } from './action-types'; -import { onlineStatusChange, isOnlineSelector, isSignedInSelector } from './'; +import { + serverStatusChange, + isServerOnlineSelector, + isSignedInSelector +} from './'; const key = 'fcc-failed-updates'; @@ -37,14 +41,14 @@ function failedUpdateEpic(action$, state$) { store.set(key, [...failures, payload]); } }), - map(() => onlineStatusChange(false)) + map(() => serverStatusChange(false)) ); const flushUpdates = action$.pipe( ofType(actionTypes.fetchUserComplete, actionTypes.updateComplete), filter(() => isSignedInSelector(state$.value)), filter(() => store.get(key)), - filter(() => isOnlineSelector(state$.value)), + filter(() => isServerOnlineSelector(state$.value)), tap(() => { let failures = store.get(key) || []; diff --git a/client/src/redux/failed-updates-epic.test.js b/client/src/redux/failed-updates-epic.test.js index f344e74619..f677d5f290 100644 --- a/client/src/redux/failed-updates-epic.test.js +++ b/client/src/redux/failed-updates-epic.test.js @@ -27,6 +27,7 @@ describe('failed-updates-epic', () => { const initialState = { app: { isOnline: true, + isServerOnline: true, appUsername: 'developmentuser' } }; diff --git a/client/src/redux/index.js b/client/src/redux/index.js index 332ff91014..02e4ac0b49 100644 --- a/client/src/redux/index.js +++ b/client/src/redux/index.js @@ -60,6 +60,7 @@ const initialState = { sessionMeta: { activeDonations: 0 }, showDonationModal: false, isOnline: true, + isServerOnline: true, donationFormState: { ...defaultDonationFormState } @@ -102,6 +103,7 @@ export const updateDonationFormState = createAction( ); export const onlineStatusChange = createAction(actionTypes.onlineStatusChange); +export const serverStatusChange = createAction(actionTypes.serverStatusChange); // TODO: re-evaluate this since /internal is no longer used. // `hardGoTo` is used to hit the API server directly @@ -189,6 +191,7 @@ export const stepsToClaimSelector = state => { }; export const isDonatingSelector = state => userSelector(state).isDonating; export const isOnlineSelector = state => state[ns].isOnline; +export const isServerOnlineSelector = state => state[ns].isServerOnline; export const isSignedInSelector = state => !!state[ns].appUsername; export const isDonationModalOpenSelector = state => state[ns].showDonationModal; export const recentlyClaimedBlockSelector = state => @@ -553,6 +556,10 @@ export const reducer = handleActions( ...state, isOnline }), + [actionTypes.serverStatusChange]: (state, { payload: isServerOnline }) => ({ + ...state, + isServerOnline + }), [actionTypes.closeDonationModal]: state => ({ ...state, showDonationModal: false diff --git a/client/src/redux/update-complete-epic.js b/client/src/redux/update-complete-epic.js index bde7029816..4953d9f3b7 100644 --- a/client/src/redux/update-complete-epic.js +++ b/client/src/redux/update-complete-epic.js @@ -2,12 +2,12 @@ import { ofType } from 'redux-observable'; import { mapTo, filter } from 'rxjs/operators'; import { actionTypes as types } from './action-types'; -import { onlineStatusChange, isOnlineSelector } from './'; +import { serverStatusChange, isServerOnlineSelector } from './'; export default function updateCompleteEpic(action$, state$) { return action$.pipe( ofType(types.updateComplete), - filter(() => !isOnlineSelector(state$.value)), - mapTo(onlineStatusChange(true)) + filter(() => !isServerOnlineSelector(state$.value)), + mapTo(serverStatusChange(true)) ); }