diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000000..4288f8fae6
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,24 @@
+// Babel config for NodeJS (server-side). Frontend Babel configuration is embed
+// inside Webpack config.
+{
+ "presets": ["env", "react", "stage-2"],
+ "plugins": [
+ ["css-modules-transform", {
+ "extensions": [".css", ".scss"],
+ "generateScopedName": "[path]___[name]__[local]___[hash:base64:5]"
+ }],
+ "inline-react-svg",
+ ["module-resolver", {
+ "extensions": [".js", ".jsx"],
+ "root": [
+ "./src/shared",
+ "./src"
+ ]
+ }],
+ ["react-css-modules", {
+ "filetypes": {
+ ".scss": "postcss-scss"
+ }
+ }]
+ ]
+}
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000..8867467b30
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,3 @@
+__coverage__/
+node_modules/
+.git/
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000000..42dd1b1464
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,3 @@
+__coverage__
+build
+node_modules
\ No newline at end of file
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000000..700bcd665c
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,8 @@
+{
+ "extends": "airbnb",
+ "settings": {
+ "import/resolver": {
+ "babel-module": {}
+ }
+ }
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 5148e527a7..22522a81b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ pids
lib-cov
# Coverage directory used by tools like istanbul
+__coverage__
coverage
# nyc test coverage
@@ -24,7 +25,7 @@ coverage
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
-build/Release
+build
# Dependency directories
node_modules
@@ -35,3 +36,14 @@ jspm_packages
# Optional REPL history
.node_repl_history
+
+# Elastic Beanstalk Files
+.elasticbeanstalk/*
+!.elasticbeanstalk/*.cfg.yml
+!.elasticbeanstalk/*.global.yml
+
+# macOS system files
+*.DS_Store
+
+# Misc files
+.vscode
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000000..564952cbde
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+v6.10.2
diff --git a/.stylelintrc b/.stylelintrc
new file mode 100644
index 0000000000..ef22505871
--- /dev/null
+++ b/.stylelintrc
@@ -0,0 +1,8 @@
+{
+ "extends": "stylelint-config-standard",
+ "rules": {
+ "selector-pseudo-class-no-unknown": [true, {
+ "ignorePseudoClasses": ["global"]
+ }]
+ }
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000..5581f1c6ea
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+FROM node:6.10.2
+LABEL version="1.0"
+LABEL description="Community App"
+
+# Create app directory
+RUN mkdir -p /opt/app
+ADD package.json /opt/app/package.json
+WORKDIR /opt/app
+RUN npm install
+
+ADD . /opt/app
+
+ARG BUILD_ENV=prod
+ENV NODE_ENV=$BUILD_ENV
+RUN npm run build
+
+EXPOSE 3000
+
+CMD ["npm", "start"]
diff --git a/README.md b/README.md
index 5d0f73dd65..75b91af5fe 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,103 @@
-# community-app
-React webapp for serving Topcoder Community
+![Dev Build Status](https://img.shields.io/circleci/project/github/topcoder-platform/community-app/develop.svg?label=develop)
+
+# Topcoder Community App
+New version of Topcoder Community website.
+
+### Deployment and Execution
+
+*Disclaimer:* Current instructions are biased towards Ubuntu 16.04. Hovewer, similar recipies should work for other OS. Should you encounter and overcome any tricky issues on other OS, you are welcome to add notes/hints into this file.
+
+1. You should have NodeJS 6.10.0 (other recent versions should also work fine);
+
+2. Install dependencies with one of the following commands:
+ - `$ npm install` Installs all dependencies. Recommended for local development;
+ - `$ npm install --production` Installs only production dependencies. These include all you need to run linters & unit tests, to build & run production version of the App. Does not include additional development tools.
+
+3. Run linters and unit tests with following commands:
+ - `$ npm run lint:js` Runs ESLint (AirBnB style);
+ - `$ npm run lint:scss` Runs Stylelint (standard Stylelint style);
+ - `$ npm run lint` Runs both ESLint and Stylelint;
+ - `$ npm run jest` Runs unit tests;
+ - `$ npm run jest -- -u` Runs unit test with update of component snapshots;
+ - `$ npm test` Runs ESLint, Stylelint and unit tests.
+
+4. Set environment variables:
+ - `PORT` Specifies the port to run the App at. Defaults to 3000;
+ - `NODE_ENV` Specifies Topcoder backend to use. Should be either `development` either `production`. Defaults to `production`.
+
+5. To rebuild the App's frontend (initially, it is automatically build as a part of the install step) run one of (the result of build will be output into `/build` folder in both cases):
+ - `$ npm run build` To rebuild production frontend;
+ - `$ npm run build:dev` This command should only be used to test whether development build of the front end works. You don't have to execute this command to run development version of the App (the server will automatically build frontend in memory anyway). You can't successfully execute this command without installing dev dependencies.
+
+6. To run the App use:
+ - `$ npm start` To run the App in normal mode. The frontend will be served from `/build` folder. The Topcoder backend to use will be chosen depending on `NODE_ENV` value;
+ - `$ npm run dev` To run the App with development tools. In this case the frontend is build in memory by server and uses dev tools like redux-devtools. The Topcoder backend to use will be chosen depending on `NODE_ENV` value. This demands dev dependencies installed at the firts step.
+
+If you run the App locally against development Topcoder backend you should access the App as `local.topcoder-dev.com:3000`. Prior doing this you should add into your `/etc/hosts` the line `127.0.0.1 local.topcoder-dev.com:3000`. To login into development Topcoder backend use `accounts.topcoder-dev.com/members` to login. Log out at `www.topcoder-dev.com`, or just wipe out auth cookies.
+
+If you run the App locally against production Topcoder backend you should run it at the port 80 and access the App as `local.topcoder.com`. Prior doing this you should add into your `/etc/hosts` the line `127.0.0.1 local.topcoder.com`. The easiest way to allow the App to listen at the port 80 on Ubuntu 16.04 is (no guarantees, how safe is it):
+- `$ sudo apt install libcap2-bin`;
+- `$ which node` to figure out your `path/to/node`;
+- `$ sudo setcap cap_net_bind_service=+ep /path/to/node`;
+- Now you can run the App.
+To login into production Topcoder backend use `accounts.topcoder.com/members` with your regular account, and to logout you can just wipe out cookies, or just log out at `www.topcoder.com`.
+
+Development dependencies include StyleFMT. You can execute `$ npm run fix:styles` to automatically correct you stylesheets to comply with Stylelint rules (but it can fail for some rules).
+To automatically correct js files, you can use `npm run fix:js`.
+
+### Development Notes
+- [Challenge Listing - Notes from winning submission](docs/challenge-listing-notes.md)
+- [Leaderboard - Notes from the winning submission](docs/leaderboard-notes.md)
+- [Wipro Community - Notes from the preliminary winning submission](docs/wipro-community.md)
+- [Why Reducer Factories and How to Use Them?](docs/why-reducer-factories-and-how-to-use-them.md)
+- [~~WYSIWYG Page Editor - Notes from the winning submission~~](docs/editor-notes.pdf)
+
+### Current Status
+
+*Note:* Server-side rendering is supported. It means, if you go to `/src/server/App.jsx` and remove the line `<_script type="application/javascript" src="/bundle.js">`, which loads JS bundle in the page, when you start the App and load any page, you'll still see a properly rendered page (without any interactivity). It means that loading of JS bundle and initialization of ReactJS do not block the proper rendering of the page.
+
+*Setup of this App is not finished yet. Here is a brief summary of current configuration and problems found on the way.*
+
+This App already contains:
+- A high-level draft of isomorphic App structure;
+- A dummy client App;
+- A set of general Topcoder stylesheets in `/src/styles`;
+- Autoprefixer;
+- Babel with latest JS support both client- and server-side;
+- ESLint (AirBnB style);
+- Express server;
+- Font loading (Roboto fonts are included into the repo);
+- Hot Module Replacement for JS code and SCSS styles in dev environment;
+- Isomorphic fetch and Topcoder API Auth;
+- Loading of .svg assets as ReactJS components with babel-plugin-inline-react-svg
+- Node-Config;
+- React;
+- React CSS Modules (via Babel plugin);
+- [react-css-themr](https://github.com/javivelasco/react-css-themr);
+- React Router;
+- Redux with Flux Standard Actions, redux-promise middleware, support of server-side rendering, and DevTools for dev environment;
+- SCSS support;
+- CSS support for third party modules;
+- StyleFMT;
+- Stylelint for scss (standard Stylelint style);
+- Unit testing with Jest;
+- Various examples;
+- Webpack;
+
+Pending low-priority stuff (these are important, but can be added along the way):
+- Webpack Dashboard (https://github.com/FormidableLabs/webpack-dashboard);
+
+### CI / CD
+Deploy scripts are setup to use AWS ECS + CircleCI. Make sure the following environment variables are setup in CircleCI:
+* AWS_ECS_SERVICE
+* AWS_REPOSITORY
+* DEV_AWS_ACCESS_KEY_ID
+* DEV_AWS_ACCOUNT_ID
+* DEV_AWS_ECS_CLUSTER
+* DEV_AWS_REGION
+* DEV_AWS_SECRET_ACCESS_KEY
+* PROD_AWS_ACCESS_KEY_ID
+* PROD_AWS_ACCOUNT_ID
+* PROD_AWS_ECS_CLUSTER
+* PROD_AWS_REGION
+* PROD_AWS_SECRET_ACCESS_KEY
diff --git a/__mocks__/redux-devtools-dock-monitor.js b/__mocks__/redux-devtools-dock-monitor.js
new file mode 100644
index 0000000000..d5be7b8fd9
--- /dev/null
+++ b/__mocks__/redux-devtools-dock-monitor.js
@@ -0,0 +1,9 @@
+/**
+ * Mock redux-devtools-dock-monitor module.
+ * Allows to test development-only code depending on that module even
+ * in production environment, where that module is not installed.
+ */
+
+export default function ReduxDevtoolsDockMonitor() {
+ return null;
+}
diff --git a/__mocks__/redux-devtools-log-monitor.js b/__mocks__/redux-devtools-log-monitor.js
new file mode 100644
index 0000000000..569b991ea8
--- /dev/null
+++ b/__mocks__/redux-devtools-log-monitor.js
@@ -0,0 +1,3 @@
+export default function ReduxDevtoolsLogMonitor() {
+ return null;
+}
diff --git a/__mocks__/redux-devtools.js b/__mocks__/redux-devtools.js
new file mode 100644
index 0000000000..6f34af2801
--- /dev/null
+++ b/__mocks__/redux-devtools.js
@@ -0,0 +1,9 @@
+import _ from 'lodash';
+
+export function createDevTools(obj) {
+ const res = () => obj;
+ res.instrument = _.noop;
+ return res;
+}
+
+export default undefined;
diff --git a/__mocks__/webpack-dev-middleware.js b/__mocks__/webpack-dev-middleware.js
new file mode 100644
index 0000000000..24f433453c
--- /dev/null
+++ b/__mocks__/webpack-dev-middleware.js
@@ -0,0 +1,5 @@
+function webpackDevMiddleware(req, res, next) {
+ if (next) next();
+}
+
+module.exports = () => webpackDevMiddleware;
diff --git a/__mocks__/webpack-hot-middleware.js b/__mocks__/webpack-hot-middleware.js
new file mode 100644
index 0000000000..4cd128cbce
--- /dev/null
+++ b/__mocks__/webpack-hot-middleware.js
@@ -0,0 +1,5 @@
+function webpackHotMiddleware(req, res, next) {
+ if (next) next();
+}
+
+module.exports = () => webpackHotMiddleware;
diff --git a/__mocks__/webpack.js b/__mocks__/webpack.js
new file mode 100644
index 0000000000..abcd7b5885
--- /dev/null
+++ b/__mocks__/webpack.js
@@ -0,0 +1,3 @@
+import _ from 'lodash';
+
+module.exports = _.noop;
diff --git a/__tests__/.eslintrc b/__tests__/.eslintrc
new file mode 100644
index 0000000000..44fa9f88a8
--- /dev/null
+++ b/__tests__/.eslintrc
@@ -0,0 +1,12 @@
+{
+ "env": {
+ "jest/globals": true
+ },
+ "plugins": [
+ "jest"
+ ],
+ "rules": {
+ "global-require": 0,
+ "import/no-dynamic-require": 0
+ }
+}
diff --git a/__tests__/client/__snapshots__/index.jsx.snap b/__tests__/client/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..a4db6dff35
--- /dev/null
+++ b/__tests__/client/__snapshots__/index.jsx.snap
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Properly starts with process.env.FRONT_ENV evaluating true Renders proper code (matching snapshot) 1`] = `
+
+ Redux with Flex Standard Actions, redux-promise middleware, and a custom pattern of server-side data fetching;
+
+
+ SCSS styles;
+
+
+ Topcoder API v2 and v3 service (see
+
+ /src/shared/services/api.js
+
+ ), with support of TC authentication (look for auth tokens either in
+
+ store.auth
+
+ of Redux store, or in
+
+ v3jwt
+
+ and
+
+ tcjwt
+
+ cookies of the front-end requests to the server);
+
+
+ Stylefmt;
+
+
+ Stylelint for SCSS (standard Stylelint style, run with
+
+ $ npm run lint:scss
+
+ ;
+
+
+ Webpack;
+
+
+
+ New Topcoder Pages
+
+
+
+
+ Submission Management Page
+
+ – New submission management page, is available at the endpoint
+
+ /challenge/:challengeId/my-submissions
+
+ . The link here leads to the test challenge.
+
+
+
+ Community Challenge Listing Page
+
+ – An example of community challenge list apge which shows only challenges with special criteria. In this case only challenges which has JavaScript technology tag.
+
+
+
+ Leaderboard
+
+ – Leaderboard page.
+
+
+
+ Community header example
+
+ – An example of cummunity header with default style. Also, there are examples of
+
+ custom red theme
+
+ ,
+
+ custom green theme
+
+ and
+
+ non-existent community page
+
+ .
+
+
+
+ Wipro Community Homepage
+
+ – Example of community implementation. This community has three more pages:
+
+ Learning & Certification
+
+ ,
+
+ Challenges
+
+ and
+
+ Leaderboard
+
+ . There are also examples of
+
+ non-existent community page
+
+ and
+
+ non-existent community
+
+ .
+
+
+
+ Wipro 2 Community Homepage
+
+ – Example of community implementation with new design. This community has three more pages:
+
+ Learn
+
+ ,
+
+ Challenges
+
+ and
+
+ Leaderboard
+
+ . There is also an example of
+
+ non-existent community page
+
+ .
+
+
+
+ Misc Examples
+
+
+
+
+ CSS Modules
+
+ - Demo/test of CSS modules in action;
+
+
+
+ Data Fetch
+
+ - Demonstrates how data fetching should be implemented in isomorphic way, using Redux with Flux Standard Actions and promise;
+
+
+
+ Fonts Test
+
+ - A simple showcase of the fonts included into this repo, and the test of their proper inclusion into the bundle;
+
+
+
+ SVG Loading
+
+ - Shows how to load
+
+ .svg
+
+ assets with use of
+
+ babel-plugin-inline-react-svg
+
+ .
+
+ This is a simple example of how to fetch data from a remote API into the Redux store, using actions and reducers. Two pages available via the links below are implemented with the same code / components, the only difference is that on one route the data are fetched at server side and injected into the page during server-side rendering, thus enhancing page loading time and hence the user experience. At the other route the data are not fetched at the server, thus the code falls back to fetching them at the client-side. Which works fine, but slower.
+
+ This is a simple showcase of the fonts included into this repo, and a test of their proper packing into the bundle.
+
+
+
+ Roboto Thin
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas semper consectetur dui, nec scelerisque lectus hendrerit a. Curabitur eget imperdiet orci. Sed non tincidunt turpis, laoreet fringilla nisl. Suspendisse tincidunt ligula arcu, nec hendrerit erat ultricies et. Nam elit nisl, pharetra in leo a, posuere facilisis lacus. Vivamus mollis est ac justo pulvinar iaculis sed ac ex. Cras et maximus enim, eget posuere ante. Cras in viverra quam. Sed lacinia rutrum semper. Praesent mollis turpis elit, vel feugiat nisl sollicitudin nec. Cras ex quam, facilisis eu placerat ac, vehicula a nulla. Vivamus et tellus in est hendrerit rhoncus et a leo. Donec dui lorem, laoreet nec malesuada sed, dictum ut elit. Nullam pretium augue vel odio ultrices, ut commodo lacus imperdiet. Integer maximus imperdiet odio, non dignissim lorem sollicitudin nec. Sed sed cursus metus.
+
+
+
+ Roboto Thin Italic
+
+ Praesent quam arcu, ultricies et dolor sed, interdum gravida nibh. Proin scelerisque porttitor nibh, nec finibus nibh interdum sit amet. Duis luctus sapien nec velit sollicitudin convallis. Ut eget neque vel nibh lacinia commodo. In ut lorem id quam molestie blandit. Integer in nunc cursus, suscipit sem id, accumsan mi. Sed luctus, quam sit amet fringilla feugiat, nisl lacus pretium nunc, et elementum est odio et risus. Praesent quis cursus urna. Ut orci elit, rutrum id accumsan luctus, cursus sed dui. Phasellus lorem urna, mattis et mauris sit amet, tristique hendrerit sapien.
+
+
+
+ Roboto Light
+
+ Sed nec dolor blandit, commodo arcu in, tincidunt nulla. In in odio id arcu luctus aliquet non in mi. Ut efficitur, lorem eget mollis tempus, ligula purus varius massa, a malesuada diam urna placerat leo. Sed quis diam ullamcorper, fringilla augue sed, hendrerit felis. Sed eget felis ac nulla feugiat gravida. Donec a sem lobortis, pulvinar nibh eu, convallis magna. Suspendisse tempus tincidunt dolor, id blandit est lacinia eu. Nam fermentum, sapien at dictum consectetur, felis neque mollis libero, at auctor nunc nunc eu tellus. Maecenas ultrices at neque eget tincidunt. Nullam vel consequat nunc, eget efficitur quam. Nam nec elit vitae metus cursus eleifend semper aliquet diam.
+
+
+
+ Roboto Light Italic
+
+ Nulla suscipit dui et placerat vulputate. Nunc et tempus neque, eget elementum elit. Integer vitae dignissim tellus, et venenatis nulla. Vivamus non lacus et ipsum imperdiet interdum tempus ullamcorper leo. Phasellus tempus magna imperdiet sagittis viverra. Curabitur varius elementum auctor. Nullam quam nisl, vestibulum et magna pharetra, vestibulum placerat leo. Aliquam faucibus maximus urna, sed mattis ex pretium in. Nam eu enim vitae massa vestibulum iaculis. Quisque nec risus varius, eleifend urna non, ornare est. Curabitur gravida tempus eros, posuere pellentesque magna euismod sed. Donec sed justo ut dolor accumsan gravida vitae nec neque. Proin ac tellus dui. Integer ac euismod massa.
+
+
+
+ Roboto Regular
+
+ Morbi a urna maximus, imperdiet ante id, rutrum sem. Nunc fermentum ante sodales convallis placerat. Donec eleifend, metus eget congue semper, lorem nibh vehicula velit, eu sollicitudin mi orci eget purus. Pellentesque accumsan fermentum arcu et hendrerit. Donec non porta purus. Vivamus eu venenatis sapien. Nullam et mi at eros finibus ultrices eget sit amet est. Curabitur non diam ornare est dapibus tempor a ut turpis. Nulla ut nibh metus. Vivamus hendrerit turpis nisl, eget fermentum nulla egestas quis.
+
+
+
+ Roboto Regular Italic
+
+ Nam vel ligula in ipsum condimentum sodales. Praesent id lorem tortor. In vel condimentum leo, nec rhoncus elit. Sed accumsan metus vitae diam ultricies, eu vestibulum metus pretium. Nullam congue, purus a tempor venenatis, leo dui blandit nibh, nec fermentum ante eros ac ipsum. Maecenas id neque ligula. Ut vitae faucibus lectus, vel tempus ipsum. Donec ut erat lobortis, vestibulum enim vel, scelerisque turpis. Aliquam ornare velit at elementum euismod.
+
+
+
+ Roboto Medium
+
+ Ut laoreet rhoncus vulputate. Quisque elementum quam justo, ac eleifend mauris viverra eget. Nunc sit amet commodo est. Nullam scelerisque elit ac porttitor finibus. Sed laoreet urna non enim molestie, iaculis suscipit felis commodo. Vestibulum gravida ante porttitor urna hendrerit, quis dapibus sem viverra. Praesent consectetur risus ac finibus varius. Ut commodo felis vel laoreet ornare. Donec imperdiet sagittis efficitur. Nunc in dui id ligula blandit vehicula a id leo. In pulvinar felis eget tortor pretium pulvinar. Integer a mi a justo sagittis finibus ac eget nisl. In eu dictum lectus, eu accumsan purus.
+
+
+
+ Roboto Medium Italic
+
+ Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum eget eros malesuada lacus porta scelerisque. Integer in dolor et metus dictum faucibus sit amet a mauris. Vivamus eget volutpat nulla, non posuere sapien. Vivamus mattis vehicula justo eu faucibus. Nunc eleifend mollis ultricies. Integer elementum ipsum eu nisi sodales, eget ornare quam posuere. Maecenas sit amet sem mattis, porttitor neque ut, molestie velit.
+
+
+
+ Roboto Bold
+
+ Nulla quis cursus orci. Mauris metus enim, volutpat id diam ac, fermentum dapibus augue. Donec mi elit, volutpat eget rutrum non, lobortis ac enim. In tempus iaculis turpis, vitae facilisis quam vehicula eget. Ut blandit, elit at porta vulputate, orci ipsum fermentum nunc, non dignissim lectus metus a velit. In hac habitasse platea dictumst. Mauris tincidunt, sem quis interdum ullamcorper, erat velit interdum lacus, eu tincidunt eros lacus vitae libero.
+
+
+
+ Roboto Bold Italic
+
+ Donec luctus ligula id augue blandit porta. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras sem eros, iaculis pulvinar gravida vitae, interdum eu lacus. Praesent rutrum sem a dolor viverra aliquet. Vestibulum dictum tempus fringilla. Pellentesque eu eros elit. Integer fringilla ipsum sed hendrerit rhoncus.
+
+
+
+ Roboto Black
+
+ In varius nibh elit. Nam nec pretium erat. Duis euismod mi vel massa scelerisque, ut tincidunt urna viverra. Praesent vel libero eros. Etiam a accumsan nulla. Nulla consequat venenatis risus quis accumsan. Etiam placerat pretium faucibus. Proin consequat in ante hendrerit lobortis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut eget est tempus, aliquam enim quis, pellentesque erat. Nulla dapibus diam interdum vehicula dignissim. Donec orci velit, varius sed nisi semper, lobortis bibendum neque.
+
+
+
+ Roboto Black Italic
+
+ Proin felis velit, suscipit sit amet consequat id, consectetur et lectus. Donec porttitor sollicitudin lorem sed laoreet. Fusce rhoncus mi id nulla cursus mollis. Sed scelerisque et sem id eleifend. Maecenas quis nisi non diam tempor mattis at ut tortor. Ut auctor est odio, id scelerisque massa facilisis in. Suspendisse sollicitudin rutrum porta. Sed at purus eget lacus finibus sagittis. Sed nulla ligula, sagittis quis ipsum vel, finibus feugiat eros. Fusce non enim a lectus imperdiet auctor. Aliquam mattis molestie ante vel dignissim. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
+
+ Nam dapibus nisl vitae elit fringilla rutrum. Aenean sollicitudin, erat a elementum rutrum, neque sem pretium metus, quis mollis nisl nunc et massa. Vestibulum sed metus in lorem tristique ullamcorper id vitae erat. Nulla mollis sapien sollicitudin lacinia lacinia. Vivamus facilisis dolor et massa placerat, at vestibulum nisl egestas. Nullam rhoncus lacus non odio luctus, eu condimentum mauris ultrices. Praesent blandit, augue a posuere aliquam, arcu tortor feugiat turpis,
+
+
+ Nam dapibus nisl vitae elit fringilla rutrum. Aenean sollicitudin, erat a elementum rutrum, neque sem pretium metus, quis mollis nisl nunc et massa. Vestibulum sed metus in lorem tristique ullamcorper id vitae erat. Nulla mollis sapien sollicitudin lacinia lacinia. Vivamus facilisis dolor et massa placerat, at vestibulum nisl egestas. Nullam rhoncus lacus non odio luctus, eu condimentum mauris ultrices. Praesent blandit, augue a posuere aliquam, arcu tortor feugiat turpis,
+
+
+
+ Join Now
+
+
+
+
+
+
+
+ Nam dapibus nisl vitae elit fringilla rutrum. Aenean sollicitudin, erat a elementum rutrum, neque sem pretium metus, quis mollis nisl nunc et massa. Vestibulum sed metus in lorem tristique ullamcorper id vitae erat. Nulla mollis sapien sollicitudin lacinia lacinia. Vivamus facilisis dolor et massa placerat, at vestibulum nisl egestas. Nullam rhoncus lacus non odio luctus, eu condimentum mauris ultrices. Praesent blandit, augue a posuere aliquam, arcu tortor feugiat turpis,
+
+
+
+
+
+
+ Nam dapibus nisl vitae elit fringilla rutrum. Aenean sollicitudin, erat a elementum rutrum, neque sem pretium metus, quis mollis nisl nunc et massa. Vestibulum sed metus in lorem tristique ullamcorper id vitae erat. Nulla mollis sapien sollicitudin lacinia lacinia. Vivamus facilisis dolor et massa placerat, at vestibulum nisl egestas. Nullam rhoncus lacus non odio luctus, eu condimentum mauris ultrices. Praesent blandit, augue a posuere aliquam, arcu tortor feugiat turpis,
+
+
+ Nam dapibus nisl vitae elit fringilla rutrum. Aenean sollicitudin, erat a elementum rutrum, neque sem pretium metus, quis mollis nisl nunc et massa. Vestibulum sed metus in lorem tristique ullamcorper id vitae erat. Nulla mollis sapien sollicitudin lacinia lacinia. Vivamus facilisis dolor et massa placerat, at vestibulum nisl egestas. Nullam rhoncus lacus non odio luctus, eu condimentum mauris ultrices. Praesent blandit, augue a posuere aliquam, arcu tortor feugiat turpis,
+
+
+
+ Join Now
+
+
+
+
+
+
+
+ Nam dapibus nisl vitae elit fringilla rutrum. Aenean sollicitudin, erat a elementum rutrum, neque sem pretium metus, quis mollis nisl nunc et massa. Vestibulum sed metus in lorem tristique ullamcorper id vitae erat. Nulla mollis sapien sollicitudin lacinia lacinia. Vivamus facilisis dolor et massa placerat, at vestibulum nisl egestas. Nullam rhoncus lacus non odio luctus, eu condimentum mauris ultrices. Praesent blandit, augue a posuere aliquam, arcu tortor feugiat turpis,
+
+
+
+
+
+
+ Nam dapibus nisl vitae elit fringilla rutrum. Aenean sollicitudin, erat a elementum rutrum, neque sem pretium metus, quis mollis nisl nunc et massa. Vestibulum sed metus in lorem tristique ullamcorper id vitae erat. Nulla mollis sapien sollicitudin lacinia lacinia. Vivamus facilisis dolor et massa placerat, at vestibulum nisl egestas. Nullam rhoncus lacus non odio luctus, eu condimentum mauris ultrices. Praesent blandit, augue a posuere aliquam, arcu tortor feugiat turpis,
+
+
+ Nam dapibus nisl vitae elit fringilla rutrum. Aenean sollicitudin, erat a elementum rutrum, neque sem pretium metus, quis mollis nisl nunc et massa. Vestibulum sed metus in lorem tristique ullamcorper id vitae erat. Nulla mollis sapien sollicitudin lacinia lacinia. Vivamus facilisis dolor et massa placerat, at vestibulum nisl egestas. Nullam rhoncus lacus non odio luctus, eu condimentum mauris ultrices. Praesent blandit, augue a posuere aliquam, arcu tortor feugiat turpis,
+
+
+
+ Join Now
+
+
+
+
+
+
+
+ Nam dapibus nisl vitae elit fringilla rutrum. Aenean sollicitudin, erat a elementum rutrum, neque sem pretium metus, quis mollis nisl nunc et massa. Vestibulum sed metus in lorem tristique ullamcorper id vitae erat. Nulla mollis sapien sollicitudin lacinia lacinia. Vivamus facilisis dolor et massa placerat, at vestibulum nisl egestas. Nullam rhoncus lacus non odio luctus, eu condimentum mauris ultrices. Praesent blandit, augue a posuere aliquam, arcu tortor feugiat turpis,
+
+
+
+
+
+
+ Nam dapibus nisl vitae elit fringilla rutrum. Aenean sollicitudin, erat a elementum rutrum, neque sem pretium metus, quis mollis nisl nunc et massa. Vestibulum sed metus in lorem tristique ullamcorper id vitae erat. Nulla mollis sapien sollicitudin lacinia lacinia. Vivamus facilisis dolor et massa placerat, at vestibulum nisl egestas. Nullam rhoncus lacus non odio luctus, eu condimentum mauris ultrices. Praesent blandit, augue a posuere aliquam, arcu tortor feugiat turpis,
+
+`;
diff --git a/__tests__/shared/containers/tc-communities/Page.jsx b/__tests__/shared/containers/tc-communities/Page.jsx
new file mode 100644
index 0000000000..cbcdf23e00
--- /dev/null
+++ b/__tests__/shared/containers/tc-communities/Page.jsx
@@ -0,0 +1,204 @@
+import _ from 'lodash';
+import React from 'react';
+import Rnd from 'react-test-renderer/shallow';
+import TU from 'react-dom/test-utils';
+import { MemoryRouter } from 'react-router-dom';
+
+const rnd = new Rnd();
+
+const mockMetaActions = {
+ tcCommunities: {
+ meta: {
+ fetchDataInit: jest.fn(),
+ fetchDataDone: jest.fn(),
+ mobileToggle: jest.fn(),
+ },
+ },
+};
+jest.setMock(require.resolve('actions/tc-communities/meta'), mockMetaActions);
+
+const mockState = {
+ tcCommunities: {
+ meta: {
+ authorizedGroupIds: ['12345'],
+ communityId: 'someId',
+ pageId: 'somePageId',
+ loading: false,
+ menuItems: [
+ { title: 'Menu Item 1', url: 'pageId1' },
+ { title: 'Menu Item 2', url: 'pageId2' },
+ { title: 'Menu Item 3', url: 'pageId3' },
+ ],
+ logos: ['some/logo/url'],
+ cssUrl: 'some/css/url',
+ isMobileOpen: false,
+ failed: false,
+ },
+ },
+ auth: {
+ profile: {
+ groups: [{ id: '12345' }],
+ },
+ },
+};
+
+const mockState2 = {
+ tcCommunities: {
+ meta: {},
+ },
+ auth: {
+ profile: {},
+ },
+};
+
+const mockState3 = {
+ tcCommunities: {
+ meta: {
+ communityId: 'anotherId',
+ pageId: 'somePageId',
+ },
+ },
+ auth: {
+ profile: {},
+ },
+};
+
+const mockState4 = {
+ tcCommunities: {
+ meta: {
+ communityId: 'someId',
+ pageId: 'somePageId',
+ isMobileOpen: true,
+ },
+ },
+ auth: {
+ profile: {
+ groups: [],
+ },
+ },
+};
+
+const Page = require('containers/tc-communities/Page').default;
+
+beforeEach(() => jest.clearAllMocks());
+
+test('Matches shapshot', () => {
+ rnd.render((
+ _.noop,
+ getState: () => mockState,
+ subscribe: _.noop,
+ }}
+ history={{}}
+ location={{}}
+ />
+ ));
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+});
+
+test('Triggers data loading if not loaded yet', () => {
+ TU.renderIntoDocument((
+
+ _.noop,
+ getState: () => mockState2,
+ subscribe: _.noop,
+ }}
+ history={{}}
+ location={{}}
+ />
+
+ ));
+ expect(mockMetaActions.tcCommunities.meta.fetchDataInit).toHaveBeenCalled();
+ expect(mockMetaActions.tcCommunities.meta.fetchDataDone).toHaveBeenCalledWith('someId');
+});
+
+test('Triggers data loading if loaded for another community', () => {
+ TU.renderIntoDocument((
+
+ _.noop,
+ getState: () => mockState3,
+ subscribe: _.noop,
+ }}
+ history={{}}
+ location={{}}
+ />
+
+ ));
+ expect(mockMetaActions.tcCommunities.meta.fetchDataInit).toHaveBeenCalled();
+ expect(mockMetaActions.tcCommunities.meta.fetchDataDone).toHaveBeenCalledWith('someId');
+});
+
+test('Triggers toggle mobile menu on click', () => {
+ const page = TU.renderIntoDocument((
+
+ _.noop,
+ getState: () => mockState,
+ subscribe: _.noop,
+ }}
+ history={{}}
+ location={{}}
+ />
+
+ ));
+
+ const btn = TU.findAllInRenderedTree(page, item =>
+ item && item.className && item.className.match(/mobile-toggle/));
+ expect(btn.length).toBe(1);
+ TU.Simulate.click(btn[0]);
+ expect(mockMetaActions.tcCommunities.meta.mobileToggle).toHaveBeenCalled();
+});
+
+test('Close mobile menu when mount', () => {
+ TU.renderIntoDocument((
+
+ _.noop,
+ getState: () => mockState4,
+ subscribe: _.noop,
+ }}
+ history={{}}
+ location={{}}
+ />
+
+ ));
+
+ expect(mockMetaActions.tcCommunities.meta.mobileToggle).toHaveBeenCalled();
+});
diff --git a/__tests__/shared/containers/tc-communities/__snapshots__/Page.jsx.snap b/__tests__/shared/containers/tc-communities/__snapshots__/Page.jsx.snap
new file mode 100644
index 0000000000..f05d972548
--- /dev/null
+++ b/__tests__/shared/containers/tc-communities/__snapshots__/Page.jsx.snap
@@ -0,0 +1,90 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Matches shapshot 1`] = `
+
+`;
diff --git a/__tests__/shared/index.jsx b/__tests__/shared/index.jsx
new file mode 100644
index 0000000000..7382149df2
--- /dev/null
+++ b/__tests__/shared/index.jsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import Rnd from 'react-test-renderer/shallow';
+
+const rnd = new Rnd();
+
+afterAll(() => {
+ process.env.NODE_ENV = 'test';
+});
+
+test('Snapshot match', () => {
+ let App = require('shared').default;
+ rnd.render((
+
+ ));
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+ jest.resetModules();
+ process.env.NODE_ENV = 'development';
+ App = require('shared').default;
+ rnd.render((
+
+ ));
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+ process.env.NODE_ENV = 'test';
+});
diff --git a/__tests__/shared/reducers/auth.js b/__tests__/shared/reducers/auth.js
new file mode 100644
index 0000000000..9dfee3bcbc
--- /dev/null
+++ b/__tests__/shared/reducers/auth.js
@@ -0,0 +1,88 @@
+import { mockAction } from 'utils/mock';
+import { toFSA } from 'utils/redux';
+
+const dummy = 'DUMMY';
+
+const mockActions = {
+ auth: {
+ loadProfile: mockAction('LOAD_PROFILE', Promise.resolve('Profile')),
+ setTcTokenV2: mockAction('SET_TC_TOKEN_V2', 'Token V2'),
+ setTcTokenV3: mockAction('SET_TC_TOKEN_V3', 'Token V3'),
+ },
+};
+jest.setMock(require.resolve('actions/auth'), mockActions);
+
+jest.setMock('tc-accounts', {
+ decodeToken: () => 'User object',
+});
+
+const reducers = require('reducers/auth');
+
+function testReducer(reducer, istate) {
+ test('Initial state', () => {
+ const state = reducer(undefined, {});
+ expect(state).toEqual(istate);
+ });
+
+ test('Load profile', () =>
+ toFSA(mockActions.auth.loadProfile()).then((action) => {
+ const state = reducer({ dummy }, action);
+ expect(state).toEqual({
+ authenticating: false,
+ dummy,
+ profile: 'Profile',
+ });
+ }),
+ );
+
+ test('Set TC Token V2', () => {
+ const state = reducer({ dummy }, mockActions.auth.setTcTokenV2());
+ expect(state).toEqual({
+ dummy,
+ tokenV2: 'Token V2',
+ });
+ });
+
+ test('Set TC Token V3', () => {
+ const state = reducer({ dummy }, mockActions.auth.setTcTokenV3());
+ expect(state).toEqual({
+ dummy,
+ tokenV3: 'Token V3',
+ user: 'User object',
+ });
+ });
+
+ test('Set TC Token V3 with failure', () => {
+ mockActions.auth.setTcTokenV3 = mockAction('SET_TC_TOKEN_V3', null);
+ const state = reducer({ dummy }, mockActions.auth.setTcTokenV3());
+ expect(state).toEqual({
+ dummy,
+ tokenV3: null,
+ user: null,
+ });
+ mockActions.auth.setTcTokenV3 = mockAction('SET_TC_TOKEN_V3', 'Token V3');
+ });
+}
+
+describe('Default reducer', () => {
+ testReducer(reducers.default, {
+ authenticating: true,
+ });
+});
+
+describe('Factory without server side rendering', () =>
+ reducers.factory().then(res =>
+ testReducer(res, {}),
+ ),
+);
+
+describe('Factory with server side rendering', () =>
+ reducers.factory({
+ cookies: {
+ tcjwt: 'Token V2',
+ v3jwt: 'Token V3',
+ },
+ }).then(res =>
+ testReducer(res, {}),
+ ),
+);
diff --git a/__tests__/shared/reducers/challenge.js b/__tests__/shared/reducers/challenge.js
new file mode 100644
index 0000000000..039b3d0888
--- /dev/null
+++ b/__tests__/shared/reducers/challenge.js
@@ -0,0 +1,89 @@
+import { mockAction } from 'utils/mock';
+
+const mockChallengeActions = {
+ fetchChallengeInit: mockAction('FETCH_CHALLENGE_INIT'),
+ fetchChallengeDone: mockAction(
+ 'FETCH_CHALLENGE_DONE',
+ Promise.resolve('payload'),
+ ),
+ fetchSubmissionsDone: mockAction(
+ 'FETCH_SUBMISSION_DONE',
+ Promise.resolve('payload'),
+ ),
+};
+jest.setMock(require.resolve('actions/challenge'), mockChallengeActions);
+
+const mockSmpActions = {
+ smp: {
+ fetchSubmissionsDone: mockAction(
+ 'FETCH_SUBMISSION_DONE',
+ Promise.resolve('payload'),
+ ),
+ },
+};
+jest.setMock(require.resolve('actions/smp'), mockSmpActions);
+
+jest.setMock(require.resolve('reducers/my-submissions-management'),
+ state => ({ ...state }),
+);
+
+const reducers = require('reducers/challenge');
+
+beforeEach(() => jest.clearAllMocks());
+
+function testReducer(reducer, istate) {
+ let state;
+
+ test('Creates expected intial state', () => {
+ state = reducer(undefined, {});
+ expect(state).toEqual(istate);
+ });
+
+ test('Handles fetchChallengeInit as expected', () => {
+ state = reducer(state, mockChallengeActions.fetchChallengeInit());
+ expect(state).toEqual({
+ mySubmissionsManagement: {},
+ loadingDetails: true,
+ fetchChallengeFailure: false,
+ details: null,
+ });
+ });
+}
+
+describe('Default reducer', () =>
+ testReducer(reducers.default, {
+ mySubmissionsManagement: {},
+ }),
+);
+
+describe('Factory without http request', () =>
+ reducers.factory().then(res =>
+ testReducer(res, {
+ mySubmissionsManagement: {},
+ }),
+ ),
+);
+
+describe('Factory with server-side rendering', () =>
+ reducers.factory({
+ cookies: {
+ tcjwt: 'TcAuthTokenV2',
+ v3jwt: 'TcAuthTokenV3',
+ },
+ url: '/challenge/12345/my-submissions',
+ }).then(res =>
+ testReducer(res, {
+ mySubmissionsManagement: {},
+ }),
+ ),
+);
+
+describe('Factory without server-side rendering', () =>
+ reducers.factory({
+ url: '/some-random-url',
+ }).then(res =>
+ testReducer(res, {
+ mySubmissionsManagement: {},
+ }),
+ ),
+);
diff --git a/__tests__/shared/reducers/examples/data-fetch.js b/__tests__/shared/reducers/examples/data-fetch.js
new file mode 100644
index 0000000000..408b3d927d
--- /dev/null
+++ b/__tests__/shared/reducers/examples/data-fetch.js
@@ -0,0 +1,91 @@
+import actions from 'actions/examples/data-fetch';
+import defaultReducer, { factory } from 'reducers/examples/data-fetch';
+import { toFSA } from 'utils/redux';
+
+const DUMMY_PAYLOAD = 'Dummy Payload 12345';
+
+const fetchFailureMock = jest.fn(() =>
+ Promise.reject(new Error('ERROR')),
+);
+
+const fetchSuccessMock = jest.fn(() =>
+ Promise.resolve({
+ json: () => ({ data: DUMMY_PAYLOAD }),
+ }),
+);
+
+function testReducer(reducer, expectedInitialState) {
+ let state;
+
+ test('creates expected initial state', () => {
+ state = reducer(undefined, {});
+ expect(state).toEqual(expectedInitialState);
+ });
+
+ test('properly handles fetch data init', () => {
+ state = reducer(state, actions.examples.dataFetch.fetchDataInit());
+ expect(state).toEqual({
+ data: null,
+ failed: false,
+ loading: true,
+ });
+ });
+
+ test('properly handles data loading with success', () => {
+ global.fetch = fetchSuccessMock;
+ return toFSA(actions.examples.dataFetch.fetchDataDone()).then((action) => {
+ state = reducer(state, action);
+ expect(state).toEqual({
+ data: DUMMY_PAYLOAD,
+ failed: undefined,
+ loading: false,
+ });
+ });
+ });
+
+ test('properly handles data loading with failure', () => {
+ global.fetch = fetchFailureMock;
+ return toFSA(actions.examples.dataFetch.fetchDataDone()).then((action) => {
+ state = reducer(state, action);
+ expect(state).toEqual({
+ data: null,
+ failed: true,
+ loading: false,
+ });
+ });
+ });
+}
+
+global.fetch = fetchSuccessMock;
+describe('default reducer', () => testReducer(defaultReducer, {}));
+
+global.fetch = fetchSuccessMock;
+describe('factory without http request', () =>
+ factory().then(res => testReducer(res, {})),
+);
+
+global.fetch = fetchSuccessMock;
+describe('factory with matching http request and success response', () =>
+ factory({
+ url: '/examples/data-fetch/server',
+ }).then(res =>
+ testReducer(res, {
+ data: DUMMY_PAYLOAD,
+ failed: undefined,
+ loading: false,
+ }),
+ ),
+);
+
+global.fetch = fetchFailureMock;
+describe('factory with matching http request and network failure', () =>
+ factory({
+ url: '/examples/data-fetch/server',
+ }).then(res =>
+ testReducer(res, {
+ data: null,
+ failed: true,
+ loading: false,
+ }),
+ ),
+);
diff --git a/__tests__/shared/reducers/leaderboard.js b/__tests__/shared/reducers/leaderboard.js
new file mode 100644
index 0000000000..d2a256248b
--- /dev/null
+++ b/__tests__/shared/reducers/leaderboard.js
@@ -0,0 +1,100 @@
+import actions from 'actions/leaderboard';
+import defaultReducer, { factory } from 'reducers/leaderboard';
+import { toFSA } from 'utils/redux';
+
+const DUMMY_PAYLOAD = [{ 'user.handle': 'fake.username' }];
+const DUMMY_AUTH = { tokenV3: 'token' };
+const DUMMY_LEADERBOARD_API_URL = 'some/api/url';
+
+const fetchFailureMock = jest.fn(() =>
+ Promise.reject(new Error('ERROR')),
+);
+
+const fetchSuccessMock = jest.fn(() =>
+ Promise.resolve({
+ json: () => (DUMMY_PAYLOAD),
+ }),
+);
+
+function testReducer(reducer, expectedInitialState) {
+ let state;
+
+ test('creates expected initial state', () => {
+ state = reducer(undefined, {});
+ expect(state).toEqual(expectedInitialState);
+ });
+
+ test('properly handles fetch leaderboard init', () => {
+ state = reducer(state, actions.leaderboard.fetchLeaderboardInit());
+ expect(state).toEqual({
+ data: null,
+ failed: false,
+ loading: true,
+ loadedApiUrl: null,
+ });
+ });
+
+ test('properly handles data loading with success', () => {
+ global.fetch = fetchSuccessMock;
+ return toFSA(actions.leaderboard.fetchLeaderboardDone(DUMMY_AUTH, DUMMY_LEADERBOARD_API_URL))
+ .then((action) => {
+ state = reducer(state, action);
+ expect(state).toEqual({
+ data: DUMMY_PAYLOAD,
+ failed: false,
+ loading: false,
+ loadedApiUrl: DUMMY_LEADERBOARD_API_URL,
+ });
+ });
+ });
+
+ test('properly handles data loading with failure', () => {
+ global.fetch = fetchFailureMock;
+ return toFSA(actions.leaderboard.fetchLeaderboardDone(DUMMY_AUTH, DUMMY_LEADERBOARD_API_URL))
+ .then((action) => {
+ state = reducer(state, action);
+ expect(state).toEqual({
+ data: null,
+ failed: true,
+ loading: false,
+ loadedApiUrl: null,
+ });
+ });
+ });
+}
+
+global.fetch = fetchSuccessMock;
+describe('default reducer', () => testReducer(defaultReducer, {}));
+
+global.fetch = fetchSuccessMock;
+describe('factory without http request', () =>
+ factory().then(res => testReducer(res, {})),
+);
+
+global.fetch = fetchSuccessMock;
+describe('factory with matching http request and success response', () =>
+ factory({
+ url: '/leaderboard',
+ }).then(res =>
+ testReducer(res, {
+ data: DUMMY_PAYLOAD,
+ failed: false,
+ loading: false,
+ loadedApiUrl: '/leaderboard',
+ }),
+ ),
+);
+
+global.fetch = fetchFailureMock;
+describe('factory with matching http request and network failure', () =>
+ factory({
+ url: '/leaderboard',
+ }).then(res =>
+ testReducer(res, {
+ data: null,
+ failed: true,
+ loading: false,
+ loadedApiUrl: null,
+ }),
+ ),
+);
diff --git a/__tests__/shared/reducers/my-submissions-management.js b/__tests__/shared/reducers/my-submissions-management.js
new file mode 100644
index 0000000000..448e0ecd18
--- /dev/null
+++ b/__tests__/shared/reducers/my-submissions-management.js
@@ -0,0 +1,65 @@
+import actions from 'actions/smp';
+import defaultReducer, { factory } from 'reducers/my-submissions-management';
+
+let reducer = defaultReducer;
+
+const mockFetch = (resolvesTo) => {
+ global.fetch = jest.fn(() => Promise.resolve({ json: () => resolvesTo }));
+};
+
+function testReducer(expectedInitialState) {
+ let state;
+
+ test('creates expected initial state', () => {
+ state = reducer(undefined, {});
+ expect(state).toEqual(expectedInitialState);
+ });
+
+ test('properly handles showDetails', () => {
+ state = reducer(state, actions.smp.showDetails(1));
+ expect(state.showDetails.has(1)).toBeTruthy();
+
+ state = reducer(state, actions.smp.showDetails(1));
+ expect(state.showDetails.has(1)).toBeFalsy();
+ });
+
+ test('properly handles delete confirm', () => {
+ state = reducer(state, actions.smp.confirmDelete('TO_BE_DELETED'));
+
+ expect(state.showModal).toBeTruthy();
+ expect(state.toBeDeletedId).toEqual('TO_BE_DELETED');
+ });
+
+ test('properly handles delete cancel', () => {
+ state = reducer(state, actions.smp.cancelDelete());
+
+ expect(state.showModal).toBeFalsy();
+ expect(state.toBeDeletedId).toEqual(0);
+ });
+
+ test('properly handles delete done', () => {
+ state = reducer(state, actions.smp.confirmDelete('TO_BE_DELETED'));
+ state = reducer(state, actions.smp.deleteSubmissionDone());
+
+ expect(state.showModal).toBeFalsy();
+ expect(state.toBeDeletedId).toEqual(0);
+ });
+}
+
+describe('default reducer', () => {
+ mockFetch({});
+ reducer = defaultReducer;
+
+ testReducer({});
+});
+
+describe('factory without http request', () => {
+ mockFetch({});
+ beforeAll(() => factory().then((res) => { reducer = res; }));
+
+ testReducer({
+ showDetails: [],
+ showModal: false,
+ toBeDeletedId: 0,
+ });
+});
diff --git a/__tests__/shared/reducers/tc-communities/meta.js b/__tests__/shared/reducers/tc-communities/meta.js
new file mode 100644
index 0000000000..77072662f9
--- /dev/null
+++ b/__tests__/shared/reducers/tc-communities/meta.js
@@ -0,0 +1,75 @@
+import { mockAction } from 'utils/mock';
+
+const mockMetaActions = {
+ tcCommunities: {
+ meta: {
+ mobileToggle: mockAction('TC_COMMUNITIES/META/MOBILE_TOGGLE'),
+ fetchDataInit: mockAction('TC_COMMUNITIES/META/FETCH_DATA_INIT'),
+ fetchDataDone: mockAction(
+ 'TC_COMMUNITIES/META/FETCH_DATA_DONE',
+ Promise.resolve('payload'),
+ ),
+ },
+ },
+};
+jest.setMock(require.resolve('actions/tc-communities/meta'), mockMetaActions);
+
+const reducers = require('reducers/tc-communities/meta');
+
+beforeEach(() => jest.clearAllMocks());
+
+function testReducer(reducer, istate) {
+ let state;
+
+ test('Creates expected intial state', () => {
+ state = reducer(undefined, {});
+ expect(state).toEqual(istate);
+ });
+
+ test('Handles fetchDataInit as expected', () => {
+ state = reducer(state, mockMetaActions.tcCommunities.meta.fetchDataInit());
+ expect(state).toEqual({
+ communityId: null,
+ logos: [],
+ menuItems: [],
+ failed: false,
+ loading: true,
+ cssUrl: null,
+ leaderboardApiUrl: null,
+ });
+ });
+
+ test('Handles mobileToggle as expected', () => {
+ state = reducer(state, mockMetaActions.tcCommunities.meta.mobileToggle());
+ expect(state.isMobileOpen).toBeTruthy();
+
+ state = reducer(state, mockMetaActions.tcCommunities.meta.mobileToggle());
+ expect(state.isMobileOpen).toBeFalsy();
+ });
+}
+
+describe('Default reducer', () => {
+ testReducer(reducers.default, {});
+});
+
+describe('Factory without http request', () =>
+ reducers.factory().then(res =>
+ testReducer(res, {}),
+ ),
+);
+
+describe('Factory with server-side rendering', () =>
+ reducers.factory({
+ url: '/community/communityId/header',
+ }).then(res =>
+ testReducer(res, {}),
+ ),
+);
+
+describe('Factory without server-side rendering', () =>
+ reducers.factory({
+ url: '/some-random-url',
+ }).then(res =>
+ testReducer(res, {}),
+ ),
+);
diff --git a/__tests__/shared/reducers/topcoder_header.js b/__tests__/shared/reducers/topcoder_header.js
new file mode 100644
index 0000000000..5a238b0e8f
--- /dev/null
+++ b/__tests__/shared/reducers/topcoder_header.js
@@ -0,0 +1,84 @@
+import { mockAction } from 'utils/mock';
+
+const FIELD = 'FIELD';
+
+const mockActions = {
+ topcoderHeader: {
+ closeMenu: mockAction('TOPCODER_HEADER/CLOSE_MENU'),
+ closeMobileMenu: mockAction('TOPCODER_HEADER/CLOSE_MOBILE_MENU'),
+ closeSearch: mockAction('TOPCODER_HEADER/CLOSE_SEARCH'),
+ openMenu: mockAction('TOPCODER_HEADER/OPEN_MENU', {
+ menu: 'MENU',
+ trigger: 'TRIGGER',
+ }),
+ openMobileMenu: mockAction('TOPCODER_HEADER/OPEN_MOBILE_MENU'),
+ openSearch: mockAction('TOPCODER_HEADER/OPEN_SEARCH', {
+ trigger: 'TRIGGER',
+ }),
+ },
+};
+
+const reducers = require('reducers/topcoder_header');
+
+function testReducer(reducer, istate) {
+ test('Correct initial state', () => {
+ const state = reducer(undefined, {});
+ expect(state).toEqual(istate);
+ });
+
+ test('TOPCODER_HEADER/CLOSE_MENU', () => {
+ const state = reducer({ FIELD }, mockActions.topcoderHeader.closeMenu());
+ expect(state).toEqual({
+ FIELD,
+ openedMenu: null,
+ });
+ });
+
+ test('TOPCODER_HEADER/CLOSE_MOBILE_MENU', () => {
+ const state = reducer({
+ FIELD,
+ }, mockActions.topcoderHeader.closeMobileMenu());
+ expect(state).toEqual({
+ FIELD,
+ mobileMenuOpened: false,
+ });
+ });
+
+ test('TOPCODER_HEADER/CLOSE_SEARCH', () => {
+ const state = reducer({ FIELD }, mockActions.topcoderHeader.closeSearch());
+ expect(state).toEqual({ FIELD, searchOpened: false });
+ });
+
+ test('TOPCODER_HEADER/OPEN_MENU', () => {
+ const state = reducer({ FIELD }, mockActions.topcoderHeader.openMenu());
+ expect(state).toEqual({
+ FIELD,
+ activeTrigger: 'TRIGGER',
+ openedMenu: 'MENU',
+ });
+ });
+
+ test('TOPCODER_HEADER/OPEN_MOBILE_MENU', () => {
+ const state = reducer({
+ FIELD,
+ }, mockActions.topcoderHeader.openMobileMenu());
+ expect(state).toEqual({
+ FIELD,
+ mobileMenuOpened: true,
+ });
+ });
+
+ test('TOPCODER_HEADER/OPEN_SEARCH', () => {
+ const state = reducer({ FIELD }, mockActions.topcoderHeader.openSearch());
+ expect(state).toEqual({
+ FIELD,
+ activeTrigger: 'TRIGGER',
+ searchOpened: true,
+ });
+ });
+}
+
+describe('Default reducer', () =>
+ testReducer(reducers.default, {}),
+);
+
diff --git a/__tests__/shared/routes/__snapshots__/index.jsx.snap b/__tests__/shared/routes/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..2e9bc06ff5
--- /dev/null
+++ b/__tests__/shared/routes/__snapshots__/index.jsx.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Matches shallow shapshot 1`] = `
+
+`;
diff --git a/__tests__/shared/routes/examples/DataFetch.jsx b/__tests__/shared/routes/examples/DataFetch.jsx
new file mode 100644
index 0000000000..3fc9bb102f
--- /dev/null
+++ b/__tests__/shared/routes/examples/DataFetch.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import Rnd from 'react-test-renderer/shallow';
+import Route from 'routes/examples/DataFetch';
+
+const rnd = new Rnd();
+
+test('Matches snapshot', () => {
+ rnd.render((
+
+ ));
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+});
diff --git a/__tests__/shared/routes/examples/__snapshots__/DataFetch.jsx.snap b/__tests__/shared/routes/examples/__snapshots__/DataFetch.jsx.snap
new file mode 100644
index 0000000000..fcb2c5fe3c
--- /dev/null
+++ b/__tests__/shared/routes/examples/__snapshots__/DataFetch.jsx.snap
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Matches snapshot 1`] = `
+
+
+
+
+`;
diff --git a/__tests__/shared/routes/examples/__snapshots__/index.jsx.snap b/__tests__/shared/routes/examples/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..943078245a
--- /dev/null
+++ b/__tests__/shared/routes/examples/__snapshots__/index.jsx.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`matches snapshots 1`] = `null`;
diff --git a/__tests__/shared/routes/examples/index.jsx b/__tests__/shared/routes/examples/index.jsx
new file mode 100644
index 0000000000..c62de0f716
--- /dev/null
+++ b/__tests__/shared/routes/examples/index.jsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { StaticRouter } from 'react-router-dom';
+
+import Examples from 'routes/examples';
+
+test('matches snapshots', () => {
+ const cmp = renderer.create(
+
+
+ ,
+ );
+ expect(cmp.toJSON()).toMatchSnapshot();
+});
diff --git a/__tests__/shared/routes/index.jsx b/__tests__/shared/routes/index.jsx
new file mode 100644
index 0000000000..cd540435b2
--- /dev/null
+++ b/__tests__/shared/routes/index.jsx
@@ -0,0 +1,9 @@
+import React from 'react';
+import Renderer from 'react-test-renderer/shallow';
+import Routes from 'routes';
+
+test('Matches shallow shapshot', () => {
+ const renderer = new Renderer();
+ renderer.render();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
+});
diff --git a/__tests__/shared/services/api.js b/__tests__/shared/services/api.js
new file mode 100644
index 0000000000..b8ba92b83e
--- /dev/null
+++ b/__tests__/shared/services/api.js
@@ -0,0 +1,104 @@
+import { getApiV2, getApiV3 } from 'services/api';
+
+global.fetch = (url, ops) => Promise.resolve({ url, ops });
+
+const ENDPOINT = '/ENDPOINT';
+const MOCK_OPS = { OPTIONS: 'OPTIONS' };
+
+jest.mock('utils/config', () => ({
+ API: {
+ V2: 'API-URL-V2',
+ V3: 'API-URL-V3',
+ },
+}));
+
+function testRes(res, base, token, method, body, mockOps) {
+ expect(res).toEqual({
+ url: `${base}${ENDPOINT}`,
+ ops: {
+ body,
+ headers: {
+ Authorization: token ? `Bearer ${token}` : undefined,
+ 'Content-Type': 'application/json',
+ },
+ method,
+ OPTIONS: mockOps ? 'OPTIONS' : undefined,
+ },
+ });
+}
+
+function testApi(api, base, token) {
+ return api.fetch(ENDPOINT, MOCK_OPS)
+ .then((res) => {
+ testRes(res, base, token, undefined, undefined, true);
+ return api.delete(ENDPOINT);
+ }).then((res) => {
+ testRes(res, base, token, 'DELETE');
+ return api.get(ENDPOINT);
+ }).then((res) => {
+ testRes(res, base, token);
+ return api.post(ENDPOINT, 'BODY');
+ })
+ .then((res) => {
+ testRes(res, base, token, 'POST', 'BODY');
+ return api.postJson(ENDPOINT, { BODY: 'BODY' });
+ })
+ .then((res) => {
+ testRes(res, base, token, 'POST', JSON.stringify({ BODY: 'BODY' }));
+ return api.put(ENDPOINT, 'BODY');
+ })
+ .then((res) => {
+ testRes(res, base, token, 'PUT', 'BODY');
+ return api.putJson(ENDPOINT, { BODY: 'BODY' });
+ })
+ .then((res) => {
+ testRes(res, base, token, 'PUT', JSON.stringify({ BODY: 'BODY' }));
+ });
+}
+
+let api;
+test('API v2 service works without auth token', () => {
+ api = getApiV2();
+ return testApi(api, 'API-URL-V2');
+});
+
+test('API v2 service works with auth token', () => {
+ api = getApiV2('TOKEN');
+ return testApi(api, 'API-URL-V2', 'TOKEN');
+});
+
+test(
+ 'API v2 service from the previous call is re-used, if token is the same',
+ () => expect(getApiV2('TOKEN')).toBe(api),
+);
+
+test(
+ 'New API v2 service is created if token is new', () => {
+ const api2 = getApiV2('TOKEN2');
+ expect(api2).not.toBe(api);
+ return testApi(api2, 'API-URL-V2', 'TOKEN2');
+ },
+);
+
+test('API v3 service works without auth token', () => {
+ api = getApiV3();
+ return testApi(api, 'API-URL-V3');
+});
+
+test('API v3 service works with auth token', () => {
+ api = getApiV3('TOKEN');
+ return testApi(api, 'API-URL-V3', 'TOKEN');
+});
+
+test(
+ 'API v3 service from the previous call is re-used, if token is the same',
+ () => expect(getApiV3('TOKEN')).toBe(api),
+);
+
+test(
+ 'New API v3 service is created if token is new', () => {
+ const api2 = getApiV3('TOKEN2');
+ expect(api2).not.toBe(api);
+ return testApi(api2, 'API-URL-V3', 'TOKEN2');
+ },
+);
diff --git a/__tests__/shared/store-factory.js b/__tests__/shared/store-factory.js
new file mode 100644
index 0000000000..59b19a10fc
--- /dev/null
+++ b/__tests__/shared/store-factory.js
@@ -0,0 +1,20 @@
+const MODULE = require.resolve('shared/store-factory');
+
+afterAll(() => {
+ delete process.env.DEV_TOOLS;
+});
+
+beforeEach(() => {
+ jest.resetModules();
+});
+
+test('Does not throw', () => {
+ expect(() => require(MODULE)).not.toThrow();
+});
+
+test('Does not throw when uses dev tools', () => {
+ process.env.DEV_TOOLS = true;
+ expect(() => require(MODULE)).not.toThrow();
+ delete process.env.DEV_TOOLS;
+});
+
diff --git a/__tests__/shared/utils/config.js b/__tests__/shared/utils/config.js
new file mode 100644
index 0000000000..a1c7a71a82
--- /dev/null
+++ b/__tests__/shared/utils/config.js
@@ -0,0 +1,17 @@
+/* eslint-env browser */
+
+window.CONFIG = 'Client-side config';
+jest.setMock('config', 'Server-side config');
+
+afterAll(() => delete process.env.FRONT_END);
+beforeEach(() => jest.resetModules());
+
+test('Serves config from node-config at the server-side', () => {
+ expect(process.env.FRONT_END).toBeUndefined();
+ expect(require('utils/config')).toBe('Server-side config');
+});
+
+test('Serves config from window.CONFIG at the client-side', () => {
+ process.env.FRONT_END = true;
+ expect(require('utils/config')).toBe('Client-side config');
+});
diff --git a/__tests__/shared/utils/isomorphy.js b/__tests__/shared/utils/isomorphy.js
new file mode 100644
index 0000000000..abd8da45aa
--- /dev/null
+++ b/__tests__/shared/utils/isomorphy.js
@@ -0,0 +1,29 @@
+const mod = () => require('utils/isomorphy');
+
+afterAll(() => {
+ delete process.env.FRONT_END;
+ process.env.NODE_ENV = 'test';
+});
+beforeEach(() => jest.resetModules());
+
+test('Client- and server-side checks work properly at the server-side', () => {
+ expect(process.env.FRONT_END).toBeUndefined();
+ expect(mod().isClientSide()).toBe(false);
+ expect(mod().isServerSide()).toBe(true);
+});
+
+test('Client- and server-side checks work properly at the client-side', () => {
+ process.env.FRONT_END = true;
+ expect(mod().isClientSide()).toBe(true);
+ expect(mod().isServerSide()).toBe(false);
+});
+
+test('isDev() returns true in dev', () => {
+ process.env.NODE_ENV = 'development';
+ expect(mod().isDev()).toBe(true);
+});
+
+test('isDev() returns false in prod', () => {
+ process.env.NODE_ENV = 'production';
+ expect(mod().isDev()).toBe(false);
+});
diff --git a/__tests__/shared/utils/logger.js b/__tests__/shared/utils/logger.js
new file mode 100644
index 0000000000..2fb39d374a
--- /dev/null
+++ b/__tests__/shared/utils/logger.js
@@ -0,0 +1,29 @@
+import _ from 'lodash';
+
+afterAll(() => {
+ process.env.NODE_ENV = 'test';
+});
+
+describe('Dev logger', () => {
+ beforeAll(() => {
+ process.env.NODE_ENV = 'development';
+ });
+ test('is an alias for console in dev environment', () =>
+ expect(require('utils/logger').default).toBe(console));
+});
+
+describe('Prod logger', () => {
+ beforeAll(() => {
+ jest.resetModules();
+ process.env.NODE_ENV = 'production';
+ });
+ test('does not use console methods', () => {
+ const logger = require('utils/logger').default;
+ const spies = _.functions(console).map(key => jest.spyOn(console, key));
+ _.functions(console).forEach((func) => {
+ expect(_.isFunction(logger[func])).toBe(true);
+ logger[func]();
+ });
+ spies.forEach(spy => expect(spy).not.toHaveBeenCalled());
+ });
+});
diff --git a/bin/www b/bin/www
new file mode 100644
index 0000000000..bcc480feaf
--- /dev/null
+++ b/bin/www
@@ -0,0 +1,11 @@
+#!/usr/bin/env node
+
+/* App startup script. */
+
+/* Enables Babel for the server-side code (with exception of this very file). */
+require('babel-register')({
+ ignore: /node_modules\/(?!appirio-tech.*|topcoder|tc-)/,
+});
+
+/* Runs the ExpressJS startup script. */
+require('../src/server');
diff --git a/browserlist b/browserlist
new file mode 100644
index 0000000000..dcb0ac7f1e
--- /dev/null
+++ b/browserlist
@@ -0,0 +1 @@
+> 5%
\ No newline at end of file
diff --git a/circle.yml b/circle.yml
new file mode 100644
index 0000000000..6063365e07
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,35 @@
+machine:
+ pre:
+ - curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
+ node:
+ version: 6.10.2
+ services:
+ - docker
+ post:
+ - docker version
+
+dependencies:
+ pre:
+ - sudo apt-get install libyaml-dev libpython2.7-dev
+ - sudo pip install 'awsebcli==3.7.4' --force-reinstall
+ override:
+ - npm install
+
+deployment:
+ development:
+ branch: develop
+ owner: topcoder-platform
+ commands:
+ - docker build --build-arg BUILD_ENV=development -t $DEV_AWS_ACCOUNT_ID.dkr.ecr.$DEV_AWS_REGION.amazonaws.com/community-app:$CIRCLE_SHA1 .
+ - ./deploy.sh DEV $CIRCLE_SHA1
+
+ production:
+ tag: /v[0-9]+(\.[0-9]+)*/
+ owner: topcoder-platform
+ commands:
+ - docker build --build-arg BUILD_ENV=production -t $PROD_AWS_ACCOUNT_ID.dkr.ecr.$PROD_AWS_REGION.amazonaws.com/community-app:$CIRCLE_TAG .
+ - ./deploy.sh PROD $CIRCLE_TAG
+
+general:
+ artifacts:
+ - ./__coverage__
diff --git a/config/default.json b/config/default.json
new file mode 100644
index 0000000000..8d86f58e5a
--- /dev/null
+++ b/config/default.json
@@ -0,0 +1,37 @@
+/* All availalbe configuration options should be documented in the default
+ * config file, even when they are overriden in every custom configuration. */
+{
+ /* Connector URL of the TC accounts App. */
+ "ACCOUNTS_APP_CONNECTOR_URL": "https://accounts.topcoder-dev.com/connector.html",
+
+ /* Configuration of Topcoder APIs. */
+ "API": {
+ "V2": "https://api.topcoder-dev.com/v2",
+ "V3": "https://api.topcoder-dev.com/v3"
+ },
+
+ "COOKIES": {
+ /* Expiration time [days] for browser cookies set by the App. */
+ "MAXAGE": 7,
+
+ /* If true the cookies set by this App will only be transmitted over secure
+ * protocols like https. */
+ "SECURE": false
+ },
+
+ /* Various URLs. Most of them lead to different segments of Topcoder
+ * platform. */
+ "URL": {
+ "ARENA": "https://arena.topcoder-dev.com/",
+ "AUTH": "https://accounts.topcoder-dev.com/member",
+ "BASE": "https://www.topcoder-dev.com",
+ "COMMUNITY": "https://community.topcoder-dev.com",
+ "FORUMS": "https://apps.topcoder-dev.com/forums",
+ "HELP": "https://help.topcoder-dev.com",
+ "MEMBER": "https://members.topcoder-dev.com",
+ "ONLINE_REVIEW": "https://software.topcoder-dev.com",
+ "STUDIO": "https://studio.topcoder-dev.com",
+ "TCO": "https://www.topcoder.com/tco",
+ "WIPRO": "https://wipro.topcoder.com"
+ }
+}
diff --git a/config/development.json b/config/development.json
new file mode 100644
index 0000000000..2c63c08510
--- /dev/null
+++ b/config/development.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/config/jest/default.js b/config/jest/default.js
new file mode 100644
index 0000000000..19cc4c4090
--- /dev/null
+++ b/config/jest/default.js
@@ -0,0 +1,18 @@
+module.exports = {
+ collectCoverage: true,
+ collectCoverageFrom: [
+ 'src/**/*.{js,jsx}',
+ ],
+ coverageDirectory: '__coverage__',
+ moduleNameMapper: {
+ '\\.(scss|css)$': 'identity-obj-proxy',
+ },
+ rootDir: '../..',
+ testPathIgnorePatterns: [
+ '__mocks__',
+ '/node_modules/',
+ ],
+ transformIgnorePatterns: [
+ '/node_modules/(?!appirio-tech.*|topcoder|tc-)',
+ ],
+};
diff --git a/config/production.json b/config/production.json
new file mode 100644
index 0000000000..303d5156c1
--- /dev/null
+++ b/config/production.json
@@ -0,0 +1,22 @@
+{
+ "ACCOUNTS_APP_CONNECTOR_URL": "https://accounts.topcoder.com/connector.html",
+ "API": {
+ "V2": "https://api.topcoder.com/v2",
+ "V3": "https://api.topcoder.com/v3"
+ },
+ "COOKIES": {
+ "MAXAGE": 7,
+ "SECURE": true
+ },
+ "URL": {
+ "ARENA": "https://arena.topcoder.com/",
+ "AUTH": "https://accounts.topcoder.com/member",
+ "BASE": "https://www.topcoder.com",
+ "COMMUNITY": "https://community.topcoder.com",
+ "FORUMS": "https://apps.topcoder.com/forums",
+ "HELP": "https://help.topcoder.com",
+ "MEMBER": "https://member.topcoder.com",
+ "ONLINE_REVIEW": "https://software.topcoder.com",
+ "STUDIO": "https://studio.topcoder.com"
+ }
+}
diff --git a/config/test.json b/config/test.json
new file mode 100644
index 0000000000..0db3279e44
--- /dev/null
+++ b/config/test.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/config/webpack/.eslintrc b/config/webpack/.eslintrc
new file mode 100644
index 0000000000..bb484d8a0a
--- /dev/null
+++ b/config/webpack/.eslintrc
@@ -0,0 +1,9 @@
+{
+ "rules": {
+ // In this folder we have just Webpack configuration files, thus dev
+ // dependencies are permitted.
+ "import/no-extraneous-dependencies": [
+ "error", { "devDependencies": true }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/config/webpack/default.js b/config/webpack/default.js
new file mode 100644
index 0000000000..f3f20df688
--- /dev/null
+++ b/config/webpack/default.js
@@ -0,0 +1,125 @@
+const autoprefixer = require('autoprefixer');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+const path = require('path');
+const webpack = require('webpack');
+
+const context = path.resolve(__dirname, '../..');
+
+module.exports = {
+ context,
+ entry: './src/client',
+ module: {
+ rules: [{
+ test: /\.(eot|otf|svg|ttf|woff|woff2)$/,
+ include: [
+ /src\/assets\/fonts/,
+ /node_modules/,
+ ],
+ loader: 'file-loader',
+ options: {
+ outputPath: '/fonts/',
+ publicPath: '/fonts/',
+ },
+ }, {
+ test: /\.(jsx?|svg)$/,
+ exclude: [
+ /node_modules\/(?!appirio-tech.*|topcoder|tc-)/,
+ /src\/assets\/fonts/,
+ ],
+ loader: 'babel-loader',
+ options: {
+ babelrc: false,
+ presets: ['env', 'react', 'stage-2'],
+ plugins: [
+ 'inline-react-svg',
+ ['module-resolver', {
+ extensions: ['.js', '.jsx'],
+ root: [
+ './src/shared',
+ './src',
+ ],
+ }],
+ ['react-css-modules', {
+ filetypes: {
+ '.scss': 'postcss-scss',
+ },
+ }],
+ ],
+ },
+ }, {
+ test: /\.(jpeg|jpg|png)$/,
+ include: /src\/assets\/images/,
+ loader: 'file-loader',
+ options: {
+ outputPath: '/images/',
+ publicPath: '/images/',
+ },
+ }, {
+ test: /\.scss$/,
+ exclude: /(bower_components|node_modules)/,
+ use: ExtractTextPlugin.extract({
+ fallback: 'style-loader',
+ use: [{
+ loader: 'css-loader',
+ options: {
+ importLoaders: 3,
+ localIdentName: '[path]___[name]__[local]___[hash:base64:5]',
+ modules: true,
+ },
+ }, 'resolve-url-loader', {
+ loader: 'postcss-loader',
+ options: {
+ plugins: [
+ autoprefixer,
+ ],
+ },
+ }, {
+ loader: 'sass-loader',
+ options: {
+ sourceMap: true,
+ },
+ }],
+ }),
+ }, {
+ /* We need to support css loading for third-party plugins,
+ * we are not supposed to use css files inside the project. */
+ test: /\.css$/,
+ use: ExtractTextPlugin.extract({
+ fallback: 'style-loader',
+ use: ['css-loader'],
+ }),
+ }],
+ },
+ output: {
+ filename: 'bundle.js',
+ path: path.resolve(__dirname, '../../build'),
+ publicPath: '/',
+ },
+ plugins: [
+ new CopyWebpackPlugin([{
+ from: path.resolve(__dirname, '../../src/assets/mock-data'),
+ to: path.resolve(__dirname, '../../build/mock-data'),
+ }, {
+ from: path.resolve(__dirname, '../../src/assets/themes'),
+ to: path.resolve(__dirname, '../../build/themes'),
+ }]),
+ new ExtractTextPlugin('style.css'),
+ new webpack.DefinePlugin({
+ 'process.env': {
+ /* Some isomorphic code relies on this variable to determine, whether
+ * it is executed client- or server-side. */
+ FRONT_END: true,
+ },
+ }),
+ ],
+ resolve: {
+ alias: {
+ /* NOTE: Aliases related to .jsx and .jsx files are defined in Babel
+ * config. */
+ assets: path.resolve(__dirname, '../../src/assets'),
+ styles: path.resolve(__dirname, '../../src/styles'),
+ },
+ extensions: ['.js', '.json', '.jsx', '.scss'],
+ },
+};
diff --git a/config/webpack/dev-css-optimization.js b/config/webpack/dev-css-optimization.js
new file mode 100644
index 0000000000..d91e452ee3
--- /dev/null
+++ b/config/webpack/dev-css-optimization.js
@@ -0,0 +1,45 @@
+/*
+ * OptimizeCssAssetsPlugin options to be used in development Webpack build.
+ * This set of options disables all optimizations except of CSS deduplication.
+ * CSS deduplication radically decreases the size of CSS bundle produced by
+ * Webpack, which severally improves performance of browser's developer tools.
+ * We do not want to use any other optimizations, as extra optimizations slow
+ * down Webpack compilation, hence they slow down Hot Module Reloading in dev.
+ */
+
+module.exports = {
+ colormin: false,
+ calc: false,
+ convertValues: false,
+ core: false,
+ discardComments: false,
+ discardEmpty: false,
+ discardOverridden: false,
+ discardUnused: false,
+ filterOptimiser: false,
+ filterPlugins: false,
+ functionOptimiser: false,
+ mergeIdents: false,
+ mergeLonghand: false,
+ mergeRules: false,
+ minifyFontValues: false,
+ minifyGradients: false,
+ minifyParams: false,
+ minifySelectors: false,
+ normalizeCharset: false,
+ normalizeString: false,
+ normalizeUnicode: false,
+ normalizeUrl: false,
+ orderedValues: false,
+ reduceBackgroundRepeat: false,
+ reduceDisplayValues: false,
+ reduceIdents: false,
+ reduceInitial: false,
+ reducePositions: false,
+ reduceTimingFunctions: false,
+ reduceTransforms: false,
+ styleCache: false,
+ svgo: false,
+ uniqueSelectors: false,
+ zIndex: false,
+};
diff --git a/config/webpack/development.js b/config/webpack/development.js
new file mode 100644
index 0000000000..1e6dd72fbd
--- /dev/null
+++ b/config/webpack/development.js
@@ -0,0 +1,55 @@
+const cssProcessorOptions = require('./dev-css-optimization');
+const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
+const webpack = require('webpack');
+const webpackMerge = require('webpack-merge');
+
+const defaultConfig = require('./default');
+
+module.exports = webpackMerge(defaultConfig, {
+ entry: [
+ 'react-hot-loader/patch',
+ 'webpack-hot-middleware/client?reload=true',
+ defaultConfig.entry,
+ ],
+ module: {
+ rules: [{
+ test: /\.(jsx?|svg)$/,
+ exclude: [
+ /node_modules\/(?!appirio-tech.*|topcoder|tc-)/,
+ /src\/assets\/fonts/,
+ ],
+ loader: 'babel-loader',
+ options: {
+ babelrc: false,
+ presets: [['env', { modules: false }], 'react', 'stage-2'],
+ plugins: [
+ 'inline-react-svg',
+ ['module-resolver', {
+ extensions: ['.js', '.jsx'],
+ root: [
+ './src/shared',
+ './src',
+ ],
+ }],
+ 'react-hot-loader/babel',
+ ['react-css-modules', {
+ filetypes: {
+ '.scss': 'postcss-scss',
+ },
+ }],
+ ],
+ },
+ }],
+ },
+ plugins: [
+ new OptimizeCssAssetsPlugin({ cssProcessorOptions }),
+ new webpack.DefinePlugin({
+ 'process.env': {
+ DEV_TOOLS: JSON.stringify(true),
+ },
+ }),
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NoEmitOnErrorsPlugin(),
+ new webpack.NamedModulesPlugin(),
+ ],
+});
diff --git a/config/webpack/production.js b/config/webpack/production.js
new file mode 100644
index 0000000000..ec5dad0012
--- /dev/null
+++ b/config/webpack/production.js
@@ -0,0 +1,21 @@
+const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
+const webpack = require('webpack');
+const webpackMerge = require('webpack-merge'); // eslint-disable-line import/no-extraneous-dependencies
+
+const defaultConfig = require('./default');
+
+module.exports = webpackMerge(defaultConfig, {
+ module: {
+ /* To avoid bundling of redux-devtools into production bundle. */
+ noParse: /\/src\/shared\/containers\/DevTools/,
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: JSON.stringify('production'),
+ },
+ }),
+ new OptimizeCssAssetsPlugin(),
+ new webpack.optimize.UglifyJsPlugin(),
+ ],
+});
diff --git a/deploy.sh b/deploy.sh
new file mode 100755
index 0000000000..a00f3870e5
--- /dev/null
+++ b/deploy.sh
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+
+# more bash-friendly output for jq
+JQ="jq --raw-output --exit-status"
+
+ENV=$1
+TAG=$2
+AWS_REGION=$(eval "echo \$${ENV}_AWS_REGION")
+AWS_ECS_CLUSTER=$(eval "echo \$${ENV}_AWS_ECS_CLUSTER")
+ACCOUNT_ID=$(eval "echo \$${ENV}_AWS_ACCOUNT_ID")
+
+configure_aws_cli() {
+ AWS_ACCESS_KEY_ID=$(eval "echo \$${ENV}_AWS_ACCESS_KEY_ID")
+ AWS_SECRET_ACCESS_KEY=$(eval "echo \$${ENV}_AWS_SECRET_ACCESS_KEY")
+ aws --version
+ aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
+ aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
+ aws configure set default.region $AWS_REGION
+ aws configure set default.output json
+ echo "Configured AWS CLI."
+}
+
+deploy_cluster() {
+
+ family="community-app-task"
+
+ make_task_def
+ register_definition
+
+ if [[ $(aws ecs update-service --cluster $AWS_ECS_CLUSTER --service $AWS_ECS_SERVICE --task-definition $revision | \
+ $JQ '.service.taskDefinition') != $revision ]]; then
+ echo "Error updating service."
+ return 1
+ fi
+
+ echo "Deployed!"
+ return 0
+}
+
+make_task_def(){
+ task_template='[
+ {
+ "name": "community-app",
+ "image": "%s.dkr.ecr.%s.amazonaws.com/%s:%s",
+ "essential": true,
+ "memory": 500,
+ "cpu": 100,
+ "environment": [
+ {
+ "name": "NODE_ENV",
+ "value": "%s"
+ }
+ ],
+ "portMappings": [
+ {
+ "hostPort": 0,
+ "containerPort": 3000,
+ "protocol": "tcp"
+ }
+ ],
+ "logConfiguration": {
+ "logDriver": "awslogs",
+ "options": {
+ "awslogs-group": "/aws/ecs/%s",
+ "awslogs-region": "%s",
+ "awslogs-stream-prefix": "community-app"
+ }
+ }
+ }
+ ]'
+
+ if [ "$ENV" = "PROD" ]; then
+ NODE_ENV=production
+ elif [ "$ENV" = "DEV" ]; then
+ NODE_ENV=development
+ fi
+
+ task_def=$(printf "$task_template" $ACCOUNT_ID $AWS_REGION $AWS_REPOSITORY $TAG $NODE_ENV $AWS_ECS_CLUSTER $AWS_REGION)
+}
+
+push_ecr_image() {
+ echo "Pushing Docker Image..."
+ eval $(aws ecr get-login --region $AWS_REGION)
+ docker push $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$AWS_REPOSITORY:$TAG
+ echo "Docker Image published."
+}
+
+register_definition() {
+
+ if revision=$(aws ecs register-task-definition --container-definitions "$task_def" --family $family | $JQ '.taskDefinition.taskDefinitionArn'); then
+ echo "Revision: $revision"
+ else
+ echo "Failed to register task definition"
+ return 1
+ fi
+
+}
+
+configure_aws_cli
+push_ecr_image
+deploy_cluster
diff --git a/docs/challenge-listing-notes-images/edit-saved-filters.jpg b/docs/challenge-listing-notes-images/edit-saved-filters.jpg
new file mode 100644
index 0000000000..449abef425
Binary files /dev/null and b/docs/challenge-listing-notes-images/edit-saved-filters.jpg differ
diff --git a/docs/challenge-listing-notes-images/filters.jpg b/docs/challenge-listing-notes-images/filters.jpg
new file mode 100644
index 0000000000..3671c52e11
Binary files /dev/null and b/docs/challenge-listing-notes-images/filters.jpg differ
diff --git a/docs/challenge-listing-notes-images/save-filters.jpg b/docs/challenge-listing-notes-images/save-filters.jpg
new file mode 100644
index 0000000000..18f7c04385
Binary files /dev/null and b/docs/challenge-listing-notes-images/save-filters.jpg differ
diff --git a/docs/challenge-listing-notes-images/tooltips.jpg b/docs/challenge-listing-notes-images/tooltips.jpg
new file mode 100644
index 0000000000..407145cf00
Binary files /dev/null and b/docs/challenge-listing-notes-images/tooltips.jpg differ
diff --git a/docs/challenge-listing-notes.md b/docs/challenge-listing-notes.md
new file mode 100644
index 0000000000..cce084154f
--- /dev/null
+++ b/docs/challenge-listing-notes.md
@@ -0,0 +1,88 @@
+# TOPCODER COMMUNITIES - LISTING OF RELEVANT CHALLENGES
+
+## VIDEO
+Video on YouTube available by [link](https://youtu.be/00gz41hFakY) only
+
+## DONE
+I've used the preferred way of implementing challenge and moved all the necessary files from `react-component` project to `community-app` project.
+- No lint errors introduced
+- Unit test are passed
+- Unit test coverage is out of threshold, but it's allowed by @birdofpreyru
+- Using the `ChallengeFilterExample` component for Topcoder App supposed to work, as no props were deleted and all the introduced props are optional
+- Master filter is defined as a function inside container `ChallengeListing` component and passed to the `ChallengeFilterExample` component as an optional property. So it can be changed to absolutely any filter or can be skipped at all.
+- All the functionality of `ChallengeFilterExample` component fully works inside `community-app` project.
+
+## VERIFICATION
+
+### Linting and tests
+- To check that there are not lint errors, run `npm run lint`
+- To check that there are no test failed, run `npm run jest`. Test coverage thresholds are only failed. Some warning are being displayed but no test are being broken.
+
+### Functionality
+
+Before opening the project it's better to login at http://accounts.topcoder-dev.com/member to get access to all the functionality.
+
+Note. When you open pages of the project, there dev-tools are shown on the right side. To hide it press `Ctrl + M`.
+
+#### 1. Master filter
+Now master filter is configured to filter challenges by technology tag which is being retrieved from the url. To check it, follow the for challenges filtered by `JavaScript` http://local.topcoder-dev.com:3000/community-challenge-listing/JavaScript. It can be easily seen, that all displayed challenges has `JavaScript` tag in their description. You also can change the url to filter challenges by other tags, [`ajax` for example](http://local.topcoder-dev.com:3000/community-challenge-listing/ajax)
+
+#### 2. Other filters can be combined with the master filter
+Just apply any other filter on the page. On the screen below there are pointed several ways to add additional filters.
+
+![](challenge-listing-notes-images/filters.jpg)
+
+#### 3. Tooltips
+There are several places where tooltips should appear. Pointed on the screen below.
+
+![](challenge-listing-notes-images/tooltips.jpg)
+
+#### 3. Save filters
+There is also functionality for saving custom filters. It works only particularly. You can still save them visually and edit. But they are not being saved on the server and also you cannot apply them. These issues comes from `react-component` project.
+
+![](challenge-listing-notes-images/save-filters.jpg)
+
+Also editing saved filters works.
+
+![](challenge-listing-notes-images/edit-saved-filters.jpg)
+
+
+## ADDITIONALLY
+- Added 'run fix:js' command to package to fix js lint errors
+- Fixed challenges sorting dropdown which cut some items (this issue now is happening on the production website also)
+
+## KNOWN ISSUES
+A list of known issues is provided to help recognizing existent problems. They are not connected directly to the challenge scope or were confirmed by @birdofpreyru as out of scope.
+- Custom user filters on filter sidebar are not displayed on mobile resolutions (same on production website)
+- DevTools in Chrome, Safari and probably other browsers work very slow. Possible reason is that in many component styles we include main style using
+ scss import `@import '~styles/tc-style'`.
+ But unlike webpack, scss import includes files content every time we call it instead of only once. It makes css bundle file very big which probably slowing down DevTools.
+- `run fix:styles` may break `run lint:scss`, submitted an [issue](https://github.com/topcoder-platform/community-app/issues/21)
+- "save filter" doesn't save filter on the server but produces errors.
+ When we press it, not only we add a custom filter to the component's state, but also send to https://lc1-user-settings-service.herokuapp.com/saved-searches.
+
+ This rises an issue with CORS even though I use domain 'http://local.topcoder-dev.com/' for local development.
+
+ `bundle.js:117754 OPTIONS https://lc1-user-settings-service.herokuapp.com/saved-searches 401 (Unauthorized)`
+
+ `Fetch API cannot load https://lc1-user-settings-service.herokuapp.com/saved-searches. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://local.topcoder-dev.com:3000' is therefore not allowed access. The response had HTTP status code 401. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.`
+
+ `android#&tracks=datasci&tracks=design&tracks=develop&mode=4&name=Past%20challenges:1 Uncaught (in promise) TypeError: Failed to fetch`
+
+ Same behaviour if we run `react-compenents` project or use https://www.topcoder-dev.com/challenges/
+- When we click on the saved custom filters, visually they restore the their settings, but no results are being shown. This issue comes from `react-component` package.
+- New warning in browser console:
+ `Warning: AutosizeInput: React.createClass is deprecated and will be removed in version 16. Use plain JavaScript classes instead. If you're not yet ready to migrate, create-react-class is available on npm as a drop-in replacement.`
+ This warning is produced by the `AutosizeInput` component which is used by `react-select` module. I've checked the repository of the later one, it's still alive and this issue https://github.com/JedWatson/react-select/issues/1661 has some moving lately . So hopefully this module can be updated soon and this warning will disappear. I think it doesn't worth to change the lib because of a warning taking into account the issue is being addressed in the repo.
+
+- There is and issue with `DateRangePicker` from `react-dates` produces errors in browser console during usage. But it still works.
+ The same errors are produced in `react-component` project and on https://www.topcoder-dev.com/challenges/.
+ I think it's not hard to fix, but as it come from react-components project, and not caused by migration, I'd like to skip it for now, as the scope of fixes during migration already is very big.
+- There are two warning are being showed during jest tests but they don't break any tests
+
+ `Warning: AutosizeInput: React.createClass is deprecated and will be removed in version 16. Use plain JavaScript classes instead. If you're not yet ready to migrate, create-react-class is available on npm as a drop-in replacement.`
+
+ `Warning: Accessing PropTypes via the main React package is deprecated. Use the prop-types package from npm instead.`
+
+ The first one is brought by `AutosizeInput` which was described in 1.
+ The second one can be brought by both `AutosizeInput` and `react-dates` as they both use them. For the `AutosizeInput` we are waiting for repo update, for the `react-dates` it looks like already fixed in the newer version. But I hope updating is out of the scope as I've tried it a little bit, but after spending some time without any luck to run it I came back to the version used in `react-components`.
diff --git a/docs/editor-notes.pdf b/docs/editor-notes.pdf
new file mode 100644
index 0000000000..bebc494d33
Binary files /dev/null and b/docs/editor-notes.pdf differ
diff --git a/docs/leaderboard-notes.md b/docs/leaderboard-notes.md
new file mode 100644
index 0000000000..d15d31ce03
--- /dev/null
+++ b/docs/leaderboard-notes.md
@@ -0,0 +1,180 @@
+
+# Submission: Top Coder Community Leaderboard
+
+## Submission overview
+
+- Video available on Youtube: https://www.youtube.com/watch?v=nhfSDt2PHMM&feature=youtu.be
+
+**Overview**
+
+- Replicated the leaderboard page (https://tco17.topcoder.com/development/leaderboard/) with similar styling
+- Added responsiveness, with same behavior as in the original page
+- No lint error introduced
+- Added test for each module that was added, with 100% coverage
+
+** Additional change**
+
+Updated the existing Avatar component to make it reusable, using the react-with-styles lib.
+See related forum thread: https://apps.topcoder.com/forums/?module=Thread&threadID=901317&start=0
+See implementation note below for details.
+
+**Added / updated files (excluding minor changes)**
+
+- shared/actions/leaderboard.js
+- shared/components/Avatar/
+- shared/components/Leaderboard/
+ - LeaderboardTable/
+ - Podium/
+ - PodiumSpot/
+ - avatarStyles.js
+- shared/reducers/leaderboard.js
+- shared/utils/withStyles.js
+
+
+## Verification steps
+
+1. Downloading source code, installing, applying patch, running the app
+
+```
+git clone https://github.com/topcoder-platform/community-app.git
+cd community-app
+git am -k ../submission.patch
+npm install
+```
+
+2. Checking lint errors (run `npm run lint` again)
+
+--> No error
+
+3. Checking test coverage
+
+Open ./community-app/__coverage__/lcov-report/index.html in a browser
+Check coverage on files listed above (should be all green)
+
+4. Checking UI (check https://tco17.topcoder.com/development/leaderboard/)
+ - Leaderboard page available from Example/Content
+ - Comparing UI feature to the provided example page
+ - Check responsiveness
+
+4. Code review / Implementation
+
+
+## Updating the Leaderboard: Switching to a real API
+
+- shared/actions/leaderboard.js
+ - Update .fetchLeaderboard()
+ - Remove .updateMockData()
+ - Remove ESlint comments at the top of the file
+- Update user properties, if necessary, in:
+ - shared/components/Leaderboard/LeaderboardTable/index.js
+ - shared/components/Leaderboard/PodiumSpot/index.js
+
+## Creating reusable components with react-with-styles
+
+The Avatar component was updated so it could be reusable. This was done using the react-with-styles module (https://github.com/airbnb/react-with-styles), which allows creation of themes and the usage of styling objects that can be passed to components though props to customize their appearance.
+
+The documentation explains in some details how to use the library, but to give a general overview, this is how react-with-styles works:
+
+- Create a module that exports an object with shared theme information (color palette, fonts, media queries, etc.)
+- Registering (initial configuration) in a withStyles.js file:
+ - Register your default theme
+ - Register your interface (Aphrodite, JSS, Radium, React Native)
+- Import your withStyles.js configuration file in a component; you will be able to access theme information and define inline styles with data passed through props
+
+The current implementation is simplest possible in order to be able to pass styling objects through props:
+
+- The withStyles.js configuration file has been placed under shared/utils/
+- No theme was defined, as it wasn't needed; The default theme has been registered with an empty object.
+- Arbitrarily, Aphrodite (https://github.com/khan/aphrodite) as been chosen for the inline styles interface, but other should work as well with the current implementation.
+
+**Implementation in the Avatar component**
+
+The Avatar component demonstrate the usage of inline styles with react-with-styles.
+The component includes a default styling object, which is used when no custom styling object hasn't been passed though props:
+
+```
+const defaultStyles = {
+ height: '32px',
+ width: '32px',
+ borderRadius: '16px',
+};
+ ```
+
+This object can be used in JSX to apply styling to an element as follow:
+
+```
+
+```
+
+The Avatar can be then used as any other component. It is for instance used that way in the TopcoderHeader component.
+
+A more interesting use case is in the leaderboard. In it default appearance, the Avatar is just a rounded image with a set height and width. But in the leaderboard, the size of the Avatar varies, both because of its use in different components and responsiveness, and it has a thick border which can be of different colors.
+Different styles have been defined to account for the different appearance the Avatar can have (see shared/components/Leaderboard/avatarStyles.js):
+
+```
+const baseAvatarStyles = {
+ height: '100%',
+ width: '100%',
+ borderRadius: '50%',
+ borderWidth: '5px',
+ borderStyle: 'solid',
+};
+
+const defaultAvatarModifier = {
+ borderColor: 'rgba(0, 0, 0, 0.05)',
+};
+
+const goldAvatarModifier = {
+ borderColor: '#fce217',
+};
+
+const silverAvatarModifier = {
+ borderColor: '#a9bcd4',
+};
+
+const bronzeAvatarModifier = {
+ borderColor: '#bd731e',
+};
+
+const defaultAvatarStyles = [baseAvatarStyles, defaultAvatarModifier];
+
+const goldAvatarStyles = [baseAvatarStyles, goldAvatarModifier];
+
+const silverAvatarStyles = [baseAvatarStyles, silverAvatarModifier];
+
+const bronzeAvatarStyles = [baseAvatarStyles, bronzeAvatarModifier];
+
+export {
+ defaultAvatarStyles,
+ goldAvatarStyles,
+ silverAvatarStyles,
+ bronzeAvatarStyles,
+};
+```
+
+Note that the width and height is set to 100%. The approach of letting the component be fluid, and having the layout control their size is recommended (see http://stackoverflow.com/questions/26882177/react-js-inline-style-best-practices) as is makes the components more adaptable and reusable.
+
+The the relevant styling object can be selected and passed to the Avatar by the component. For instance, in the PodiumSpot component, the proper styling object is selected based on the user rank (1, 2 or 3, to select a gold, silver or bronze Avatar):
+
+
+```
+import { goldAvatarStyles, silverAvatarStyles, bronzeAvatarStyles } from '../avatarStyles';
+
+...
+
+const CUSTOM_STYLES = {
+ 1: goldAvatarStyles,
+ 2: silverAvatarStyles,
+ 3: bronzeAvatarStyles,
+};
+
+...
+
+
+```
+
+**Useful resources**
+
+- https://github.com/airbnb/react-with-styles
+- https://github.com/airbnb/javascript/tree/master/css-in-javascript#themes
+- http://stackoverflow.com/questions/26882177/react-js-inline-style-best-practices
diff --git a/docs/why-reducer-factories-and-how-to-use-them.md b/docs/why-reducer-factories-and-how-to-use-them.md
new file mode 100644
index 0000000000..9f951d1caf
--- /dev/null
+++ b/docs/why-reducer-factories-and-how-to-use-them.md
@@ -0,0 +1,151 @@
+# Why Reducer Factories and How to Use Them?
+
+Just following documentation of [Redux](http://redux.js.org) and [redux-actions](https://github.com/acdlite/redux-actions) you would use `handleAction(type, reducer|reducerMap, defaultState)` or `handleActions(reducerMap, defaultState)` to define separate reducers for FSA ([Flux Standard Actions](https://github.com/acdlite/flux-standard-action)), and `combineReducers(reducers)` to combine them into the single reducer for your Redux store. These reducers define both the inial state of Redux store, and all possible mutations of the state in response to dispatched Redux actions.
+
+Now we want to support server-side rendering, i.e. in response to user's request for a page we want to generate entire page at the server-side and serve it to the user as one piece, rather then serving the basic template of the page and then making user to wait until the client-side loads all necessary data from the server. Reducer factories is one of alternative ways to make it work.
+
+The idea is simple: we wrap reducer definitions inside factory functions, which take the HTTP request object as an optional argument, and return a promise which resolves to the reducer. When the optional argument is provided, the factory assumes server-side rendering, and using any relevant data from the HTTP request, it takes care about generation of the initial state most appropriate for that request. When the argument is not provided, the factory assumes client-side rendering and uses the default initial state. This way we can conveniently blend-in any async operations into generation of the reducer for Redux store, and, in the case of server-side rendering, to optionally make the initial state of the store dependent on any data from the HTTP request. Also, there is no problem to use reducer factories along with simple reducers.
+
+A basic example of this idea can be found at the `/examples/data-fetch` route of the App located in this repo. In this example we just make a remote call to a public endpoint of Topcoder API to get the list of active challenges. There are two related actions, defined in [`/src/shared/actions/examples/data-fetch.js`](../src/shared/actions/examples/data-fetch.js):
+- `examples.dataFetch.fetchDataInit` just signals to the reducer that we start loading the data, thus we should drop any previously loaded data, and set the loading flag, which will tell React to show the loading indicator. This action does not assume any payload;
+- `examples.dataFetch.fetchDataDone` has as the payload the promise, which resolves to the fetched data. That promise is created with help of [fetch()](https://github.com/matthew-andrews/isomorphic-fetch). As we use [redux-promise](https://github.com/acdlite/redux-promise) middleware, when you dispatch this action, it does not arrive to the Redux store immediately, instead it is automatically delayed until the promise in its payload is resolved or rejected. Once it happens, the payload is automatically replaced with the result or error, the error flag of the action is set, and only after that the resulting FSA actually arrives to the store to be handled by reducers.
+
+The relevant reducer is defined inside [`/src/shared/reducers/examples/data-fetch.js`](../src/shared/reducers/examples/data-fetch.js). First of all, we have this code, which defines the reducer:
+
+```
+/**
+ * Handles examples.dataFetch.fetchDataDone action.
+ * @param {Object} state Previous state.
+ * @param {Object} action Action.
+ */
+function onDone(state, action) {
+ return {
+ ...state,
+ data: action.error ? null : action.payload,
+ failed: action.error,
+ loading: false,
+ };
+}
+
+/**
+ * Creates a new dataFetch reducer with the specified initial state.
+ * @param {Object} initialState Optional. Initial state.
+ * @return dataFetch reducer.
+ */
+function create(initialState) {
+ return handleActions({
+ [actions.examples.dataFetch.fetchDataInit](state) {
+ return {
+ ...state,
+ data: null,
+ failed: false,
+ loading: true,
+ };
+ },
+ [actions.examples.dataFetch.fetchDataDone]: onDone,
+ }, initialState || {});
+}
+```
+
+The default export from the module is just a simple reducer with the empty (`{}`) initial state:
+```
+/* Default reducer with empty initial state. */
+export default create();
+```
+
+The reducer factory is exported separately, and it is quite short and simple as it maximally re-uses existing code:
+```
+/**
+ * Factory which creates a new reducer with its initial state tailored to the
+ * ExpressJS HTTP request, if specified (for efficient server-side rendering).
+ * If HTTP request is not specified, it creates just the default reducer.
+ * @param {Object} req Optional. ExpressJS HTTP request.
+ * @return Promise which resolves to the new reducer.
+ */
+export function factory(req) {
+ if (req && req.url.endsWith('/examples/data-fetch/server')) {
+ return toFSA(actions.examples.dataFetch.fetchDataDone())
+ .then(res => create(onDone({}, res)));
+ }
+ return Promise.resolve(create());
+}
+```
+
+Let's see how it works line by line.
+
+1. We check whether the factory is called during server-side rendering, and whether we are rendering the route relevant to the section of Redux store's state, managed by this reducer:
+ ```
+ if (req && req.url.endsWith('/examples/data-fetch/server')) {
+ ```
+ if it is not the case, we just return the promise resolved to the reducer with empty initial state:
+ ```
+ return Promise.resolve(create());
+ ```
+
+2. To fetch relevant data we just use the action we already have:
+ ```
+ toFSA(actions.examples.dataFetch.fetchDataDone())
+ ```
+ `actions.examples.dataFetch.fetchDataDone()` triggers the API call and returns the action object with the API call promise as the payload. `toFSA(..)`, from our small helper library [`/src/shared/utils/redux.js`](../src/shared/utils/redux.js), transforms that action the same way as `redux-promise` middleware would do. Note that we don't dispatch this action, the Redux store is not even created at this point! The reason for this transform is that this way we can re-use the code from `onDone(..)` handler to easily create the intial state, and the resulting reducer as simple as:
+ ```
+ .then(res => create(onDone({}, res)));
+ ```
+
+ Sure, we don't have to re-use action handling code from reducer, when it does not makes sense. For example, the piece of code in this section can also be written as:
+ ```
+ actions.examples.dataFetch.fetchDataDone().payload
+ .then(res => res.json(), () => { failed: true })
+ .then(res => create({
+ data: res.data || null,
+ failed: Boolean(res.failed),
+ }));
+ ```
+
+Now, to combine reducers wrapped inside factories together with each other, and probably with simple reducers, we do something like this [`/src/shared/reducers/examples/index.js`](../src/shared/reducers/examples/index.js):
+```
+import { combineReducers } from 'redux';
+import { resolveReducers } from 'utils/redux';
+import { factory as dataFetchFactory } from './data-fetch';
+import { factory as anotherReducerFactory } from './another-reducer';
+import simpleReducer from './simpleReducer';
+
+export function factory(req) {
+ return resolveReducers({
+ dataFetch: dataFetchFactory(req),
+ anotherReducer: anotherReducerFactory(req),
+ }).then(reducers => combineReducers({
+ ...reducers,
+ simpleReducer,
+ }));
+}
+```
+
+### ... and what about the ReactJS side?
+
+Have a look into [`/src/shared/containers/examples/DataFetch/index.jsx`](../src/shared/containers/examples/DataFetch/index.jsx). ReactJS containers are still responsible for client-side loading of data in the case when user actions inside the running App change the current route. Related code looks like this:
+```
+componentDidMount() {
+ if (!this.props.data && !this.props.loading) this.props.loadData();
+}
+```
+We check that data necessary for this component are not in the state already, nor are being loaded currently, and if both true, we trigger the loading process. The way this container is connected to the Redux store's dispatch, `loadData()` automatically dispatches the pair of necessary actions:
+```
+loadData: () => {
+ dispatch(actions.examples.dataFetch.fetchDataInit());
+ dispatch(actions.examples.dataFetch.fetchDataDone());
+},
+```
+Placing the first piece of code inside `componentDidMount()` ensures that this code is executed only client-side, and it will work properly no matter whether the server-sider data loading is working properly or not.
+
+### Final note
+
+It may look that due to reducer factories we have dublicate data loading code both inside the component and inside the factory. A closer look reveal, however, that the code inside component and inside reducer factories is working differently. During client-side data loading we always should:
+
+1. Test that data have not been loaded already, nor are being loaded now;
+2. Write data-loading flag into the state, so that ReactJS can show loading indicator (1-st update of the UI);
+3. Load the data and write them into the state, thus triggering 2-nd update of UI.
+
+With our implementation of server-side loading we do just what we need in this case:
+1. Check HTTP request, to decide whether we need to load data for this page;
+2. We load the data and write them into the initial state;
+3. Then we create the store with the necessary intial state and render the page.
diff --git a/docs/wipro-community.md b/docs/wipro-community.md
new file mode 100644
index 0000000000..ba4e255b17
--- /dev/null
+++ b/docs/wipro-community.md
@@ -0,0 +1,43 @@
+# TOPCODER COMMUNITIES - FIRST ASSEMBLY
+
+## VIDEO
+Video on YouTube available by [link](https://youtu.be/oD3sO11hERM) only
+
+## DEPLOYMENT
+To run the project clone git repository https://github.com/topcoder-platform/community-app/tree/develop from the `develop` branch, commit #`d0e27b10143b48711ada642bd9982196ab68d09c`, and apply the patch from the submission archive.
+
+Deployment of the project hasn't been changed, just refer to the project's readme for details.
+
+Basically it's `npm i`, `npm run dev` and open http://local.topcoder-dev.com:3000/ which supposed to point the `127.0.0.1` inside the system's hosts file.
+
+### NOTES
+- There could be warnings about 6 lines add whitespace errors during git patch applying. But all these lines are inside snapshot files, so this code is being generated automatically.
+- ~~Project has to run using domain `http://local.topcoder-dev.com:3000/`. It's because we use server not only to serve frontend but also for demo API.
+ Though for API we have to define absolute paths to work properly on the server side.~~
+- For reviewing purpose it's preferable to run `npm run dev` because in dev mode there are more errors can be seen in browser console, in particular server-rendering warnings.
+
+## VERIFICATION
+
+### Linting and tests
+All tests for altered components was adjusted.
+- To check lint errors, run `npm run lint`
+- To perform tests, run `npm run jest`
+
+### Functionality
+1. Content page http://local.topcoder-dev.com:3000/ has links to all the pages of the introduced Wipro community: [Home](http://local.topcoder-dev.com:3000/community/wipro/home), [Learning & Certification](http://local.topcoder-dev.com:3000/community/wipro/about), [Challenges](http://local.topcoder-dev.com:3000/community/wipro/challenges) and [Leaderboard](http://local.topcoder-dev.com:3000/community/wipro/leaderboard). There are also examples of [non-existent community page](http://local.topcoder-dev.com:3000/community/wipro/404) and [non-existent community](http://local.topcoder-dev.com:3000/community/404/home).
+2. All of the examples can be opened by a direct link to check that server-rendering is correct. Leaderborad page uses Avatar component which breaks server-rendering though by itself this component doesn't introduce any server-rendering issues when using inside community.
+
+#### ~~Leaderboard page server rendering~~
+~~To avoid appearing server-rendering error the easiest way is to slightly modify Avatar component so it doesn't use themr. We just need to provide constant strings to className properties instead of themr object variables, like this:~~
+```jsx
+function Avatar({ theme, url }) {
+ return url
+ ?
+ : ;
+}
+```
+~~After that we can re-run `npm run dev` and check that [Leaderboard](http://local.topcoder-dev.com:3000/community/wipro/leaderboard) page doesn't have server-rendering errors anymore. For sure visual styles of avatar will be broken this time.~~
+
+### Additionally
+- fix leaderboard server-rendering (before there was another small issue in this component not related to Avatar, `failed` property in store got undefined value, which wasn't rendered to the store on the server)
+- fix `ChallengeListingExample` component (before each time it was created, keyword list and subtracks list was populated with entities again, which lead to duplicates)
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000..a6b3b40d0c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,116 @@
+{
+ "name": "community-app",
+ "version": "1.0.0",
+ "description": "Topcoder community website",
+ "main": "index.js",
+ "scripts": {
+ "build": "./node_modules/.bin/webpack --env=production --progress --profile --colors",
+ "build:dev": "./node_modules/.bin/webpack --env=development --progress --profile --colors",
+ "dev": "DEV_TOOLS=true node ./bin/www",
+ "fix:js": "eslint --fix --ext .js,.jsx .",
+ "fix:styles": "stylefmt -r **/*.scss",
+ "jest": "jest --maxWorkers=4 --config config/jest/default.js",
+ "lint": "npm run lint:js; npm run lint:scss",
+ "lint:js": "eslint --ext .js,.jsx .",
+ "lint:scss": "stylelint **/*.scss",
+ "postinstall": "npm run build",
+ "start": "node ./bin/www",
+ "test": "npm run lint; npm run jest"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/topcoder-platform/community-app.git"
+ },
+ "author": "",
+ "license": "UNLICENSED",
+ "private": true,
+ "bugs": {
+ "url": "https://github.com/topcoder-platform/community-app/issues"
+ },
+ "homepage": "https://github.com/topcoder-platform/community-app#readme",
+ "engines": {
+ "node": "^6.10.0"
+ },
+ "dependencies": {
+ "atob": "^2.0.3",
+ "autoprefixer": "^7.1.0",
+ "babel-loader": "^6.4.0",
+ "babel-plugin-css-modules-transform": "^1.2.7",
+ "babel-plugin-inline-react-svg": "^0.3.1",
+ "babel-plugin-module-alias": "^1.6.0",
+ "babel-plugin-module-resolver": "^2.5.0",
+ "babel-plugin-react-css-modules": "^2.6.0",
+ "babel-preset-env": "^1.2.1",
+ "babel-preset-react": "^6.23.0",
+ "babel-preset-stage-2": "^6.22.0",
+ "babel-register": "^6.24.0",
+ "body-parser": "^1.17.1",
+ "bootstrap": "^3.3.7",
+ "browser-cookies": "^1.1.0",
+ "classnames": "^2.2.5",
+ "config": "^1.25.1",
+ "cookie-parser": "^1.4.3",
+ "copy-webpack-plugin": "^4.0.1",
+ "css-loader": "^0.27.3",
+ "debug": "^2.6.3",
+ "eslint": "^3.19.0",
+ "eslint-config-airbnb": "^15.0.1",
+ "eslint-import-resolver-babel-module": "^3.0.0",
+ "eslint-plugin-import": "^2.3.0",
+ "eslint-plugin-jest": "^20.0.0",
+ "eslint-plugin-jsx-a11y": "^5.0.3",
+ "eslint-plugin-react": "^7.0.1",
+ "express": "^4.15.2",
+ "extract-text-webpack-plugin": "^2.1.0",
+ "file-loader": "^0.10.1",
+ "identity-obj-proxy": "^3.0.0",
+ "isomorphic-fetch": "^2.2.1",
+ "jest": "^20.0.0",
+ "jquery": "^3.2.1",
+ "lodash": "^4.17.4",
+ "moment": "^2.18.1",
+ "morgan": "^1.8.1",
+ "node-sass": "^4.5.0",
+ "optimize-css-assets-webpack-plugin": "^1.3.0",
+ "postcss-loader": "^1.3.3",
+ "postcss-scss": "^0.4.1",
+ "prop-types": "^15.5.8",
+ "rc-tooltip": "^3.4.3",
+ "react": "^15.4.2",
+ "react-css-themr": "^2.1.2",
+ "react-dates": "^4.3.0",
+ "react-dom": "^15.4.2",
+ "react-dropdown": "^1.2.1",
+ "react-hot-loader": "^3.0.0-beta.6",
+ "react-redux": "^5.0.3",
+ "react-router-dom": "^4.0.0",
+ "react-select": "^1.0.0-rc.3",
+ "react-stickynode": "^1.3.1",
+ "react-test-renderer": "^15.4.2",
+ "react-waypoint": "^6.0.0",
+ "redux": "^3.6.0",
+ "redux-actions": "^2.0.1",
+ "redux-promise": "^0.5.3",
+ "resolve-url-loader": "^2.0.2",
+ "sass-loader": "^6.0.3",
+ "serialize-javascript": "^1.3.0",
+ "serve-favicon": "^2.4.1",
+ "shortid": "^2.2.8",
+ "style-loader": "^0.14.0",
+ "stylefmt": "^6.0.0",
+ "stylelint": "^7.9.0",
+ "stylelint-config-standard": "^16.0.0",
+ "tc-accounts": "https://github.com/appirio-tech/accounts-app.git#dev",
+ "uuid": "^3.0.1",
+ "webpack": "^2.2.1",
+ "webpack-merge": "^4.0.0"
+ },
+ "devDependencies": {
+ "redux-devtools": "^3.3.2",
+ "redux-devtools-dock-monitor": "^1.1.1",
+ "redux-devtools-log-monitor": "^1.2.0",
+ "stylefmt": "^5.3.0",
+ "webpack-dev-middleware": "^1.10.1",
+ "webpack-hot-middleware": "^2.17.1"
+ }
+}
diff --git a/src/assets/fonts/GEInsp/GEInspIt.eot b/src/assets/fonts/GEInsp/GEInspIt.eot
new file mode 100644
index 0000000000..8f26c59e97
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspIt.eot differ
diff --git a/src/assets/fonts/GEInsp/GEInspIt.ttf b/src/assets/fonts/GEInsp/GEInspIt.ttf
new file mode 100644
index 0000000000..95a6a220cd
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspIt.ttf differ
diff --git a/src/assets/fonts/GEInsp/GEInspIt.woff b/src/assets/fonts/GEInsp/GEInspIt.woff
new file mode 100644
index 0000000000..1375c8a72e
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspIt.woff differ
diff --git a/src/assets/fonts/GEInsp/GEInspMd.eot b/src/assets/fonts/GEInsp/GEInspMd.eot
new file mode 100644
index 0000000000..c1a38eb19b
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspMd.eot differ
diff --git a/src/assets/fonts/GEInsp/GEInspMd.ttf b/src/assets/fonts/GEInsp/GEInspMd.ttf
new file mode 100644
index 0000000000..bb88a8276e
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspMd.ttf differ
diff --git a/src/assets/fonts/GEInsp/GEInspMd.woff b/src/assets/fonts/GEInsp/GEInspMd.woff
new file mode 100644
index 0000000000..07b765716d
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspMd.woff differ
diff --git a/src/assets/fonts/GEInsp/GEInspMdIt.eot b/src/assets/fonts/GEInsp/GEInspMdIt.eot
new file mode 100644
index 0000000000..3d706f4fb5
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspMdIt.eot differ
diff --git a/src/assets/fonts/GEInsp/GEInspMdIt.ttf b/src/assets/fonts/GEInsp/GEInspMdIt.ttf
new file mode 100644
index 0000000000..b1af39c440
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspMdIt.ttf differ
diff --git a/src/assets/fonts/GEInsp/GEInspMdIt.woff b/src/assets/fonts/GEInsp/GEInspMdIt.woff
new file mode 100644
index 0000000000..5a6946b98f
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspMdIt.woff differ
diff --git a/src/assets/fonts/GEInsp/GEInspRg.eot b/src/assets/fonts/GEInsp/GEInspRg.eot
new file mode 100644
index 0000000000..4fabc33ebe
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspRg.eot differ
diff --git a/src/assets/fonts/GEInsp/GEInspRg.ttf b/src/assets/fonts/GEInsp/GEInspRg.ttf
new file mode 100644
index 0000000000..e323ca8d06
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspRg.ttf differ
diff --git a/src/assets/fonts/GEInsp/GEInspRg.woff b/src/assets/fonts/GEInsp/GEInspRg.woff
new file mode 100644
index 0000000000..49d6f42399
Binary files /dev/null and b/src/assets/fonts/GEInsp/GEInspRg.woff differ
diff --git a/src/assets/fonts/GESans/GESans-Bold.otf b/src/assets/fonts/GESans/GESans-Bold.otf
new file mode 100644
index 0000000000..5f5fc39014
Binary files /dev/null and b/src/assets/fonts/GESans/GESans-Bold.otf differ
diff --git a/src/assets/fonts/GESans/GESans-BoldItalic.otf b/src/assets/fonts/GESans/GESans-BoldItalic.otf
new file mode 100644
index 0000000000..a5743d4def
Binary files /dev/null and b/src/assets/fonts/GESans/GESans-BoldItalic.otf differ
diff --git a/src/assets/fonts/GESans/GESans-Italic.otf b/src/assets/fonts/GESans/GESans-Italic.otf
new file mode 100644
index 0000000000..b3d38227a7
Binary files /dev/null and b/src/assets/fonts/GESans/GESans-Italic.otf differ
diff --git a/src/assets/fonts/GESans/GESans-Regular.otf b/src/assets/fonts/GESans/GESans-Regular.otf
new file mode 100644
index 0000000000..9626245596
Binary files /dev/null and b/src/assets/fonts/GESans/GESans-Regular.otf differ
diff --git a/src/assets/fonts/opensans/LICENSE.txt b/src/assets/fonts/opensans/LICENSE.txt
new file mode 100755
index 0000000000..75b52484ea
--- /dev/null
+++ b/src/assets/fonts/opensans/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/src/assets/fonts/opensans/opensans-bold-webfont.eot b/src/assets/fonts/opensans/opensans-bold-webfont.eot
new file mode 100755
index 0000000000..81668d62ec
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-bold-webfont.eot differ
diff --git a/src/assets/fonts/opensans/opensans-bold-webfont.svg b/src/assets/fonts/opensans/opensans-bold-webfont.svg
new file mode 100755
index 0000000000..1b5c1ccb78
--- /dev/null
+++ b/src/assets/fonts/opensans/opensans-bold-webfont.svg
@@ -0,0 +1,1824 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/opensans/opensans-bold-webfont.ttf b/src/assets/fonts/opensans/opensans-bold-webfont.ttf
new file mode 100755
index 0000000000..8a28567635
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-bold-webfont.ttf differ
diff --git a/src/assets/fonts/opensans/opensans-bold-webfont.woff b/src/assets/fonts/opensans/opensans-bold-webfont.woff
new file mode 100755
index 0000000000..4edb5819e7
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-bold-webfont.woff differ
diff --git a/src/assets/fonts/opensans/opensans-bolditalic-webfont.eot b/src/assets/fonts/opensans/opensans-bolditalic-webfont.eot
new file mode 100755
index 0000000000..b827d674c9
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-bolditalic-webfont.eot differ
diff --git a/src/assets/fonts/opensans/opensans-bolditalic-webfont.svg b/src/assets/fonts/opensans/opensans-bolditalic-webfont.svg
new file mode 100755
index 0000000000..7d483577fe
--- /dev/null
+++ b/src/assets/fonts/opensans/opensans-bolditalic-webfont.svg
@@ -0,0 +1,1824 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/opensans/opensans-bolditalic-webfont.ttf b/src/assets/fonts/opensans/opensans-bolditalic-webfont.ttf
new file mode 100755
index 0000000000..dc0828ba5b
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-bolditalic-webfont.ttf differ
diff --git a/src/assets/fonts/opensans/opensans-bolditalic-webfont.woff b/src/assets/fonts/opensans/opensans-bolditalic-webfont.woff
new file mode 100755
index 0000000000..d024607f84
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-bolditalic-webfont.woff differ
diff --git a/src/assets/fonts/opensans/opensans-extrabold-webfont.eot b/src/assets/fonts/opensans/opensans-extrabold-webfont.eot
new file mode 100755
index 0000000000..16d328cf80
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-extrabold-webfont.eot differ
diff --git a/src/assets/fonts/opensans/opensans-extrabold-webfont.svg b/src/assets/fonts/opensans/opensans-extrabold-webfont.svg
new file mode 100755
index 0000000000..e763c4a497
--- /dev/null
+++ b/src/assets/fonts/opensans/opensans-extrabold-webfont.svg
@@ -0,0 +1,1824 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/opensans/opensans-extrabold-webfont.ttf b/src/assets/fonts/opensans/opensans-extrabold-webfont.ttf
new file mode 100755
index 0000000000..4c0f46c593
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-extrabold-webfont.ttf differ
diff --git a/src/assets/fonts/opensans/opensans-extrabold-webfont.woff b/src/assets/fonts/opensans/opensans-extrabold-webfont.woff
new file mode 100755
index 0000000000..00152bbb72
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-extrabold-webfont.woff differ
diff --git a/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.eot b/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.eot
new file mode 100755
index 0000000000..b5ccf80ec3
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.eot differ
diff --git a/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.svg b/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.svg
new file mode 100755
index 0000000000..6c6dce8547
--- /dev/null
+++ b/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.svg
@@ -0,0 +1,1824 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.ttf b/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.ttf
new file mode 100755
index 0000000000..b22ad3c262
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.ttf differ
diff --git a/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.woff b/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.woff
new file mode 100755
index 0000000000..34abe47eac
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-extrabolditalic-webfont.woff differ
diff --git a/src/assets/fonts/opensans/opensans-italic-webfont.eot b/src/assets/fonts/opensans/opensans-italic-webfont.eot
new file mode 100755
index 0000000000..d78e933123
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-italic-webfont.eot differ
diff --git a/src/assets/fonts/opensans/opensans-italic-webfont.svg b/src/assets/fonts/opensans/opensans-italic-webfont.svg
new file mode 100755
index 0000000000..930528f495
--- /dev/null
+++ b/src/assets/fonts/opensans/opensans-italic-webfont.svg
@@ -0,0 +1,1824 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/opensans/opensans-italic-webfont.ttf b/src/assets/fonts/opensans/opensans-italic-webfont.ttf
new file mode 100755
index 0000000000..33013c51c1
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-italic-webfont.ttf differ
diff --git a/src/assets/fonts/opensans/opensans-italic-webfont.woff b/src/assets/fonts/opensans/opensans-italic-webfont.woff
new file mode 100755
index 0000000000..19c4c78511
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-italic-webfont.woff differ
diff --git a/src/assets/fonts/opensans/opensans-light-webfont.eot b/src/assets/fonts/opensans/opensans-light-webfont.eot
new file mode 100755
index 0000000000..103d20c8e4
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-light-webfont.eot differ
diff --git a/src/assets/fonts/opensans/opensans-light-webfont.svg b/src/assets/fonts/opensans/opensans-light-webfont.svg
new file mode 100755
index 0000000000..b3e3faabc3
--- /dev/null
+++ b/src/assets/fonts/opensans/opensans-light-webfont.svg
@@ -0,0 +1,1824 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/opensans/opensans-light-webfont.ttf b/src/assets/fonts/opensans/opensans-light-webfont.ttf
new file mode 100755
index 0000000000..3a3ba18df1
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-light-webfont.ttf differ
diff --git a/src/assets/fonts/opensans/opensans-light-webfont.woff b/src/assets/fonts/opensans/opensans-light-webfont.woff
new file mode 100755
index 0000000000..091f524a95
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-light-webfont.woff differ
diff --git a/src/assets/fonts/opensans/opensans-lightitalic-webfont.eot b/src/assets/fonts/opensans/opensans-lightitalic-webfont.eot
new file mode 100755
index 0000000000..1a494c3a6b
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-lightitalic-webfont.eot differ
diff --git a/src/assets/fonts/opensans/opensans-lightitalic-webfont.svg b/src/assets/fonts/opensans/opensans-lightitalic-webfont.svg
new file mode 100755
index 0000000000..4f3a047331
--- /dev/null
+++ b/src/assets/fonts/opensans/opensans-lightitalic-webfont.svg
@@ -0,0 +1,1824 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/opensans/opensans-lightitalic-webfont.ttf b/src/assets/fonts/opensans/opensans-lightitalic-webfont.ttf
new file mode 100755
index 0000000000..ea4ea2519a
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-lightitalic-webfont.ttf differ
diff --git a/src/assets/fonts/opensans/opensans-lightitalic-webfont.woff b/src/assets/fonts/opensans/opensans-lightitalic-webfont.woff
new file mode 100755
index 0000000000..250e2a8e84
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-lightitalic-webfont.woff differ
diff --git a/src/assets/fonts/opensans/opensans-regular-webfont.eot b/src/assets/fonts/opensans/opensans-regular-webfont.eot
new file mode 100755
index 0000000000..863ae1939d
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-regular-webfont.eot differ
diff --git a/src/assets/fonts/opensans/opensans-regular-webfont.svg b/src/assets/fonts/opensans/opensans-regular-webfont.svg
new file mode 100755
index 0000000000..3e5d0ac377
--- /dev/null
+++ b/src/assets/fonts/opensans/opensans-regular-webfont.svg
@@ -0,0 +1,1824 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/opensans/opensans-regular-webfont.ttf b/src/assets/fonts/opensans/opensans-regular-webfont.ttf
new file mode 100755
index 0000000000..ee7fb2910e
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-regular-webfont.ttf differ
diff --git a/src/assets/fonts/opensans/opensans-regular-webfont.woff b/src/assets/fonts/opensans/opensans-regular-webfont.woff
new file mode 100755
index 0000000000..cd4decfbcf
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-regular-webfont.woff differ
diff --git a/src/assets/fonts/opensans/opensans-semibold-webfont.eot b/src/assets/fonts/opensans/opensans-semibold-webfont.eot
new file mode 100755
index 0000000000..6a61da0650
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-semibold-webfont.eot differ
diff --git a/src/assets/fonts/opensans/opensans-semibold-webfont.svg b/src/assets/fonts/opensans/opensans-semibold-webfont.svg
new file mode 100755
index 0000000000..b94dca7582
--- /dev/null
+++ b/src/assets/fonts/opensans/opensans-semibold-webfont.svg
@@ -0,0 +1,1824 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/opensans/opensans-semibold-webfont.ttf b/src/assets/fonts/opensans/opensans-semibold-webfont.ttf
new file mode 100755
index 0000000000..ea15de0481
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-semibold-webfont.ttf differ
diff --git a/src/assets/fonts/opensans/opensans-semibold-webfont.woff b/src/assets/fonts/opensans/opensans-semibold-webfont.woff
new file mode 100755
index 0000000000..d18635f832
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-semibold-webfont.woff differ
diff --git a/src/assets/fonts/opensans/opensans-semibolditalic-webfont.eot b/src/assets/fonts/opensans/opensans-semibolditalic-webfont.eot
new file mode 100755
index 0000000000..19fec09ce1
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-semibolditalic-webfont.eot differ
diff --git a/src/assets/fonts/opensans/opensans-semibolditalic-webfont.svg b/src/assets/fonts/opensans/opensans-semibolditalic-webfont.svg
new file mode 100755
index 0000000000..a687cd0871
--- /dev/null
+++ b/src/assets/fonts/opensans/opensans-semibolditalic-webfont.svg
@@ -0,0 +1,1824 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/opensans/opensans-semibolditalic-webfont.ttf b/src/assets/fonts/opensans/opensans-semibolditalic-webfont.ttf
new file mode 100755
index 0000000000..8e62e64511
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-semibolditalic-webfont.ttf differ
diff --git a/src/assets/fonts/opensans/opensans-semibolditalic-webfont.woff b/src/assets/fonts/opensans/opensans-semibolditalic-webfont.woff
new file mode 100755
index 0000000000..48e87f0b2b
Binary files /dev/null and b/src/assets/fonts/opensans/opensans-semibolditalic-webfont.woff differ
diff --git a/src/assets/fonts/roboto/apache-license.txt b/src/assets/fonts/roboto/apache-license.txt
new file mode 100755
index 0000000000..63d530249b
--- /dev/null
+++ b/src/assets/fonts/roboto/apache-license.txt
@@ -0,0 +1,203 @@
+Font data copyright Google 2012
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-black.eot b/src/assets/fonts/roboto/roboto-black.eot
new file mode 100755
index 0000000000..fa326d1d12
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-black.eot differ
diff --git a/src/assets/fonts/roboto/roboto-black.svg b/src/assets/fonts/roboto/roboto-black.svg
new file mode 100755
index 0000000000..945dec6cbd
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-black.svg
@@ -0,0 +1,642 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-black.ttf b/src/assets/fonts/roboto/roboto-black.ttf
new file mode 100755
index 0000000000..3c3b2b8ae6
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-black.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-black.woff b/src/assets/fonts/roboto/roboto-black.woff
new file mode 100755
index 0000000000..0229086571
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-black.woff differ
diff --git a/src/assets/fonts/roboto/roboto-blackitalic.eot b/src/assets/fonts/roboto/roboto-blackitalic.eot
new file mode 100755
index 0000000000..a2aebfb7da
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-blackitalic.eot differ
diff --git a/src/assets/fonts/roboto/roboto-blackitalic.svg b/src/assets/fonts/roboto/roboto-blackitalic.svg
new file mode 100755
index 0000000000..c9cc3cdb7b
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-blackitalic.svg
@@ -0,0 +1,642 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-blackitalic.ttf b/src/assets/fonts/roboto/roboto-blackitalic.ttf
new file mode 100755
index 0000000000..2020dcbc9c
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-blackitalic.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-blackitalic.woff b/src/assets/fonts/roboto/roboto-blackitalic.woff
new file mode 100755
index 0000000000..1875c0b950
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-blackitalic.woff differ
diff --git a/src/assets/fonts/roboto/roboto-bold.eot b/src/assets/fonts/roboto/roboto-bold.eot
new file mode 100755
index 0000000000..b73776ee3b
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-bold.eot differ
diff --git a/src/assets/fonts/roboto/roboto-bold.svg b/src/assets/fonts/roboto/roboto-bold.svg
new file mode 100755
index 0000000000..43b5ed2222
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-bold.svg
@@ -0,0 +1,593 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-bold.ttf b/src/assets/fonts/roboto/roboto-bold.ttf
new file mode 100755
index 0000000000..1da72769a8
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-bold.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-bold.woff b/src/assets/fonts/roboto/roboto-bold.woff
new file mode 100755
index 0000000000..0c6994871e
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-bold.woff differ
diff --git a/src/assets/fonts/roboto/roboto-bolditalic.eot b/src/assets/fonts/roboto/roboto-bolditalic.eot
new file mode 100755
index 0000000000..b803ec1687
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-bolditalic.eot differ
diff --git a/src/assets/fonts/roboto/roboto-bolditalic.svg b/src/assets/fonts/roboto/roboto-bolditalic.svg
new file mode 100755
index 0000000000..f877a3c821
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-bolditalic.svg
@@ -0,0 +1,642 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-bolditalic.ttf b/src/assets/fonts/roboto/roboto-bolditalic.ttf
new file mode 100755
index 0000000000..78bab05c8c
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-bolditalic.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-bolditalic.woff b/src/assets/fonts/roboto/roboto-bolditalic.woff
new file mode 100755
index 0000000000..99de61af52
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-bolditalic.woff differ
diff --git a/src/assets/fonts/roboto/roboto-italic.eot b/src/assets/fonts/roboto/roboto-italic.eot
new file mode 100755
index 0000000000..b708f047ff
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-italic.eot differ
diff --git a/src/assets/fonts/roboto/roboto-italic.svg b/src/assets/fonts/roboto/roboto-italic.svg
new file mode 100755
index 0000000000..49ddd4ab76
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-italic.svg
@@ -0,0 +1,642 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-italic.ttf b/src/assets/fonts/roboto/roboto-italic.ttf
new file mode 100755
index 0000000000..ae258e8416
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-italic.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-italic.woff b/src/assets/fonts/roboto/roboto-italic.woff
new file mode 100755
index 0000000000..dd74244382
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-italic.woff differ
diff --git a/src/assets/fonts/roboto/roboto-light.eot b/src/assets/fonts/roboto/roboto-light.eot
new file mode 100755
index 0000000000..072cdc480c
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-light.eot differ
diff --git a/src/assets/fonts/roboto/roboto-light.svg b/src/assets/fonts/roboto/roboto-light.svg
new file mode 100755
index 0000000000..db6a6171ed
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-light.svg
@@ -0,0 +1,641 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-light.ttf b/src/assets/fonts/roboto/roboto-light.ttf
new file mode 100755
index 0000000000..3b2fea0ace
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-light.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-light.woff b/src/assets/fonts/roboto/roboto-light.woff
new file mode 100755
index 0000000000..cc534a3815
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-light.woff differ
diff --git a/src/assets/fonts/roboto/roboto-lightitalic.eot b/src/assets/fonts/roboto/roboto-lightitalic.eot
new file mode 100755
index 0000000000..77396a1ff9
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-lightitalic.eot differ
diff --git a/src/assets/fonts/roboto/roboto-lightitalic.svg b/src/assets/fonts/roboto/roboto-lightitalic.svg
new file mode 100755
index 0000000000..4bd14bcca7
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-lightitalic.svg
@@ -0,0 +1,641 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-lightitalic.ttf b/src/assets/fonts/roboto/roboto-lightitalic.ttf
new file mode 100755
index 0000000000..b9b38118a3
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-lightitalic.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-lightitalic.woff b/src/assets/fonts/roboto/roboto-lightitalic.woff
new file mode 100755
index 0000000000..3071ff4f23
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-lightitalic.woff differ
diff --git a/src/assets/fonts/roboto/roboto-medium.eot b/src/assets/fonts/roboto/roboto-medium.eot
new file mode 100755
index 0000000000..f9ad99566d
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-medium.eot differ
diff --git a/src/assets/fonts/roboto/roboto-medium.svg b/src/assets/fonts/roboto/roboto-medium.svg
new file mode 100755
index 0000000000..4ce289dfa4
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-medium.svg
@@ -0,0 +1,593 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-medium.ttf b/src/assets/fonts/roboto/roboto-medium.ttf
new file mode 100755
index 0000000000..8aa64d8232
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-medium.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-medium.woff b/src/assets/fonts/roboto/roboto-medium.woff
new file mode 100755
index 0000000000..cd810ef929
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-medium.woff differ
diff --git a/src/assets/fonts/roboto/roboto-mediumitalic.eot b/src/assets/fonts/roboto/roboto-mediumitalic.eot
new file mode 100755
index 0000000000..a03fe4b248
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-mediumitalic.eot differ
diff --git a/src/assets/fonts/roboto/roboto-mediumitalic.svg b/src/assets/fonts/roboto/roboto-mediumitalic.svg
new file mode 100755
index 0000000000..904d7c5280
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-mediumitalic.svg
@@ -0,0 +1,642 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-mediumitalic.ttf b/src/assets/fonts/roboto/roboto-mediumitalic.ttf
new file mode 100755
index 0000000000..6439927f16
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-mediumitalic.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-mediumitalic.woff b/src/assets/fonts/roboto/roboto-mediumitalic.woff
new file mode 100755
index 0000000000..69a1458011
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-mediumitalic.woff differ
diff --git a/src/assets/fonts/roboto/roboto-regular.eot b/src/assets/fonts/roboto/roboto-regular.eot
new file mode 100755
index 0000000000..9b5e8e4138
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-regular.eot differ
diff --git a/src/assets/fonts/roboto/roboto-regular.svg b/src/assets/fonts/roboto/roboto-regular.svg
new file mode 100755
index 0000000000..de7d77fea5
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-regular.svg
@@ -0,0 +1,621 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-regular.ttf b/src/assets/fonts/roboto/roboto-regular.ttf
new file mode 100755
index 0000000000..44dd78d5e1
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-regular.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-regular.woff b/src/assets/fonts/roboto/roboto-regular.woff
new file mode 100755
index 0000000000..bfa05d53f4
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-regular.woff differ
diff --git a/src/assets/fonts/roboto/roboto-thin.eot b/src/assets/fonts/roboto/roboto-thin.eot
new file mode 100755
index 0000000000..2284a3b3ef
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-thin.eot differ
diff --git a/src/assets/fonts/roboto/roboto-thin.svg b/src/assets/fonts/roboto/roboto-thin.svg
new file mode 100755
index 0000000000..7394e3d0a6
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-thin.svg
@@ -0,0 +1,631 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-thin.ttf b/src/assets/fonts/roboto/roboto-thin.ttf
new file mode 100755
index 0000000000..18919f7a96
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-thin.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-thin.woff b/src/assets/fonts/roboto/roboto-thin.woff
new file mode 100755
index 0000000000..f10b831e85
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-thin.woff differ
diff --git a/src/assets/fonts/roboto/roboto-thinitalic.eot b/src/assets/fonts/roboto/roboto-thinitalic.eot
new file mode 100755
index 0000000000..e6291f2657
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-thinitalic.eot differ
diff --git a/src/assets/fonts/roboto/roboto-thinitalic.svg b/src/assets/fonts/roboto/roboto-thinitalic.svg
new file mode 100755
index 0000000000..951cccd9dc
--- /dev/null
+++ b/src/assets/fonts/roboto/roboto-thinitalic.svg
@@ -0,0 +1,631 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/roboto/roboto-thinitalic.ttf b/src/assets/fonts/roboto/roboto-thinitalic.ttf
new file mode 100755
index 0000000000..a4e7ae08e0
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-thinitalic.ttf differ
diff --git a/src/assets/fonts/roboto/roboto-thinitalic.woff b/src/assets/fonts/roboto/roboto-thinitalic.woff
new file mode 100755
index 0000000000..9ef17a8681
Binary files /dev/null and b/src/assets/fonts/roboto/roboto-thinitalic.woff differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.eot b/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.eot
new file mode 100755
index 0000000000..c70c63ac9c
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.eot differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.svg b/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.svg
new file mode 100755
index 0000000000..786dda605d
--- /dev/null
+++ b/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.svg
@@ -0,0 +1,1427 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.ttf b/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.ttf
new file mode 100755
index 0000000000..401af8a73e
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.ttf differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.woff b/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.woff
new file mode 100755
index 0000000000..2bf730dadd
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.woff differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.woff2 b/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.woff2
new file mode 100755
index 0000000000..281d5d9e7f
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-bold-italic-webfont.woff2 differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-bold-webfont.eot b/src/assets/fonts/sofiapro/sofiapro-bold-webfont.eot
new file mode 100755
index 0000000000..839cea12c8
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-bold-webfont.eot differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-bold-webfont.svg b/src/assets/fonts/sofiapro/sofiapro-bold-webfont.svg
new file mode 100755
index 0000000000..79f34529c4
--- /dev/null
+++ b/src/assets/fonts/sofiapro/sofiapro-bold-webfont.svg
@@ -0,0 +1,1427 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/sofiapro/sofiapro-bold-webfont.ttf b/src/assets/fonts/sofiapro/sofiapro-bold-webfont.ttf
new file mode 100755
index 0000000000..119d6c9277
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-bold-webfont.ttf differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-bold-webfont.woff b/src/assets/fonts/sofiapro/sofiapro-bold-webfont.woff
new file mode 100755
index 0000000000..54454a171c
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-bold-webfont.woff differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-bold-webfont.woff2 b/src/assets/fonts/sofiapro/sofiapro-bold-webfont.woff2
new file mode 100755
index 0000000000..2ee2c99ad2
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-bold-webfont.woff2 differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.eot b/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.eot
new file mode 100755
index 0000000000..f068e4c2df
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.eot differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.svg b/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.svg
new file mode 100755
index 0000000000..cc28354df7
--- /dev/null
+++ b/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.svg
@@ -0,0 +1,1375 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.ttf b/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.ttf
new file mode 100755
index 0000000000..f8e4b76a06
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.ttf differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.woff b/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.woff
new file mode 100755
index 0000000000..fc841ee6ff
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.woff differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.woff2 b/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.woff2
new file mode 100755
index 0000000000..90915a2508
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-light-italic-webfont.woff2 differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-light-webfont.eot b/src/assets/fonts/sofiapro/sofiapro-light-webfont.eot
new file mode 100755
index 0000000000..1c3401824f
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-light-webfont.eot differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-light-webfont.svg b/src/assets/fonts/sofiapro/sofiapro-light-webfont.svg
new file mode 100755
index 0000000000..4c930790f5
--- /dev/null
+++ b/src/assets/fonts/sofiapro/sofiapro-light-webfont.svg
@@ -0,0 +1,1375 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/sofiapro/sofiapro-light-webfont.ttf b/src/assets/fonts/sofiapro/sofiapro-light-webfont.ttf
new file mode 100755
index 0000000000..327d20b83c
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-light-webfont.ttf differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-light-webfont.woff b/src/assets/fonts/sofiapro/sofiapro-light-webfont.woff
new file mode 100755
index 0000000000..897e02aabc
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-light-webfont.woff differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-light-webfont.woff2 b/src/assets/fonts/sofiapro/sofiapro-light-webfont.woff2
new file mode 100755
index 0000000000..4cf0b2a8cf
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-light-webfont.woff2 differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.eot b/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.eot
new file mode 100755
index 0000000000..ea509c2f44
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.eot differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.svg b/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.svg
new file mode 100755
index 0000000000..572ec1f67c
--- /dev/null
+++ b/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.svg
@@ -0,0 +1,1402 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.ttf b/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.ttf
new file mode 100755
index 0000000000..bed2418506
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.ttf differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.woff b/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.woff
new file mode 100755
index 0000000000..3f120cbe50
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.woff differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.woff2 b/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.woff2
new file mode 100755
index 0000000000..630ff7715b
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-medium-italic-webfont.woff2 differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-medium-webfont.eot b/src/assets/fonts/sofiapro/sofiapro-medium-webfont.eot
new file mode 100755
index 0000000000..fc468f1b8f
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-medium-webfont.eot differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-medium-webfont.svg b/src/assets/fonts/sofiapro/sofiapro-medium-webfont.svg
new file mode 100755
index 0000000000..efb06d72ff
--- /dev/null
+++ b/src/assets/fonts/sofiapro/sofiapro-medium-webfont.svg
@@ -0,0 +1,1402 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/sofiapro/sofiapro-medium-webfont.ttf b/src/assets/fonts/sofiapro/sofiapro-medium-webfont.ttf
new file mode 100755
index 0000000000..c82d22602a
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-medium-webfont.ttf differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-medium-webfont.woff b/src/assets/fonts/sofiapro/sofiapro-medium-webfont.woff
new file mode 100755
index 0000000000..6f049efdb1
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-medium-webfont.woff differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-medium-webfont.woff2 b/src/assets/fonts/sofiapro/sofiapro-medium-webfont.woff2
new file mode 100755
index 0000000000..dfd424ac8c
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-medium-webfont.woff2 differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.eot b/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.eot
new file mode 100755
index 0000000000..54bbd3d5ed
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.eot differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.svg b/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.svg
new file mode 100755
index 0000000000..9df410c5c3
--- /dev/null
+++ b/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.svg
@@ -0,0 +1,1376 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.ttf b/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.ttf
new file mode 100755
index 0000000000..9c4c9de9fd
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.ttf differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.woff b/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.woff
new file mode 100755
index 0000000000..87f655df62
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.woff differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.woff2 b/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.woff2
new file mode 100755
index 0000000000..8b2e70ff4e
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-regular-italic-webfont.woff2 differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-regular-webfont.eot b/src/assets/fonts/sofiapro/sofiapro-regular-webfont.eot
new file mode 100755
index 0000000000..8d0fb7961c
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-regular-webfont.eot differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-regular-webfont.svg b/src/assets/fonts/sofiapro/sofiapro-regular-webfont.svg
new file mode 100755
index 0000000000..37abb09a52
--- /dev/null
+++ b/src/assets/fonts/sofiapro/sofiapro-regular-webfont.svg
@@ -0,0 +1,1376 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/sofiapro/sofiapro-regular-webfont.ttf b/src/assets/fonts/sofiapro/sofiapro-regular-webfont.ttf
new file mode 100755
index 0000000000..c50ad46972
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-regular-webfont.ttf differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-regular-webfont.woff b/src/assets/fonts/sofiapro/sofiapro-regular-webfont.woff
new file mode 100755
index 0000000000..9f5d87e64b
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-regular-webfont.woff differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-regular-webfont.woff2 b/src/assets/fonts/sofiapro/sofiapro-regular-webfont.woff2
new file mode 100755
index 0000000000..c9cf3faa6f
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-regular-webfont.woff2 differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.eot b/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.eot
new file mode 100755
index 0000000000..dff0383819
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.eot differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.svg b/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.svg
new file mode 100755
index 0000000000..179a6b1775
--- /dev/null
+++ b/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.svg
@@ -0,0 +1,1385 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.ttf b/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.ttf
new file mode 100755
index 0000000000..d9efd1f4d6
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.ttf differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.woff b/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.woff
new file mode 100755
index 0000000000..3bd4104850
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.woff differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.woff2 b/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.woff2
new file mode 100755
index 0000000000..81716c0e6f
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-semibold-italic-webfont.woff2 differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.eot b/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.eot
new file mode 100755
index 0000000000..9f68b6e6d3
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.eot differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.svg b/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.svg
new file mode 100755
index 0000000000..a2924b2aad
--- /dev/null
+++ b/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.svg
@@ -0,0 +1,1385 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.ttf b/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.ttf
new file mode 100755
index 0000000000..3c0ff63dfb
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.ttf differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.woff b/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.woff
new file mode 100755
index 0000000000..0851d481e7
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.woff differ
diff --git a/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.woff2 b/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.woff2
new file mode 100755
index 0000000000..caca12575d
Binary files /dev/null and b/src/assets/fonts/sofiapro/sofiapro-semibold-webfont.woff2 differ
diff --git a/src/assets/images/Rectangle.svg b/src/assets/images/Rectangle.svg
new file mode 100644
index 0000000000..0f6a6735dc
--- /dev/null
+++ b/src/assets/images/Rectangle.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/ico-user-default.svg b/src/assets/images/ico-user-default.svg
new file mode 100644
index 0000000000..ed6ce1a7b5
--- /dev/null
+++ b/src/assets/images/ico-user-default.svg
@@ -0,0 +1,20 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/logo_topcoder.svg b/src/assets/images/logo_topcoder.svg
new file mode 100644
index 0000000000..3fa9d3aa62
--- /dev/null
+++ b/src/assets/images/logo_topcoder.svg
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/logo_topcoder_with_name.svg b/src/assets/images/logo_topcoder_with_name.svg
new file mode 100644
index 0000000000..526eb7f317
--- /dev/null
+++ b/src/assets/images/logo_topcoder_with_name.svg
@@ -0,0 +1,29 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/magnifying_glass.svg b/src/assets/images/magnifying_glass.svg
new file mode 100755
index 0000000000..dba367b51e
--- /dev/null
+++ b/src/assets/images/magnifying_glass.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/nav/blog.svg b/src/assets/images/nav/blog.svg
new file mode 100644
index 0000000000..a1fb066be2
--- /dev/null
+++ b/src/assets/images/nav/blog.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/book-cp.svg b/src/assets/images/nav/book-cp.svg
new file mode 100644
index 0000000000..da6235f6a4
--- /dev/null
+++ b/src/assets/images/nav/book-cp.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/book-data.svg b/src/assets/images/nav/book-data.svg
new file mode 100644
index 0000000000..ccd5e5e106
--- /dev/null
+++ b/src/assets/images/nav/book-data.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/book-design.svg b/src/assets/images/nav/book-design.svg
new file mode 100644
index 0000000000..09594ed84e
--- /dev/null
+++ b/src/assets/images/nav/book-design.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/book-develop.svg b/src/assets/images/nav/book-develop.svg
new file mode 100644
index 0000000000..d18293957c
--- /dev/null
+++ b/src/assets/images/nav/book-develop.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/book-tutorials.svg b/src/assets/images/nav/book-tutorials.svg
new file mode 100644
index 0000000000..ce026cdb31
--- /dev/null
+++ b/src/assets/images/nav/book-tutorials.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/community.svg b/src/assets/images/nav/community.svg
new file mode 100644
index 0000000000..cbb9be9262
--- /dev/null
+++ b/src/assets/images/nav/community.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/dashboard.svg b/src/assets/images/nav/dashboard.svg
new file mode 100644
index 0000000000..083934db63
--- /dev/null
+++ b/src/assets/images/nav/dashboard.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/data-science.svg b/src/assets/images/nav/data-science.svg
new file mode 100644
index 0000000000..5ad1513cdc
--- /dev/null
+++ b/src/assets/images/nav/data-science.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/events.svg b/src/assets/images/nav/events.svg
new file mode 100644
index 0000000000..733691cc3f
--- /dev/null
+++ b/src/assets/images/nav/events.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/exit.svg b/src/assets/images/nav/exit.svg
new file mode 100644
index 0000000000..ccb3120e79
--- /dev/null
+++ b/src/assets/images/nav/exit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/forums.svg b/src/assets/images/nav/forums.svg
new file mode 100644
index 0000000000..f8ed557f2b
--- /dev/null
+++ b/src/assets/images/nav/forums.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/help.svg b/src/assets/images/nav/help.svg
new file mode 100644
index 0000000000..43a8e4f495
--- /dev/null
+++ b/src/assets/images/nav/help.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/ico-tco16.svg b/src/assets/images/nav/ico-tco16.svg
new file mode 100644
index 0000000000..ba4dd1abd2
--- /dev/null
+++ b/src/assets/images/nav/ico-tco16.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/members.svg b/src/assets/images/nav/members.svg
new file mode 100644
index 0000000000..4a2fe56536
--- /dev/null
+++ b/src/assets/images/nav/members.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/profile.svg b/src/assets/images/nav/profile.svg
new file mode 100644
index 0000000000..46afcaac00
--- /dev/null
+++ b/src/assets/images/nav/profile.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/programs.svg b/src/assets/images/nav/programs.svg
new file mode 100644
index 0000000000..4441b49c95
--- /dev/null
+++ b/src/assets/images/nav/programs.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/rocket.svg b/src/assets/images/nav/rocket.svg
new file mode 100644
index 0000000000..513f6517b5
--- /dev/null
+++ b/src/assets/images/nav/rocket.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/settings.svg b/src/assets/images/nav/settings.svg
new file mode 100644
index 0000000000..71859faaf3
--- /dev/null
+++ b/src/assets/images/nav/settings.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/statistics.svg b/src/assets/images/nav/statistics.svg
new file mode 100644
index 0000000000..21874ed8de
--- /dev/null
+++ b/src/assets/images/nav/statistics.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/tco-generic.svg b/src/assets/images/nav/tco-generic.svg
new file mode 100644
index 0000000000..c187ee9364
--- /dev/null
+++ b/src/assets/images/nav/tco-generic.svg
@@ -0,0 +1,19 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/nav/track-copilot.svg b/src/assets/images/nav/track-copilot.svg
new file mode 100644
index 0000000000..f65b691901
--- /dev/null
+++ b/src/assets/images/nav/track-copilot.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/track-cp.svg b/src/assets/images/nav/track-cp.svg
new file mode 100644
index 0000000000..2c86a0884e
--- /dev/null
+++ b/src/assets/images/nav/track-cp.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/track-data.svg b/src/assets/images/nav/track-data.svg
new file mode 100644
index 0000000000..830aac6857
--- /dev/null
+++ b/src/assets/images/nav/track-data.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/track-design.svg b/src/assets/images/nav/track-design.svg
new file mode 100644
index 0000000000..a2cc7a0e70
--- /dev/null
+++ b/src/assets/images/nav/track-design.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/track-develop.svg b/src/assets/images/nav/track-develop.svg
new file mode 100644
index 0000000000..4b76ac1b5b
--- /dev/null
+++ b/src/assets/images/nav/track-develop.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/nav/wallet.svg b/src/assets/images/nav/wallet.svg
new file mode 100644
index 0000000000..8621bdb2fe
--- /dev/null
+++ b/src/assets/images/nav/wallet.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/images/tc-communities/dollar.svg b/src/assets/images/tc-communities/dollar.svg
new file mode 100644
index 0000000000..0e1d7cfebb
--- /dev/null
+++ b/src/assets/images/tc-communities/dollar.svg
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/tc-communities/logo_topcoder_gray.svg b/src/assets/images/tc-communities/logo_topcoder_gray.svg
new file mode 100644
index 0000000000..d1a63e7c21
--- /dev/null
+++ b/src/assets/images/tc-communities/logo_topcoder_gray.svg
@@ -0,0 +1,35 @@
+
+
diff --git a/src/assets/images/tc-communities/medal.svg b/src/assets/images/tc-communities/medal.svg
new file mode 100644
index 0000000000..53d85ad92b
--- /dev/null
+++ b/src/assets/images/tc-communities/medal.svg
@@ -0,0 +1,28 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/tc-communities/member.svg b/src/assets/images/tc-communities/member.svg
new file mode 100644
index 0000000000..d660a85055
--- /dev/null
+++ b/src/assets/images/tc-communities/member.svg
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/tc-communities/network.svg b/src/assets/images/tc-communities/network.svg
new file mode 100644
index 0000000000..e816c6655d
--- /dev/null
+++ b/src/assets/images/tc-communities/network.svg
@@ -0,0 +1,34 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/tc-communities/rocket.svg b/src/assets/images/tc-communities/rocket.svg
new file mode 100644
index 0000000000..7cfdbb2282
--- /dev/null
+++ b/src/assets/images/tc-communities/rocket.svg
@@ -0,0 +1,20 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/tc-communities/search.svg b/src/assets/images/tc-communities/search.svg
new file mode 100644
index 0000000000..6cc12f48d8
--- /dev/null
+++ b/src/assets/images/tc-communities/search.svg
@@ -0,0 +1,19 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/tc-communities/suitcase.svg b/src/assets/images/tc-communities/suitcase.svg
new file mode 100644
index 0000000000..0fb1ee6b9c
--- /dev/null
+++ b/src/assets/images/tc-communities/suitcase.svg
@@ -0,0 +1,17 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/tc-communities/tick_down.png b/src/assets/images/tc-communities/tick_down.png
new file mode 100644
index 0000000000..c8a914481c
Binary files /dev/null and b/src/assets/images/tc-communities/tick_down.png differ
diff --git a/src/assets/images/tc-communities/tick_down_big.svg b/src/assets/images/tc-communities/tick_down_big.svg
new file mode 100644
index 0000000000..d0c85450f5
--- /dev/null
+++ b/src/assets/images/tc-communities/tick_down_big.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/tc-communities/wipro/about-hero.png b/src/assets/images/tc-communities/wipro/about-hero.png
new file mode 100644
index 0000000000..f7fec2c3a1
Binary files /dev/null and b/src/assets/images/tc-communities/wipro/about-hero.png differ
diff --git a/src/assets/images/tc-communities/wipro/hands-circle.jpg b/src/assets/images/tc-communities/wipro/hands-circle.jpg
new file mode 100644
index 0000000000..131b75be1e
Binary files /dev/null and b/src/assets/images/tc-communities/wipro/hands-circle.jpg differ
diff --git a/src/assets/mock-data/submissions-data-design-mock.json b/src/assets/mock-data/submissions-data-design-mock.json
new file mode 100644
index 0000000000..54dbcf756d
--- /dev/null
+++ b/src/assets/mock-data/submissions-data-design-mock.json
@@ -0,0 +1,66 @@
+{
+ "challenge": {
+ "allowSubmit": true,
+ "name": "GET - Rapid UX - Positive Train Control Design Concepts Challenge",
+ "phase": "Round 2",
+ "phaseEnd": "1489679594883",
+ "type": "design"
+ },
+ "submissions": [{
+ "id": "0253653",
+ "preview": "http://placehold.it/90/ff0000",
+ "screening": {
+ "status": "pending"
+ },
+ "submitted": "1488470630291",
+ "type": "Final"
+ }, {
+ "id": "0253614",
+ "preview": "http://placehold.it/90/f0ff0f",
+ "screening": {
+ "status": "passed",
+ "warnings": [{
+ "brief": "Incomplete Source Files",
+ "details": "The provided source files don't match the JPEGs from the submission .zip archive. Sources for files 14..25 are missing."
+ }, {
+ "brief": "Stock Art is Not Allowed",
+ "details": "This challenge doesn't allow you to use any istockphoto, others than the files provided by client. The submitter violated this rule and used 3 images from istockphoto.com. Please check challenge's specifications carefully."
+ }]
+ },
+ "submitted": "1488480630291",
+ "type": "Checkpoint"
+ }, {
+ "id": "0253145",
+ "preview": "http://placehold.it/90/0660ff",
+ "screening": {
+ "status": "passed"
+ },
+ "submitted": "1488570630291",
+ "type": "Checkpoint"
+ }, {
+ "id": "0253144",
+ "preview": "http://placehold.it/90/ff0ff0",
+ "screening": {
+ "status": "failed",
+ "warnings": [{
+ "brief": "You Have Made a Fatal Mistake",
+ "details": "Sorry, you have totally lost this challenge. Don't be too upset though, those who never fail, never rise up singing either."
+ }]
+ },
+ "submitted": "1488670630291",
+ "type": "Checkpoint"
+ }, {
+ "id": "0253188",
+ "preview": "http://placehold.it/90/0f00f0",
+ "screening": {
+ "status": "failed"
+ },
+ "submitted": "1488670630291",
+ "type": "Checkpoint"
+ }, {
+ "id": "0253199",
+ "preview": "http://placehold.it/90/00ff00",
+ "submitted": "1488670630291",
+ "type": "Checkpoint"
+ }]
+}
diff --git a/src/assets/mock-data/submissions-data-develop-mock.json b/src/assets/mock-data/submissions-data-develop-mock.json
new file mode 100644
index 0000000000..944e986c07
--- /dev/null
+++ b/src/assets/mock-data/submissions-data-develop-mock.json
@@ -0,0 +1,14 @@
+{
+ "challenge": {
+ "name": "John Hancock - Project Coesus Database Upgrade",
+ "phase": "Submission",
+ "phaseEnd": "1489679594883",
+ "type": "develop"
+ },
+ "submissions": [{
+ "id": "0253653",
+ "preview": "http://placehold.it/40/ff0ff0",
+ "submitted": "1488470630291",
+ "type": "Final"
+ }]
+}
diff --git a/src/assets/themes/green/Header.css b/src/assets/themes/green/Header.css
new file mode 100644
index 0000000000..b5fb6de7b3
--- /dev/null
+++ b/src/assets/themes/green/Header.css
@@ -0,0 +1,12 @@
+.tc-communities__header__container {
+ background-color: green;
+}
+
+.tc-communities__header__menu-link,
+.tc-communities__header__menu-link:visited {
+ color: #fff;
+}
+
+.tc-communities__header__menu-link:hover {
+ color: #2886af;
+}
diff --git a/src/assets/themes/red/Header.css b/src/assets/themes/red/Header.css
new file mode 100644
index 0000000000..e1dd5009c0
--- /dev/null
+++ b/src/assets/themes/red/Header.css
@@ -0,0 +1,12 @@
+.tc-communities__header__container {
+ background-color: red;
+}
+
+.tc-communities__header__menu-link,
+.tc-communities__header__menu-link:visited {
+ color: #fff;
+}
+
+.tc-communities__header__menu-link:hover {
+ color: #2886af;
+}
diff --git a/src/assets/themes/wipro/logo_topcoder_with_name.svg b/src/assets/themes/wipro/logo_topcoder_with_name.svg
new file mode 100644
index 0000000000..526eb7f317
--- /dev/null
+++ b/src/assets/themes/wipro/logo_topcoder_with_name.svg
@@ -0,0 +1,29 @@
+
+
\ No newline at end of file
diff --git a/src/assets/themes/wipro2/challenges/banner.jpg b/src/assets/themes/wipro2/challenges/banner.jpg
new file mode 100644
index 0000000000..41520fea1c
Binary files /dev/null and b/src/assets/themes/wipro2/challenges/banner.jpg differ
diff --git a/src/assets/themes/wipro2/home/banner.jpg b/src/assets/themes/wipro2/home/banner.jpg
new file mode 100644
index 0000000000..d7b9d47f15
Binary files /dev/null and b/src/assets/themes/wipro2/home/banner.jpg differ
diff --git a/src/assets/themes/wipro2/home/image-text-do.jpg b/src/assets/themes/wipro2/home/image-text-do.jpg
new file mode 100644
index 0000000000..edc57ebacc
Binary files /dev/null and b/src/assets/themes/wipro2/home/image-text-do.jpg differ
diff --git a/src/assets/themes/wipro2/home/image-text-learn.jpg b/src/assets/themes/wipro2/home/image-text-learn.jpg
new file mode 100644
index 0000000000..937cc021ca
Binary files /dev/null and b/src/assets/themes/wipro2/home/image-text-learn.jpg differ
diff --git a/src/assets/themes/wipro2/home/news-01.jpg b/src/assets/themes/wipro2/home/news-01.jpg
new file mode 100644
index 0000000000..4a37552dd2
Binary files /dev/null and b/src/assets/themes/wipro2/home/news-01.jpg differ
diff --git a/src/assets/themes/wipro2/home/news-02.jpg b/src/assets/themes/wipro2/home/news-02.jpg
new file mode 100644
index 0000000000..c7230fd7cb
Binary files /dev/null and b/src/assets/themes/wipro2/home/news-02.jpg differ
diff --git a/src/assets/themes/wipro2/home/news-03.jpg b/src/assets/themes/wipro2/home/news-03.jpg
new file mode 100644
index 0000000000..81635e9113
Binary files /dev/null and b/src/assets/themes/wipro2/home/news-03.jpg differ
diff --git a/src/assets/themes/wipro2/leaderboard/banner.jpg b/src/assets/themes/wipro2/leaderboard/banner.jpg
new file mode 100644
index 0000000000..e107c76abb
Binary files /dev/null and b/src/assets/themes/wipro2/leaderboard/banner.jpg differ
diff --git a/src/assets/themes/wipro2/learn/banner.jpg b/src/assets/themes/wipro2/learn/banner.jpg
new file mode 100644
index 0000000000..3f7b11cbcd
Binary files /dev/null and b/src/assets/themes/wipro2/learn/banner.jpg differ
diff --git a/src/assets/themes/wipro2/learn/courses-01.jpg b/src/assets/themes/wipro2/learn/courses-01.jpg
new file mode 100644
index 0000000000..0b1483028d
Binary files /dev/null and b/src/assets/themes/wipro2/learn/courses-01.jpg differ
diff --git a/src/assets/themes/wipro2/learn/courses-02.jpg b/src/assets/themes/wipro2/learn/courses-02.jpg
new file mode 100644
index 0000000000..633ebfcefe
Binary files /dev/null and b/src/assets/themes/wipro2/learn/courses-02.jpg differ
diff --git a/src/assets/themes/wipro2/learn/courses-03.jpg b/src/assets/themes/wipro2/learn/courses-03.jpg
new file mode 100644
index 0000000000..f904002400
Binary files /dev/null and b/src/assets/themes/wipro2/learn/courses-03.jpg differ
diff --git a/src/assets/themes/wipro2/subscribe-bg.jpg b/src/assets/themes/wipro2/subscribe-bg.jpg
new file mode 100644
index 0000000000..91f8fcfc4d
Binary files /dev/null and b/src/assets/themes/wipro2/subscribe-bg.jpg differ
diff --git a/src/client/.eslintrc b/src/client/.eslintrc
new file mode 100644
index 0000000000..08a2de76a3
--- /dev/null
+++ b/src/client/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "env": {
+ "browser": true
+ }
+}
\ No newline at end of file
diff --git a/src/client/index.jsx b/src/client/index.jsx
new file mode 100644
index 0000000000..8fc1b41897
--- /dev/null
+++ b/src/client/index.jsx
@@ -0,0 +1,99 @@
+/**
+ * Client-side rendering of the App.
+ */
+
+import actions from 'actions/auth';
+import cookies from 'browser-cookies';
+import { BrowserRouter, browserHistory } from 'react-router-dom';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Provider } from 'react-redux';
+import shortId from 'shortid';
+import {
+ configureConnector,
+ decodeToken,
+ getFreshToken,
+} from 'tc-accounts';
+import logger from 'utils/logger';
+
+import storeFactory from '../shared/store-factory';
+import './styles.scss';
+
+/* Isomorphic code may rely on this environment variable to check whether it is
+ * executed client- or server-side. */
+if (!process.env.FRONT_END) {
+ throw new Error(
+ 'process.env.FRONT_END must evaluate to true at the client side');
+}
+
+const config = window.CONFIG;
+
+/**
+ * Uses Topcoder accounts-app to fetch / refresh authentication tokens.
+ * Results will be storted in the Redux store, inside state.auth.
+ * @param {Object} store Redux store.
+ */
+function authenticate(store) {
+ /* TODO: The iframe injected into the page by this call turns out to be
+ * visible as a tiny nob (~3x3 px). It has html ID `tc-accounts-iframe`
+ * (as specified in this call). We should hide it by adding the proper side
+ * in our stylesheet, or to figure out, why it is not hidden by default. */
+ configureConnector({
+ connectorUrl: config.ACCOUNTS_APP_CONNECTOR_URL,
+ frameId: 'tc-accounts-iframe',
+ });
+
+ getFreshToken().then((tctV3) => {
+ const tctV2 = cookies.get('tcjwt');
+ logger.log('Authenticated as:', decodeToken(tctV3));
+ logger.log('Topcoder API v3 token:', tctV3);
+ if (!tctV2) logger.error('Failed to fetch API v2 token!');
+ else logger.log('Topcoder API v2 token:', tctV2);
+ return ({ tctV2, tctV3 });
+ }).catch(() => {
+ logger.warn('Authentication failed!');
+ return ({});
+ }).then(({ tctV2, tctV3 }) => {
+ const auth = store.getState().auth;
+ if (auth.tokenV3 !== (tctV3 || null)) {
+ store.dispatch(actions.auth.setTcTokenV3(tctV3));
+ }
+ if (auth.tokenV2 !== (tctV2 || null)) {
+ store.dispatch(actions.auth.setTcTokenV2(tctV2));
+ }
+ store.dispatch(actions.auth.loadProfile(tctV3));
+ });
+}
+
+storeFactory(undefined, window.ISTATE).then((store) => {
+ authenticate(store);
+
+ function render() {
+ const App = require('../shared').default; // eslint-disable-line global-require
+ ReactDOM.render(
+
+
+
+
+ ,
+ document.getElementById('react-view'),
+ );
+ }
+
+ render();
+
+ if (module.hot) {
+ module.hot.accept('../shared', render);
+
+ /* This block of code forces reloading of style.css file each time
+ * webpack hot middleware reports about update of the code. */
+ /* eslint-disable no-underscore-dangle */
+ const hotReporter = window.__webpack_hot_middleware_reporter__;
+ const hotSuccess = hotReporter.success;
+ hotReporter.success = () => {
+ const link = document.querySelectorAll('link[rel=stylesheet]')[0];
+ link.href = `/style.css?v=${shortId.generate()}`;
+ hotSuccess();
+ };
+ }
+});
diff --git a/src/client/styles.scss b/src/client/styles.scss
new file mode 100644
index 0000000000..41ddcb7d3e
--- /dev/null
+++ b/src/client/styles.scss
@@ -0,0 +1,8 @@
+:global {
+ /* This global style is necessary to completely hide the iframe injected into
+ * the page by Topcoder accounts-app. */
+ #tc-accounts-iframe {
+ border: none;
+ display: block;
+ }
+}
diff --git a/src/server/.eslintrc b/src/server/.eslintrc
new file mode 100644
index 0000000000..1ac998e606
--- /dev/null
+++ b/src/server/.eslintrc
@@ -0,0 +1,7 @@
+{
+ "rules": {
+ // It is fine to use console server-side
+ // http://eslint.org/docs/rules/no-console
+ "no-console": 0
+ }
+}
diff --git a/src/server/index.js b/src/server/index.js
new file mode 100644
index 0000000000..534723d982
--- /dev/null
+++ b/src/server/index.js
@@ -0,0 +1,63 @@
+/**
+ * ExpressJS startup script.
+ */
+
+import _ from 'lodash';
+import http from 'http';
+import app from './server';
+
+/**
+ * Normalizes a port into a number, string, or false.
+ * @param {String} value Port name or number.
+ * @return Port number (Number), name (String), or false.
+ */
+function normalizePort(value) {
+ const port = parseInt(value, 10);
+ if (isNaN(port)) return value; /* named pipe */
+ if (port >= 0) return port; /* port number */
+ return false;
+}
+
+/* Get port from environment and store in Express. */
+const port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/* Creates HTTP server. */
+const server = http.createServer(app);
+
+/**
+ * Error handler for HTTP server.
+ * @param {Object} error
+ */
+function onError(error) {
+ if (error.syscall !== 'listen') throw error;
+ const bind = _.isString(port) ? `Pipe ${port}` : `Port ${port}`;
+
+ /* Human-readable messages for some specific listen errors. */
+ switch (error.code) {
+ case 'EACCES':
+ console.error(`${bind} requires elevated privileges`);
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(`${bind} is already in use`);
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Listening event handler for HTTP server.
+ */
+function onListening() {
+ const addr = server.address();
+ const bind = _.isString(addr) ? `pipe ${addr}` : `port ${addr.port}`;
+ console.log(`Server listening on ${bind} in ${process.env.NODE_ENV} mode`);
+}
+
+/* Listens on provided port, on all network interfaces. */
+server.on('error', onError);
+server.on('listening', onListening);
+server.listen(port);
diff --git a/src/server/renderer.jsx b/src/server/renderer.jsx
new file mode 100644
index 0000000000..fb4f37a854
--- /dev/null
+++ b/src/server/renderer.jsx
@@ -0,0 +1,68 @@
+/**
+ * This module implements ExpressJS middleware for server-side rendering of
+ * the App.
+ */
+
+import config from 'config';
+import React from 'react';
+import ReactDOM from 'react-dom/server'; // This may cause warning of PropTypes
+import { StaticRouter } from 'react-router-dom';
+import { Provider } from 'react-redux';
+import serializeJs from 'serialize-javascript';
+
+import App from '../shared';
+
+/* This is always initial state of the store. Later we'll have to provide a way
+ * to put the store into correct state depending on the demanded route. */
+import storeFactory from '../shared/store-factory';
+
+export default (req, res) => {
+ storeFactory(req).then((store) => {
+ const context = {};
+ const appHtml = ReactDOM.renderToString((
+
+
+
+
+
+ ));
+ if (context.status) res.status(context.status);
+ res.send((
+ `
+
+
+ Topcoder
+
+
+
+
+