Skip to content

Commit bc802cb

Browse files
authored
feat: added warning for unreachable server (freeCodeCamp#43576)
* feat: added warning for unreachable server * fix: update initial state in test file * fix: make offline warning scroll with page * adjust z-indexes for warning banners * add hyperlink for offline warning
1 parent 83354c5 commit bc802cb

File tree

15 files changed

+70
-14
lines changed

15 files changed

+70
-14
lines changed

client/i18n/locales/chinese-traditional/translations.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@
364364
},
365365
"misc": {
366366
"offline": "你已離線,學習進度可能不會被保存",
367+
"server-offline": "The server could not be reached and your progress may not be saved. Please contact support if this message persists",
367368
"unsubscribed": "你已成功取消訂閱",
368369
"keep-coding": "無論你做什麼,都要繼續編程!",
369370
"email-signup": "郵件註冊",

client/i18n/locales/chinese/translations.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@
364364
},
365365
"misc": {
366366
"offline": "你已离线,学习进度可能不会被保存",
367+
"server-offline": "The server could not be reached and your progress may not be saved. Please contact support if this message persists",
367368
"unsubscribed": "你已成功取消订阅",
368369
"keep-coding": "无论你做什么,都要继续编程!",
369370
"email-signup": "邮件注册",

client/i18n/locales/english/translations.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@
318318
"nicely-done": "Nicely done. You just completed {{block}}.",
319319
"credit-card": "Credit Card",
320320
"credit-card-2": "Or donate with a credit card:",
321-
"or-card": "Or donate with card",
321+
"or-card": "Or donate with card",
322322
"paypal": "with PayPal:",
323323
"need-email": "We need a valid email address to which we can send your donation tax receipt.",
324324
"went-wrong": "Something went wrong processing your donation. Your card has not been charged.",
@@ -364,6 +364,7 @@
364364
},
365365
"misc": {
366366
"offline": "You appear to be offline, your progress may not be saved",
367+
"server-offline": "The server could not be reached and your progress may not be saved. Please contact <0>support</0> if this message persists",
367368
"unsubscribed": "You have successfully been unsubscribed",
368369
"keep-coding": "Whatever you go on to, keep coding!",
369370
"email-signup": "Email Sign Up",

client/i18n/locales/espanol/translations.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@
364364
},
365365
"misc": {
366366
"offline": "Parece que no estás conectado, es posible que tu progreso no se guarde",
367+
"server-offline": "The server could not be reached and your progress may not be saved. Please contact support if this message persists",
367368
"unsubscribed": "Haz cancelado tu subscripción exitosamente",
368369
"keep-coding": "Sea lo que sea que hagas, ¡sigue programando!",
369370
"email-signup": "Registrarse con Email",

client/i18n/locales/italian/translations.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@
364364
},
365365
"misc": {
366366
"offline": "Sembra che tu sia offline, i tuoi progressi potrebbero non essere salvati",
367+
"server-offline": "The server could not be reached and your progress may not be saved. Please contact support if this message persists",
367368
"unsubscribed": "Hai annullato correttamente l'iscrizione",
368369
"keep-coding": "Qualsiasi cosa tu abbia intenzione di fare, continua a programmare!",
369370
"email-signup": "Registrazione con email",

client/i18n/locales/portuguese/translations.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@
364364
},
365365
"misc": {
366366
"offline": "Você parece estar off-line, seu progresso pode não ser salvo",
367+
"server-offline": "The server could not be reached and your progress may not be saved. Please contact support if this message persists",
367368
"unsubscribed": "Sua assinatura foi cancelada com sucesso",
368369
"keep-coding": "Seja o que for, continue programando!",
369370
"email-signup": "Inscrição via e-mail",

client/src/components/Flash/flash.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
padding-bottom: 3px;
88
position: fixed;
99
width: 100%;
10-
z-index: 100;
10+
z-index: 150;
1111
}
1212

1313
.flash-message div {

client/src/components/OfflineWarning/offline-warning.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,11 @@
33
display: flex;
44
justify-content: center;
55
align-items: center;
6+
position: fixed;
7+
width: 100%;
8+
z-index: 150;
9+
}
10+
11+
.offline-warning a {
12+
margin: 0 1ch;
613
}

client/src/components/OfflineWarning/offline-warning.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { useTranslation } from 'react-i18next';
2+
import { Trans, useTranslation } from 'react-i18next';
33

