2018-07-26 14:37:10 +01:00
|
|
|
import React, { Fragment, Component } from 'react';
|
2018-03-26 13:01:24 +01:00
|
|
|
import PropTypes from 'prop-types';
|
2018-05-24 19:45:38 +01:00
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { bindActionCreators } from 'redux';
|
2018-09-30 11:37:19 +01:00
|
|
|
import { createSelector } from 'reselect';
|
2018-03-26 13:01:24 +01:00
|
|
|
import Helmet from 'react-helmet';
|
2019-01-24 20:33:13 +03:00
|
|
|
import fontawesome from '@fortawesome/fontawesome';
|
2018-03-26 13:01:24 +01:00
|
|
|
|
2018-07-26 14:37:10 +01:00
|
|
|
import {
|
|
|
|
fetchUser,
|
2018-09-30 11:37:19 +01:00
|
|
|
isSignedInSelector,
|
2018-07-26 14:37:10 +01:00
|
|
|
onlineStatusChange,
|
2019-08-19 18:54:04 +03:00
|
|
|
isOnlineSelector,
|
2020-02-04 08:43:56 +03:00
|
|
|
userSelector,
|
|
|
|
executeGA
|
2018-09-30 11:37:19 +01:00
|
|
|
} from '../../redux';
|
2019-06-09 05:25:55 +05:30
|
|
|
import { flashMessageSelector, removeFlashMessage } from '../Flash/redux';
|
2018-09-30 11:37:19 +01:00
|
|
|
|
|
|
|
import { isBrowser } from '../../../utils';
|
|
|
|
|
2019-02-28 15:49:23 +00:00
|
|
|
import WithInstantSearch from '../search/WithInstantSearch';
|
2018-09-30 11:37:19 +01:00
|
|
|
import OfflineWarning from '../OfflineWarning';
|
|
|
|
import Flash from '../Flash';
|
|
|
|
import Header from '../Header';
|
2019-01-14 16:28:00 +03:00
|
|
|
import Footer from '../Footer';
|
2019-10-17 12:18:54 +02:00
|
|
|
// preload common fonts
|
|
|
|
import latoLightURL from '../../../static/fonts/lato/Lato-Light.woff';
|
|
|
|
import latoRegularURL from '../../../static/fonts/lato/Lato-Regular.woff';
|
|
|
|
import latoBoldURL from '../../../static/fonts/lato/Lato-Bold.woff';
|
|
|
|
// eslint-disable-next-line max-len
|
|
|
|
import robotoRegularURL from '../../../static/fonts/roboto-mono/RobotoMono-Regular.woff';
|
|
|
|
// eslint-disable-next-line max-len
|
|
|
|
import robotoBoldURL from '../../../static/fonts/roboto-mono/RobotoMono-Bold.woff';
|
|
|
|
// eslint-disable-next-line max-len
|
|
|
|
import robotoItalicURL from '../../../static/fonts/roboto-mono/RobotoMono-Italic.woff';
|
2018-04-06 14:51:52 +01:00
|
|
|
|
2019-10-16 13:02:26 +03:00
|
|
|
import './fonts.css';
|
2018-05-30 10:20:58 +01:00
|
|
|
import './global.css';
|
2019-08-19 18:54:04 +03:00
|
|
|
import './variables.css';
|
2018-03-26 13:01:24 +01:00
|
|
|
|
2019-01-24 20:33:13 +03:00
|
|
|
fontawesome.config = {
|
|
|
|
autoAddCss: false
|
|
|
|
};
|
|
|
|
|
2018-05-18 19:07:32 +01:00
|
|
|
const metaKeywords = [
|
|
|
|
'javascript',
|
|
|
|
'js',
|
|
|
|
'website',
|
|
|
|
'web',
|
|
|
|
'development',
|
|
|
|
'free',
|
|
|
|
'code',
|
|
|
|
'camp',
|
|
|
|
'course',
|
|
|
|
'courses',
|
|
|
|
'html',
|
|
|
|
'css',
|
|
|
|
'react',
|
|
|
|
'redux',
|
|
|
|
'api',
|
|
|
|
'front',
|
|
|
|
'back',
|
|
|
|
'end',
|
|
|
|
'learn',
|
|
|
|
'tutorial',
|
|
|
|
'programming'
|
|
|
|
];
|
|
|
|
|
2018-09-30 11:37:19 +01:00
|
|
|
const propTypes = {
|
|
|
|
children: PropTypes.node.isRequired,
|
2020-02-04 08:43:56 +03:00
|
|
|
executeGA: PropTypes.func,
|
2018-09-30 11:37:19 +01:00
|
|
|
fetchUser: PropTypes.func.isRequired,
|
2019-06-09 05:25:55 +05:30
|
|
|
flashMessage: PropTypes.shape({
|
|
|
|
id: PropTypes.string,
|
|
|
|
type: PropTypes.string,
|
|
|
|
message: PropTypes.string
|
|
|
|
}),
|
|
|
|
hasMessage: PropTypes.bool,
|
2018-09-30 11:37:19 +01:00
|
|
|
isOnline: PropTypes.bool.isRequired,
|
|
|
|
isSignedIn: PropTypes.bool,
|
|
|
|
onlineStatusChange: PropTypes.func.isRequired,
|
2019-02-28 15:49:23 +00:00
|
|
|
pathname: PropTypes.string.isRequired,
|
2019-02-17 14:47:20 +03:00
|
|
|
removeFlashMessage: PropTypes.func.isRequired,
|
2019-08-19 18:54:04 +03:00
|
|
|
showFooter: PropTypes.bool,
|
2019-11-15 01:35:21 +05:30
|
|
|
theme: PropTypes.string,
|
|
|
|
useTheme: PropTypes.bool
|
2018-09-30 11:37:19 +01:00
|
|
|
};
|
|
|
|
|
2018-06-29 19:00:05 +05:30
|
|
|
const mapStateToProps = createSelector(
|
2018-07-26 14:37:10 +01:00
|
|
|
isSignedInSelector,
|
2019-06-09 05:25:55 +05:30
|
|
|
flashMessageSelector,
|
2018-07-26 14:37:10 +01:00
|
|
|
isOnlineSelector,
|
2019-08-19 18:54:04 +03:00
|
|
|
userSelector,
|
|
|
|
(isSignedIn, flashMessage, isOnline, user) => ({
|
2018-07-26 14:37:10 +01:00
|
|
|
isSignedIn,
|
2019-06-09 05:25:55 +05:30
|
|
|
flashMessage,
|
|
|
|
hasMessage: !!flashMessage.message,
|
2019-08-19 18:54:04 +03:00
|
|
|
isOnline,
|
|
|
|
theme: user.theme
|
2018-07-26 14:37:10 +01:00
|
|
|
})
|
2018-06-29 19:00:05 +05:30
|
|
|
);
|
2019-10-30 21:46:39 +09:00
|
|
|
|
2018-05-24 19:45:38 +01:00
|
|
|
const mapDispatchToProps = dispatch =>
|
2018-09-30 11:37:19 +01:00
|
|
|
bindActionCreators(
|
2020-02-04 08:43:56 +03:00
|
|
|
{ fetchUser, removeFlashMessage, onlineStatusChange, executeGA },
|
2018-09-30 11:37:19 +01:00
|
|
|
dispatch
|
|
|
|
);
|
2018-05-24 19:45:38 +01:00
|
|
|
|
2018-09-30 11:37:19 +01:00
|
|
|
class DefaultLayout extends Component {
|
2018-05-18 19:07:32 +01:00
|
|
|
componentDidMount() {
|
2020-02-04 08:43:56 +03:00
|
|
|
const { isSignedIn, fetchUser, pathname, executeGA } = this.props;
|
2019-02-28 15:49:23 +00:00
|
|
|
if (!isSignedIn) {
|
|
|
|
fetchUser();
|
2018-09-30 11:37:19 +01:00
|
|
|
}
|
2020-02-04 08:43:56 +03:00
|
|
|
executeGA({ type: 'page', data: pathname });
|
2018-07-26 14:37:10 +01:00
|
|
|
|
|
|
|
window.addEventListener('online', this.updateOnlineStatus);
|
|
|
|
window.addEventListener('offline', this.updateOnlineStatus);
|
2018-05-18 19:07:32 +01:00
|
|
|
}
|
2018-07-26 14:37:10 +01:00
|
|
|
|
2019-02-28 15:49:23 +00:00
|
|
|
componentDidUpdate(prevProps) {
|
2020-02-04 08:43:56 +03:00
|
|
|
const { pathname, executeGA } = this.props;
|
2019-02-28 15:49:23 +00:00
|
|
|
const { pathname: prevPathname } = prevProps;
|
|
|
|
if (pathname !== prevPathname) {
|
2020-02-04 08:43:56 +03:00
|
|
|
executeGA({ type: 'page', data: pathname });
|
2018-05-18 19:07:32 +01:00
|
|
|
}
|
|
|
|
}
|
2018-07-26 14:37:10 +01:00
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
window.removeEventListener('online', this.updateOnlineStatus);
|
|
|
|
window.removeEventListener('offline', this.updateOnlineStatus);
|
|
|
|
}
|
|
|
|
|
|
|
|
updateOnlineStatus = () => {
|
|
|
|
const { onlineStatusChange } = this.props;
|
|
|
|
const isOnline =
|
|
|
|
isBrowser() && 'navigator' in window ? window.navigator.onLine : null;
|
|
|
|
return typeof isOnline === 'boolean' ? onlineStatusChange(isOnline) : null;
|
|
|
|
};
|
|
|
|
|
2018-05-18 19:07:32 +01:00
|
|
|
render() {
|
2018-09-30 11:37:19 +01:00
|
|
|
const {
|
|
|
|
children,
|
2019-06-09 05:25:55 +05:30
|
|
|
hasMessage,
|
|
|
|
flashMessage,
|
2019-02-28 15:49:23 +00:00
|
|
|
isOnline,
|
|
|
|
isSignedIn,
|
|
|
|
removeFlashMessage,
|
2019-08-19 18:54:04 +03:00
|
|
|
showFooter = true,
|
2019-11-15 01:35:21 +05:30
|
|
|
theme = 'default',
|
|
|
|
useTheme = true
|
2018-09-30 11:37:19 +01:00
|
|
|
} = this.props;
|
2018-05-18 19:07:32 +01:00
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
<Helmet
|
2019-08-19 18:54:04 +03:00
|
|
|
bodyAttributes={{
|
2019-11-15 01:35:21 +05:30
|
|
|
class: useTheme
|
|
|
|
? `${theme === 'default' ? 'light-palette' : 'dark-palette'}`
|
|
|
|
: 'light-palette'
|
2019-08-19 18:54:04 +03:00
|
|
|
}}
|
2018-05-18 19:07:32 +01:00
|
|
|
meta={[
|
|
|
|
{
|
|
|
|
name: 'description',
|
2020-04-02 17:26:21 +05:30
|
|
|
content: `Learn to code at home. Build projects. Earn certifications. Since 2014,
|
|
|
|
more than 40,000 freeCodeCamp.org graduates have gotten jobs at tech
|
|
|
|
companies including Google, Apple, Amazon, and Microsoft.`
|
2018-05-18 19:07:32 +01:00
|
|
|
},
|
|
|
|
{ name: 'keywords', content: metaKeywords.join(', ') }
|
|
|
|
]}
|
2019-02-19 01:59:12 +03:00
|
|
|
>
|
2019-10-17 12:18:54 +02:00
|
|
|
<link
|
|
|
|
as='font'
|
|
|
|
crossOrigin='anonymous'
|
|
|
|
href={latoRegularURL}
|
|
|
|
rel='preload'
|
|
|
|
type='font/woff'
|
|
|
|
/>
|
|
|
|
<link
|
|
|
|
as='font'
|
|
|
|
crossOrigin='anonymous'
|
|
|
|
href={latoLightURL}
|
|
|
|
rel='preload'
|
|
|
|
type='font/woff'
|
|
|
|
/>
|
|
|
|
<link
|
|
|
|
as='font'
|
|
|
|
crossOrigin='anonymous'
|
|
|
|
href={latoBoldURL}
|
|
|
|
rel='preload'
|
|
|
|
type='font/woff'
|
|
|
|
/>
|
|
|
|
<link
|
|
|
|
as='font'
|
|
|
|
crossOrigin='anonymous'
|
|
|
|
href={robotoRegularURL}
|
|
|
|
rel='preload'
|
|
|
|
type='font/woff'
|
|
|
|
/>
|
|
|
|
<link
|
|
|
|
as='font'
|
|
|
|
crossOrigin='anonymous'
|
|
|
|
href={robotoBoldURL}
|
|
|
|
rel='preload'
|
|
|
|
type='font/woff'
|
|
|
|
/>
|
|
|
|
<link
|
|
|
|
as='font'
|
|
|
|
crossOrigin='anonymous'
|
|
|
|
href={robotoItalicURL}
|
|
|
|
rel='preload'
|
|
|
|
type='font/woff'
|
|
|
|
/>
|
2019-01-24 20:33:13 +03:00
|
|
|
<style>{fontawesome.dom.css()}</style>
|
|
|
|
</Helmet>
|
2019-06-10 22:53:15 +03:00
|
|
|
<WithInstantSearch>
|
2019-09-19 19:45:01 +03:00
|
|
|
<Header />
|
2019-09-01 17:20:53 +03:00
|
|
|
<div className={`default-layout`}>
|
2019-02-28 15:49:23 +00:00
|
|
|
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
|
2019-06-09 05:25:55 +05:30
|
|
|
{hasMessage && flashMessage ? (
|
|
|
|
<Flash flashMessage={flashMessage} onClose={removeFlashMessage} />
|
2019-02-28 15:49:23 +00:00
|
|
|
) : null}
|
|
|
|
{children}
|
2019-05-14 17:33:24 +03:00
|
|
|
{showFooter && <Footer />}
|
2019-02-28 15:49:23 +00:00
|
|
|
</div>
|
|
|
|
</WithInstantSearch>
|
2018-05-18 19:07:32 +01:00
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2018-03-26 13:01:24 +01:00
|
|
|
|
2018-09-30 11:37:19 +01:00
|
|
|
DefaultLayout.displayName = 'DefaultLayout';
|
|
|
|
DefaultLayout.propTypes = propTypes;
|
2018-03-26 13:01:24 +01:00
|
|
|
|
2018-09-30 11:37:19 +01:00
|
|
|
export default connect(
|
|
|
|
mapStateToProps,
|
|
|
|
mapDispatchToProps
|
|
|
|
)(DefaultLayout);
|