diff --git a/src/shared/components/Contentful/Route.jsx b/src/shared/components/Contentful/Route.jsx
index 18c23943f8..cce6a728f4 100644
--- a/src/shared/components/Contentful/Route.jsx
+++ b/src/shared/components/Contentful/Route.jsx
@@ -7,7 +7,7 @@ import _ from 'lodash';
import ContentfulLoader from 'containers/ContentfulLoader';
import Error404 from 'components/Error404';
import LoadingIndicator from 'components/LoadingIndicator';
-import { MetaTags } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import PT from 'prop-types';
import React from 'react';
import { Route, Switch } from 'react-router-dom';
diff --git a/src/shared/components/MetaTags.jsx b/src/shared/components/MetaTags.jsx
new file mode 100644
index 0000000000..bca2314f22
--- /dev/null
+++ b/src/shared/components/MetaTags.jsx
@@ -0,0 +1,82 @@
+/**
+ * Auxiliary wrapper around React Helmet that helps to generate meta tags for
+ * generic use cases.
+ *
+ * NOTE: This component relies on `domain` path of Redux store to hold
+ * the current app domain, which will serve as the base path for the bundled
+ * images.
+ */
+
+import PT from 'prop-types';
+import React from 'react';
+import { connect } from 'react-redux';
+import { Helmet } from 'react-helmet';
+
+function MetaTags({
+ description,
+ domain,
+ image,
+ siteName,
+ socialDescription,
+ socialTitle,
+ title,
+ url,
+}) {
+ const img = `${domain}${image}`;
+ const socTitle = socialTitle || title;
+ const socDesc = socialDescription || description;
+ return (
+
+ {/* General tags. */}
+
+ {title}
+
+
+
+ {/* Twitter cards. */}
+
+
+
+ { image ? : null }
+ {
+ siteName ? (
+
+ ) : null
+ }
+
+ {/* Open Graph data. */}
+
+ { image ? : null }
+ { image ? : null }
+
+ {
+ siteName ? () : null
+ }
+ { url ? () : null }
+
+ );
+}
+
+MetaTags.defaultProps = {
+ image: null,
+ siteName: null,
+ socialDescription: null,
+ socialTitle: null,
+ url: null,
+};
+
+MetaTags.propTypes = {
+ description: PT.string.isRequired,
+ domain: PT.string.isRequired,
+ image: PT.string,
+ siteName: PT.string,
+ socialDescription: PT.string,
+ socialTitle: PT.string,
+ title: PT.string.isRequired,
+ url: PT.string,
+};
+
+/* TODO: It is not good to depend on the domain written into redux state here,
+ * better pass it via the renderer context at the server side, and get it from
+ * the location at the frontend side, or something similar? */
+export default connect(state => ({ domain: state.domain }))(MetaTags);
diff --git a/src/shared/components/Settings/index.jsx b/src/shared/components/Settings/index.jsx
index f535d39618..9585bb8a31 100644
--- a/src/shared/components/Settings/index.jsx
+++ b/src/shared/components/Settings/index.jsx
@@ -3,7 +3,7 @@
*/
import React from 'react';
import PT from 'prop-types';
-import { MetaTags } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import { TABS } from 'actions/page/settings';
diff --git a/src/shared/containers/EDU/Home.jsx b/src/shared/containers/EDU/Home.jsx
index 30c8455a26..6ec731d03e 100644
--- a/src/shared/containers/EDU/Home.jsx
+++ b/src/shared/containers/EDU/Home.jsx
@@ -3,7 +3,7 @@
*/
import React from 'react';
import { config } from 'topcoder-react-utils';
-import { Helmet } from 'react-helmet';
+import MetaTags from 'components/MetaTags';
import Viewport from 'components/Contentful/Viewport';
import SearchBar from 'components/Contentful/SearchBar/SearchBar';
import { getService } from 'services/contentful';
@@ -44,15 +44,15 @@ export default class EDUHome extends React.Component {
render() {
const { taxonomy } = this.state;
+ const title = 'Topcoder Thrive | Topcoder Community | Topcoder';
+ const description = 'Thrive is our vault of content that we have been gathering over the years. It is full of tutorials and workshops that matter. Grow with us!';
+
return (
-
- THRIVE - Grow with us. Tutorials and workshops that matter.
-
-
-
-
-
+
{/* Banner */}
diff --git a/src/shared/containers/EDU/Search.jsx b/src/shared/containers/EDU/Search.jsx
index 6b311aa68e..b16fcfdf12 100644
--- a/src/shared/containers/EDU/Search.jsx
+++ b/src/shared/containers/EDU/Search.jsx
@@ -5,6 +5,7 @@ import _ from 'lodash';
import moment from 'moment';
import React from 'react';
import { config, isomorphy } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import Viewport from 'components/Contentful/Viewport';
import SearchBar from 'components/Contentful/SearchBar/SearchBar';
import { getService } from 'services/contentful';
@@ -13,7 +14,6 @@ import { updateQuery } from 'utils/url';
import qs from 'qs';
import LoadingIndicator from 'components/LoadingIndicator';
import SearchPageFilter from 'components/Contentful/SearchPageFilter/SearchPageFilter';
-import { Helmet } from 'react-helmet';
// Partials
import ResultTabs from './partials/ResultTabs';
// CSS
@@ -87,18 +87,28 @@ export default class EDUSearch extends React.Component {
const {
taxonomy, query, tree, isShowFilter,
} = this.state;
+ const title = 'Topcoder Thrive | Topcoder Community | Topcoder';
+ const description = 'Thrive is our vault of content that we have been gathering over the years. It is full of tutorials and workshops that matter. Grow with us!';
+
+ const metaTags = (
+
+ );
// This container needs at least those variables
// to be able to render meaningful data
- if (!taxonomy) return
;
+ if (!taxonomy) {
+ return (
+
+ { metaTags }
+ ;
+
+ );
+ }
return (
-
- THRIVE - Search {`${query.title}`}
-
-
-
-
-
+ { metaTags }
{/* Banner */}
diff --git a/src/shared/containers/EDU/Tracks.jsx b/src/shared/containers/EDU/Tracks.jsx
index d98099191e..8ede19d331 100644
--- a/src/shared/containers/EDU/Tracks.jsx
+++ b/src/shared/containers/EDU/Tracks.jsx
@@ -5,6 +5,7 @@ import _ from 'lodash';
import moment from 'moment';
import React from 'react';
import { config, isomorphy } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import Viewport from 'components/Contentful/Viewport';
import SearchBar from 'components/Contentful/SearchBar/SearchBar';
import { getService } from 'services/contentful';
@@ -14,7 +15,6 @@ import qs from 'qs';
import TracksTree from 'components/Contentful/TracksTree/TracksTree';
import LoadingIndicator from 'components/LoadingIndicator';
import TracksFilter from 'components/Contentful/TracksFilter/TracksFilter';
-import { Helmet } from 'react-helmet';
// SVGs & Assets
import Dev from 'assets/images/img-development.png';
import Design from 'assets/images/img_design.png';
@@ -163,18 +163,28 @@ export default class EDUTracks extends React.Component {
taxonomy, query, tree, isShowFilter,
articleCnt, videoCnt, forumCnt,
} = this.state;
+ const title = 'Topcoder Thrive | Topcoder Community | Topcoder';
+ const description = 'Thrive is our vault of content that we have been gathering over the years. It is full of tutorials and workshops that matter. Grow with us!';
+
+ const metaTags = (
+
+ );
// This container needs at least those variables
// to be able to render meaningful data
- if (!taxonomy) return
;
+ if (!taxonomy) {
+ return (
+
+ { metaTags }
+ ;
+
+ );
+ }
return (
-
- THRIVE - {`${query.track}`}
-
-
-
-
-
+ { metaTags }
{/* Banner */}
- Topcoder - Gig Work Opportunities
+
{
id ? (
diff --git a/src/shared/containers/Profile.jsx b/src/shared/containers/Profile.jsx
index 90a95b91f6..8c12c66334 100644
--- a/src/shared/containers/Profile.jsx
+++ b/src/shared/containers/Profile.jsx
@@ -7,7 +7,7 @@ import PT from 'prop-types';
import { connect } from 'react-redux';
import { actions } from 'topcoder-react-lib';
-import { MetaTags } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import Error404 from 'components/Error404';
import LoadingIndicator from 'components/LoadingIndicator';
import ProfilePage from 'components/ProfilePage';
diff --git a/src/shared/containers/ProfileStats.jsx b/src/shared/containers/ProfileStats.jsx
index 561ab79d9c..259e95865f 100644
--- a/src/shared/containers/ProfileStats.jsx
+++ b/src/shared/containers/ProfileStats.jsx
@@ -9,7 +9,7 @@ import Error404 from 'components/Error404';
import LoadingIndicator from 'components/LoadingIndicator';
import ProfileStatsPage from 'components/ProfilePage/Stats';
import { shouldShowGraph, isValidTrack } from 'utils/memberStats';
-import { MetaTags } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import _ from 'lodash';
import qs from 'qs';
diff --git a/src/shared/containers/challenge-detail/index.jsx b/src/shared/containers/challenge-detail/index.jsx
index d088940562..ddbb35160e 100644
--- a/src/shared/containers/challenge-detail/index.jsx
+++ b/src/shared/containers/challenge-detail/index.jsx
@@ -39,7 +39,8 @@ import {
SUBTRACKS,
CHALLENGE_STATUS,
} from 'utils/tc';
-import { config, MetaTags } from 'topcoder-react-utils';
+import { config } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import { actions } from 'topcoder-react-lib';
import { getService } from 'services/contentful';
// import {
diff --git a/src/shared/containers/challenge-listing/Listing/index.jsx b/src/shared/containers/challenge-listing/Listing/index.jsx
index ab8c52a856..77104cb8e6 100644
--- a/src/shared/containers/challenge-listing/Listing/index.jsx
+++ b/src/shared/containers/challenge-listing/Listing/index.jsx
@@ -25,7 +25,7 @@ import sidebarActions from 'actions/challenge-listing/sidebar';
import communityActions from 'actions/tc-communities';
// import SORT from 'utils/challenge-listing/sort';
import { BUCKETS, filterChanged, sortChangedBucket } from 'utils/challenge-listing/buckets';
-import { MetaTags } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import { USER_GROUP_MAXAGE } from 'config';
import { updateChallengeType } from 'utils/challenge';
diff --git a/src/shared/containers/terms-detail/index.jsx b/src/shared/containers/terms-detail/index.jsx
index af5efa5ab0..66755806ec 100644
--- a/src/shared/containers/terms-detail/index.jsx
+++ b/src/shared/containers/terms-detail/index.jsx
@@ -11,7 +11,7 @@ import _ from 'lodash';
import LoadingIndicator from 'components/LoadingIndicator';
import TermDetails from 'components/Terms/TermDetails';
import { actions } from 'topcoder-react-lib';
-import { MetaTags } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import { Modal, PrimaryButton } from 'topcoder-react-ui-kit';
import SwitchWithLabel from 'components/SwitchWithLabel';
import { themr } from 'react-css-super-themr';
diff --git a/src/shared/routes/Communities/Blockchain/Routes.jsx b/src/shared/routes/Communities/Blockchain/Routes.jsx
index 6ede568403..e25565ef27 100644
--- a/src/shared/routes/Communities/Blockchain/Routes.jsx
+++ b/src/shared/routes/Communities/Blockchain/Routes.jsx
@@ -27,7 +27,8 @@ import Settings from 'routes/Settings';
import TermsDetail from 'routes/TermsDetail';
import { Route, Switch } from 'react-router-dom';
import { ThemeProvider } from 'react-css-super-themr';
-import { config, MetaTags } from 'topcoder-react-utils';
+import { config } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import primaryButtonStyle from 'components/buttons/outline/round/open-sans/green-uppercase.scss';
import secondaryButtonStyle from 'components/buttons/outline/round/open-sans/blue-uppercase.scss';
diff --git a/src/shared/routes/Communities/Cognitive/Routes.jsx b/src/shared/routes/Communities/Cognitive/Routes.jsx
index 1e82a637f8..df8e3f2854 100644
--- a/src/shared/routes/Communities/Cognitive/Routes.jsx
+++ b/src/shared/routes/Communities/Cognitive/Routes.jsx
@@ -23,7 +23,8 @@ import socialImage from 'assets/images/communities/cognitive/social.jpg';
import TermsDetail from 'routes/TermsDetail';
import { Route, Switch } from 'react-router-dom';
-import { config, MetaTags } from 'topcoder-react-utils';
+import { config } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
export default function Cognitive({ base, member, meta }) {
return (
diff --git a/src/shared/routes/Communities/Mobile/Routes.jsx b/src/shared/routes/Communities/Mobile/Routes.jsx
index 92a94cb724..8a5697d40a 100644
--- a/src/shared/routes/Communities/Mobile/Routes.jsx
+++ b/src/shared/routes/Communities/Mobile/Routes.jsx
@@ -17,7 +17,8 @@ import ProfileStats from 'routes/ProfileStats';
import { Route, Switch } from 'react-router-dom';
import { ThemeProvider } from 'react-css-super-themr';
-import { config, MetaTags } from 'topcoder-react-utils';
+import { config } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import primaryButtonStyle from 'components/buttons/outline/round/open-sans/green-uppercase.scss';
import secondaryButtonStyle from 'components/buttons/outline/round/open-sans/blue-uppercase.scss';
diff --git a/src/shared/routes/EDUHome.jsx b/src/shared/routes/EDUHome.jsx
new file mode 100644
index 0000000000..ef5db510aa
--- /dev/null
+++ b/src/shared/routes/EDUHome.jsx
@@ -0,0 +1,26 @@
+/**
+ * The loader of EDUHome page webpack chunks.
+ */
+import path from 'path';
+import React from 'react';
+import LoadingPagePlaceholder from 'components/LoadingPagePlaceholder';
+import { AppChunk, webpack } from 'topcoder-react-utils';
+
+export default function EDUHome(props) {
+ return (
+
import(/* webpackChunkName: "eduHome/chunk" */ 'containers/EDU/Home')
+ .then(({ default: Home }) => (
+
+ ))
+ }
+ renderPlaceholder={() => }
+ renderServer={() => {
+ const p = webpack.resolveWeak('containers/EDU/Home');
+ const Home = webpack.requireWeak(path.resolve(__dirname, p));
+ return ;
+ }}
+ />
+ );
+}
diff --git a/src/shared/routes/EDUSearch.jsx b/src/shared/routes/EDUSearch.jsx
new file mode 100644
index 0000000000..be5be4dcfe
--- /dev/null
+++ b/src/shared/routes/EDUSearch.jsx
@@ -0,0 +1,26 @@
+/**
+ * The loader of EDUSearch page webpack chunks.
+ */
+import path from 'path';
+import React from 'react';
+import LoadingPagePlaceholder from 'components/LoadingPagePlaceholder';
+import { AppChunk, webpack } from 'topcoder-react-utils';
+
+export default function EDUSearch(props) {
+ return (
+ import(/* webpackChunkName: "eduSearch/chunk" */ 'containers/EDU/Search')
+ .then(({ default: Search }) => (
+
+ ))
+ }
+ renderPlaceholder={() => }
+ renderServer={() => {
+ const p = webpack.resolveWeak('containers/EDU/Search');
+ const Search = webpack.requireWeak(path.resolve(__dirname, p));
+ return ;
+ }}
+ />
+ );
+}
diff --git a/src/shared/routes/EDUTracks.jsx b/src/shared/routes/EDUTracks.jsx
new file mode 100644
index 0000000000..2b0c3565d0
--- /dev/null
+++ b/src/shared/routes/EDUTracks.jsx
@@ -0,0 +1,26 @@
+/**
+ * The loader of EDUTracks page webpack chunks.
+ */
+import path from 'path';
+import React from 'react';
+import LoadingPagePlaceholder from 'components/LoadingPagePlaceholder';
+import { AppChunk, webpack } from 'topcoder-react-utils';
+
+export default function EDUTracks(props) {
+ return (
+ import(/* webpackChunkName: "eduTracks/chunk" */ 'containers/EDU/Tracks')
+ .then(({ default: Tracks }) => (
+
+ ))
+ }
+ renderPlaceholder={() => }
+ renderServer={() => {
+ const p = webpack.resolveWeak('containers/EDU/Tracks');
+ const Tracks = webpack.requireWeak(path.resolve(__dirname, p));
+ return ;
+ }}
+ />
+ );
+}
diff --git a/src/shared/routes/GigsPages.jsx b/src/shared/routes/GigsPages.jsx
index dca924c079..b822b9ab69 100644
--- a/src/shared/routes/GigsPages.jsx
+++ b/src/shared/routes/GigsPages.jsx
@@ -1,21 +1,26 @@
/**
* The loader of Gigs page webpack chunks.
*/
+import path from 'path';
import React from 'react';
-
import LoadingPagePlaceholder from 'components/LoadingPagePlaceholder';
-import { AppChunk } from 'topcoder-react-utils';
+import { AppChunk, webpack } from 'topcoder-react-utils';
export default function GigsPagesRoute(props) {
return (
import(/* webpackChunkName: "gigsPages/chunk" */ 'containers/GigsPages')
.then(({ default: GigsPagesContainer }) => (
))
}
renderPlaceholder={() => }
+ renderServer={() => {
+ const p = webpack.resolveWeak('containers/GigsPages');
+ const GigsPagesContainer = webpack.requireWeak(path.resolve(__dirname, p));
+ return ;
+ }}
/>
);
}
diff --git a/src/shared/routes/Topcoder/Routes.jsx b/src/shared/routes/Topcoder/Routes.jsx
index 91904b8d53..2c8cd97267 100644
--- a/src/shared/routes/Topcoder/Routes.jsx
+++ b/src/shared/routes/Topcoder/Routes.jsx
@@ -22,9 +22,9 @@ import ContentfulLoader from 'containers/ContentfulLoader';
import LoadingIndicator from 'components/LoadingIndicator';
import Article from 'components/Contentful/Article';
-import EDUHome from 'containers/EDU/Home';
-import EDUTracks from 'containers/EDU/Tracks';
-import EDUSearch from 'containers/EDU/Search';
+import EDUHome from '../EDUHome';
+import EDUTracks from '../EDUTracks';
+import EDUSearch from '../EDUSearch';
import ChallengeListing from './ChallengeListing';
import Dashboard from './Dashboard';
import Notifications from './Notifications';
diff --git a/src/shared/routes/index.jsx b/src/shared/routes/index.jsx
index a69a461bf4..385172a9bc 100644
--- a/src/shared/routes/index.jsx
+++ b/src/shared/routes/index.jsx
@@ -11,7 +11,8 @@ import React from 'react';
import {
Switch, Route, withRouter, Redirect,
} from 'react-router-dom';
-import { MetaTags, config } from 'topcoder-react-utils';
+import { config } from 'topcoder-react-utils';
+import MetaTags from 'components/MetaTags';
import PT from 'prop-types';