44
import './offline-warning.css';
55

@@ -8,20 +8,30 @@ let id: ReturnType<typeof setTimeout>;
88

99
interface OfflineWarningProps {
1010
isOnline: boolean;
11+
isServerOnline: boolean;
1112
isSignedIn: boolean;
1213
}
1314

1415
function OfflineWarning({
1516
isOnline,
17+
isServerOnline,
1618
isSignedIn
1719
}: OfflineWarningProps): JSX.Element | null {
1820
const { t } = useTranslation();
1921
const [showWarning, setShowWarning] = React.useState(false);
22+
let message;
2023

21-
if (!isSignedIn || isOnline) {
24+
if (!isSignedIn || (isOnline && isServerOnline)) {
2225
clearTimeout(id);
2326
if (showWarning) setShowWarning(false);
2427
} else {
28+
message = !isOnline ? (
29+
t('misc.offline')
30+
) : (
31+
<Trans i18nKey='misc.server-offline'>
32+
<a href={'mailto:[email protected]'}>placeholder</a>
33+
</Trans>
34+
);
2535
timeout();
2636
}
2737

@@ -32,7 +42,10 @@ function OfflineWarning({
3242
}
3343

3444
return showWarning ? (
35-
<div className='offline-warning alert-info'>{t('misc.offline')}</div>
45+
<>
46+
<div className='offline-warning alert-info'>{message}</div>
47+
<div style={{ height: `38px` }} />
48+
</>
3649
) : null;
3750
}
3851

client/src/components/layouts/Default.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import {
1818
fetchUser,
1919
isSignedInSelector,
2020
onlineStatusChange,
21+
serverStatusChange,
2122
isOnlineSelector,
23+
isServerOnlineSelector,
2224
userFetchStateSelector,
2325
userSelector,
2426
usernameSelector,
@@ -55,10 +57,12 @@ const propTypes = {
5557
}),
5658
hasMessage: PropTypes.bool,
5759
isOnline: PropTypes.bool.isRequired,
60+
isServerOnline: PropTypes.bool.isRequired,
5861
isSignedIn: PropTypes.bool,
5962
onlineStatusChange: PropTypes.func.isRequired,
6063
pathname: PropTypes.string.isRequired,
6164
removeFlashMessage: PropTypes.func.isRequired,
65+
serverStatusChange: PropTypes.func.isRequired,
6266
showFooter: PropTypes.bool,
6367
signedInUserName: PropTypes.string,
6468
t: PropTypes.func.isRequired,
@@ -71,14 +75,16 @@ const mapStateToProps = createSelector(
7175
isSignedInSelector,
7276
flashMessageSelector,
7377
isOnlineSelector,
78+
isServerOnlineSelector,
7479
userFetchStateSelector,
7580
userSelector,
7681
usernameSelector,
77-
(isSignedIn, flashMessage, isOnline, fetchState, user) => ({
82+
(isSignedIn, flashMessage, isOnline, isServerOnline, fetchState, user) => ({
7883
isSignedIn,
7984
flashMessage,
8085
hasMessage: !!flashMessage.message,
8186
isOnline,
87+
isServerOnline,
8288
fetchState,
8389
theme: user.theme,
8490
user
@@ -87,7 +93,13 @@ const mapStateToProps = createSelector(
8793

8894
const mapDispatchToProps = dispatch =>
8995
bindActionCreators(
90-
{ fetchUser, removeFlashMessage, onlineStatusChange, executeGA },
96+
{
97+
fetchUser,
98+
removeFlashMessage,
99+
onlineStatusChange,
100+
serverStatusChange,
101+
executeGA
102+
},
91103
dispatch
92104
);
93105

@@ -130,6 +142,7 @@ class DefaultLayout extends Component {
130142
fetchState,
131143
flashMessage,
132144
isOnline,
145+
isServerOnline,
133146
isSignedIn,
134147
removeFlashMessage,
135148
showFooter = true,
@@ -201,7 +214,11 @@ class DefaultLayout extends Component {
201214
</Helmet>
202215
<div className={`default-layout`}>
203216
<Header fetchState={fetchState} user={user} />
204-
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
217+
<OfflineWarning
218+
isOnline={isOnline}
219+
isServerOnline={isServerOnline}
220+
isSignedIn={isSignedIn}
221+
/>
205222
{hasMessage && flashMessage ? (
206223
<Flash flashMessage={flashMessage} onClose={removeFlashMessage} />
207224
) : null}

client/src/redux/action-types.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const actionTypes = createTypes(
1212
'preventProgressDonationRequests',
1313
'openDonationModal',
1414
'onlineStatusChange',
15+
'serverStatusChange',
1516
'resetUserData',
1617
'tryToShowDonationModal',
1718
'executeGA',

client/src/redux/failed-updates-epic.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import { backEndProject } from '../../utils/challenge-types';
1515
import { isGoodXHRStatus } from '../templates/Challenges/utils';
1616
import postUpdate$ from '../templates/Challenges/utils/postUpdate$';
1717
import { actionTypes } from './action-types';
18-
import { onlineStatusChange, isOnlineSelector, isSignedInSelector } from './';
18+
import {
19+
serverStatusChange,
20+
isServerOnlineSelector,
21+
isSignedInSelector
22+
} from './';
1923

2024
const key = 'fcc-failed-updates';
2125

@@ -37,14 +41,14 @@ function failedUpdateEpic(action$, state$) {
3741
store.set(key, [...failures, payload]);
3842
}
3943
}),
40-
map(() => onlineStatusChange(false))
44+
map(() => serverStatusChange(false))
4145
);
4246

4347
const flushUpdates = action$.pipe(
4448
ofType(actionTypes.fetchUserComplete, actionTypes.updateComplete),
4549
filter(() => isSignedInSelector(state$.value)),
4650
filter(() => store.get(key)),
47-
filter(() => isOnlineSelector(state$.value)),
51+
filter(() => isServerOnlineSelector(state$.value)),
4852
tap(() => {
4953
let failures = store.get(key) || [];
5054

client/src/redux/failed-updates-epic.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('failed-updates-epic', () => {
2727
const initialState = {
2828
app: {
2929
isOnline: true,
30+
isServerOnline: true,
3031
appUsername: 'developmentuser'
3132
}
3233
};

client/src/redux/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const initialState = {
6060
sessionMeta: { activeDonations: 0 },
6161
showDonationModal: false,
6262
isOnline: true,
63+
isServerOnline: true,
6364
donationFormState: {
6465
...defaultDonationFormState
6566
}
@@ -102,6 +103,7 @@ export const updateDonationFormState = createAction(
102103
);
103104

104105
export const onlineStatusChange = createAction(actionTypes.onlineStatusChange);
106+
export const serverStatusChange = createAction(actionTypes.serverStatusChange);
105107

106108
// TODO: re-evaluate this since /internal is no longer used.
107109
// `hardGoTo` is used to hit the API server directly
@@ -189,6 +191,7 @@ export const stepsToClaimSelector = state => {
189191
};
190192
export const isDonatingSelector = state => userSelector(state).isDonating;
191193
export const isOnlineSelector = state => state[ns].isOnline;
194+
export const isServerOnlineSelector = state => state[ns].isServerOnline;
192195
export const isSignedInSelector = state => !!state[ns].appUsername;
193196
export const isDonationModalOpenSelector = state => state[ns].showDonationModal;
194197
export const recentlyClaimedBlockSelector = state =>
@@ -553,6 +556,10 @@ export const reducer = handleActions(
553556
...state,
554557
isOnline
555558
}),
559+
[actionTypes.serverStatusChange]: (state, { payload: isServerOnline }) => ({
560+
...state,
561+
isServerOnline
562+
}),
556563
[actionTypes.closeDonationModal]: state => ({
557564
...state,
558565
showDonationModal: false

client/src/redux/update-complete-epic.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { ofType } from 'redux-observable';
22
import { mapTo, filter } from 'rxjs/operators';
33

44
import { actionTypes as types } from './action-types';
5-
import { onlineStatusChange, isOnlineSelector } from './';
5+
import { serverStatusChange, isServerOnlineSelector } from './';
66

77
export default function updateCompleteEpic(action$, state$) {
88
return action$.pipe(
99
ofType(types.updateComplete),
10-
filter(() => !isOnlineSelector(state$.value)),
11-
mapTo(onlineStatusChange(true))
10+
filter(() => !isServerOnlineSelector(state$.value)),
11+
mapTo(serverStatusChange(true))
1212
);
1313
}

0 commit comments

Comments
 (0)