Skip to content

Commit b570d0b

Browse files
committed
Implements #4502
1 parent b1f1d08 commit b570d0b

File tree

11 files changed

+336
-6
lines changed

11 files changed

+336
-6
lines changed

config/default.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ module.exports = {
159159
COMMUNITY_API: 'http://localhost:8000',
160160
COMMUNITY_APP_GITHUB_ISSUES: 'https://github.com/topcoder-platform/community-app/issues',
161161
EMAIL_VERIFY_URL: 'http://www.topcoder-dev.com/settings/account/changeEmail',
162-
THRIVE_POLL_FEED: 'https://www.topcoder.com/feed',
163162
},
164163

165164
/* Information about Topcoder user groups can be cached in various places.
@@ -401,4 +400,5 @@ module.exports = {
401400
TC_EDU_ARTICLES_PATH: '/articles',
402401
TC_EDU_SEARCH_PATH: '/search',
403402
TC_EDU_SEARCH_BAR_MAX_RESULTS_EACH_GROUP: 3,
403+
POLICY_PAGES_PATH: '/policy',
404404
};

src/shared/actions/contentful.js

+28
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,32 @@ async function getChallengesBlockDone(blockProps) {
295295
};
296296
}
297297

298+
/**
299+
* Policy pages fetch init
300+
*/
301+
function getPolicyPagesInit() {
302+
return {};
303+
}
304+
305+
/**
306+
* Policy pages fetch done
307+
*/
308+
async function getPolicyPagesDone() {
309+
const service = getService({
310+
preview: false,
311+
spaceName: 'default',
312+
environment: 'master',
313+
});
314+
315+
const res = await service.queryEntries({
316+
content_type: 'policyPage',
317+
});
318+
319+
return {
320+
data: [...res.items],
321+
};
322+
}
323+
298324
export default redux.createActions({
299325
CONTENTFUL: {
300326
BOOK_CONTENT: bookContent,
@@ -310,5 +336,7 @@ export default redux.createActions({
310336
GET_MENU_DONE: getMenuDone,
311337
GET_CHALLENGES_BLOCK_INIT: getChallengesBlockInit,
312338
GET_CHALLENGES_BLOCK_DONE: getChallengesBlockDone,
339+
GET_POLICY_PAGES_INIT: getPolicyPagesInit,
340+
GET_POLICY_PAGES_DONE: getPolicyPagesDone,
313341
},
314342
});

src/shared/components/Header/index.jsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ try {
1919

2020
const Header = ({
2121
profile, auth, notifications, loadNotifications, markNotificationAsRead,
22-
markAllNotificationAsRead, markAllNotificationAsSeen, dismissChallengeNotifications,
22+
markAllNotificationAsRead, markAllNotificationAsSeen, dismissChallengeNotifications, headerMenu,
2323
}) => {
2424
const [activeLevel1Id, setActiveLevel1Id] = useState();
2525
const [path, setPath] = useState();
@@ -71,7 +71,7 @@ const Header = ({
7171
return (
7272
<div>
7373
<TopNavRef
74-
menu={config.HEADER_MENU}
74+
menu={headerMenu || config.HEADER_MENU}
7575
rightMenu={(
7676
<LoginNavRef
7777
loggedIn={!_.isEmpty(profile)}
@@ -114,6 +114,7 @@ const Header = ({
114114
Header.defaultProps = {
115115
profile: null,
116116
auth: null,
117+
headerMenu: null,
117118
};
118119

119120
Header.propTypes = {
@@ -128,6 +129,7 @@ Header.propTypes = {
128129
markAllNotificationAsRead: PT.func.isRequired,
129130
markAllNotificationAsSeen: PT.func.isRequired,
130131
dismissChallengeNotifications: PT.func.isRequired,
132+
headerMenu: PT.arrayOf(PT.object),
131133
};
132134

133135
export default Header;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* PolicyPages component.
3+
*/
4+
import _ from 'lodash';
5+
import React from 'react';
6+
import PT from 'prop-types';
7+
import Sticky from 'react-stickynode';
8+
import { config, Link } from 'topcoder-react-utils';
9+
import cn from 'classnames';
10+
import ContentBlock from 'components/Contentful/ContentBlock';
11+
import Error404 from 'components/Error404';
12+
import './styles.scss';
13+
14+
const menuItems = (policyData, slug) => _.map(policyData, item => (
15+
<li key={item.slug} styleName={cn({ active: slug === item.slug })}>
16+
<Link to={`${config.POLICY_PAGES_PATH}/${item.slug}`}>{item.menuLinkText}</Link>
17+
</li>
18+
));
19+
20+
function PolicyPages({
21+
match,
22+
policyData,
23+
}) {
24+
let { slug } = match.params;
25+
const pages = policyData.Policies.concat(policyData.Legal);
26+
if (!slug) {
27+
// eslint-disable-next-line prefer-destructuring
28+
slug = pages[0].slug;
29+
}
30+
const policyPage = _.find(pages, { slug });
31+
console.log('PolicyPages', slug, policyData, policyPage, pages[0]);
32+
return (
33+
<div styleName="policy-pages-container">
34+
<Sticky top=".policy-pages-container">
35+
<div styleName="navi">
36+
<p styleName="navi-section-title">Policies</p>
37+
<ul styleName="navi-section">{menuItems(policyData.Policies, slug)}</ul>
38+
<p styleName="navi-section-title2">Legal</p>
39+
<ul styleName="navi-section">{menuItems(policyData.Legal, slug)}</ul>
40+
</div>
41+
</Sticky>
42+
<div styleName="page-content">
43+
{
44+
slug && !policyPage ? (
45+
<Error404 />
46+
) : (
47+
<ContentBlock id={policyPage.pageContent.sys.id} />
48+
)
49+
}
50+
</div>
51+
</div>
52+
);
53+
}
54+
55+
PolicyPages.defaultProps = {
56+
57+
};
58+
59+
PolicyPages.propTypes = {
60+
match: PT.shape().isRequired,
61+
policyData: PT.shape().isRequired,
62+
};
63+
64+
export default PolicyPages;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
@import '~styles/mixins';
2+
3+
.policy-pages-container {
4+
position: relative;
5+
max-width: $screen-lg;
6+
margin: 130px auto 0 auto;
7+
display: grid;
8+
grid-template-columns: 300px 1fr;
9+
10+
@include xs-to-md {
11+
display: flex;
12+
flex-direction: column;
13+
margin-top: 0;
14+
}
15+
16+
.navi {
17+
display: flex;
18+
flex-direction: column;
19+
width: 200px;
20+
margin-right: 85px;
21+
padding-left: 15px;
22+
padding-top: 15px;
23+
24+
@include xs-to-md {
25+
width: 100%;
26+
background: white;
27+
border-bottom: 1px solid #e0e0e0;
28+
}
29+
30+
.navi-section-title,
31+
.navi-section-title2 {
32+
text-transform: uppercase;
33+
color: #2a2a2a;
34+
font-family: Barlow, sans-serif;
35+
font-size: 20px;
36+
font-weight: 600;
37+
line-height: 24px;
38+
margin-bottom: 15px;
39+
40+
@include xs-to-md {
41+
margin-bottom: 5px;
42+
}
43+
}
44+
45+
.navi-section-title2 {
46+
margin-top: 42px;
47+
48+
@include xs-to-md {
49+
margin-top: 12px;
50+
}
51+
}
52+
53+
.navi-section {
54+
list-style: none;
55+
56+
li {
57+
a {
58+
color: #2a2a2a;
59+
font-family: Roboto, sans-serif;
60+
font-size: 16px;
61+
font-weight: 400;
62+
line-height: 38px;
63+
64+
&:hover {
65+
font-weight: 700;
66+
}
67+
}
68+
69+
&.active {
70+
a {
71+
font-weight: 700;
72+
}
73+
}
74+
}
75+
}
76+
}
77+
78+
.page-content {
79+
min-height: 100vh;
80+
flex: 1;
81+
}
82+
}

src/shared/containers/PolicyPages.jsx

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* Connects the Redux store to the PolicyPages component.
3+
*/
4+
import _ from 'lodash';
5+
import React from 'react';
6+
import { connect } from 'react-redux';
7+
import PT from 'prop-types';
8+
import actions from 'actions/contentful';
9+
import PolicyPages from 'components/PolicyPages';
10+
import LoadingIndicator from 'components/LoadingIndicator';
11+
import Header from 'containers/TopcoderHeader';
12+
import Footer from 'components/TopcoderFooter';
13+
14+
const HEADE_MENU = [
15+
{
16+
id: 'business',
17+
title: 'BUSINESS',
18+
href: 'https://www.topcoder.com',
19+
},
20+
{
21+
id: 'community-123',
22+
title: 'COMMUNITY',
23+
href: '/community/learn',
24+
},
25+
];
26+
27+
class PolicyPagesContainer extends React.Component {
28+
componentDidMount() {
29+
const { loadingPolicyPages, policyData, getPolicyPages } = this.props;
30+
if (!loadingPolicyPages && !policyData) {
31+
getPolicyPages();
32+
}
33+
}
34+
35+
render() {
36+
const {
37+
loadingPolicyPages, policyData,
38+
} = this.props;
39+
if (loadingPolicyPages || !policyData) return <LoadingIndicator />;
40+
if (_.isEmpty(policyData)) {
41+
return (
42+
<h1 style={{ 'text-align': 'center' }}>Please, publish Policy Pages in Contentful space to see this page.</h1>
43+
);
44+
}
45+
return (
46+
<div>
47+
<Header headerMenu={HEADE_MENU} />
48+
<PolicyPages {...this.props} />
49+
<Footer />
50+
</div>
51+
);
52+
}
53+
}
54+
55+
PolicyPagesContainer.defaultProps = {
56+
loadingPolicyPages: false,
57+
policyData: null,
58+
};
59+
60+
PolicyPagesContainer.propTypes = {
61+
loadingPolicyPages: PT.bool,
62+
match: PT.shape().isRequired,
63+
policyData: PT.shape(),
64+
getPolicyPages: PT.func.isRequired,
65+
};
66+
67+
function mapStateToProps(state, ownProps) {
68+
return {
69+
p: ownProps.p,
70+
policyData: state.policyPages.policyData,
71+
};
72+
}
73+
74+
function mapDispatchToProps(dispatch) {
75+
return {
76+
getPolicyPages: () => {
77+
dispatch(actions.contentful.getPolicyPagesInit());
78+
dispatch(actions.contentful.getPolicyPagesDone());
79+
},
80+
};
81+
}
82+
83+
export default connect(
84+
mapStateToProps,
85+
mapDispatchToProps,
86+
)(PolicyPagesContainer);

src/shared/containers/TopcoderHeader.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function mapDispatchToProps(dispatch) {
3838
};
3939
}
4040

41-
function mapStateToProps(state) {
41+
function mapStateToProps(state, ownProps) {
4242
return {
4343
...state.topcoderHeader,
4444
profile: {
@@ -51,6 +51,7 @@ function mapStateToProps(state) {
5151
auth: {
5252
...state.auth,
5353
},
54+
headerMenu: ownProps.headerMenu,
5455
};
5556
}
5657

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Reducer for state.policyPages
3+
*/
4+
import _ from 'lodash';
5+
import actions from 'actions/contentful';
6+
import { handleActions } from 'redux-actions';
7+
8+
function onGetPolicyPagesInit(state) {
9+
return {
10+
...state,
11+
loadingPolicyPages: true,
12+
};
13+
}
14+
15+
function onGetPolicyPagesDone(state, action) {
16+
const policyData = _.groupBy(action.payload.data.map(pp => pp.fields), 'menuSection');
17+
return {
18+
...state,
19+
loadingPolicyPages: false,
20+
policyData,
21+
};
22+
}
23+
24+
/**
25+
* Creates challengesBlock reducer with the specified initial state.
26+
* @param {Object} state Optional. If not given, the default one is
27+
* generated automatically.
28+
* @return {Function} Reducer.
29+
*/
30+
function create(state = {}) {
31+
return handleActions({
32+
[actions.contentful.getPolicyPagesInit]: onGetPolicyPagesInit,
33+
[actions.contentful.getPolicyPagesDone]: onGetPolicyPagesDone,
34+
}, state);
35+
}
36+
37+
/* Reducer with the default initial state. */
38+
export default create();

src/shared/reducers/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import rss from './rss';
2727
import newsletterArchive from './newsletterArchive';
2828
import menuNavigation from './contentful/menuNavigation';
2929
import challengesBlock from './contentful/challengesBlock';
30+
import policyPages from './contentful/policyPages';
3031
import { factory as challengeListingFactory } from './challenge-listing';
3132
import { factory as examplesFactory } from './examples';
3233
import { factory as pageFactory } from './page';
@@ -136,6 +137,7 @@ export function factory(req) {
136137
newsletterArchive,
137138
menuNavigation,
138139
challengesBlock,
140+
policyPages,
139141
}));
140142
}
141143

0 commit comments

Comments
 (0)