Add judo heroes example universal/isomorphic javascript app

This commit is contained in:
Timothy Warren 2016-09-02 12:25:57 -04:00
parent 8f720c10b2
commit a5c30d6f3d
39 changed files with 797 additions and 0 deletions

View File

@ -0,0 +1,3 @@
{
"presets": ["react", "es2015"]
}

View File

@ -0,0 +1,20 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = false
charset = utf-8
indent_style = tab
trim_trailing_whitespace = true
[*.{cpp,c,h,hpp,cxx}]
insert_final_newline = true
# Yaml files
[*.{yml,yaml}]
indent_style = space
indent_size = 4

4
frontendJS/universal-react/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
dist/
babel_cache/
node_modules/
public/js/bundle.js

View File

@ -0,0 +1,27 @@
{
"dependencies": {
"babel-cli": "^6.11.4",
"babel-core": "^6.13.2",
"babel-preset-es2015": "^6.13.2",
"babel-preset-react": "^6.11.1",
"ejs": "^2.5.1",
"express": "^4.14.0",
"react": "^15.3.1",
"react-dom": "^15.3.1",
"react-router": "^2.6.1"
},
"devDependencies": {
"babel-loader": "^6.2.5",
"http-server": "^0.9.0",
"webpack": "^1.13.2"
},
"scripts": {
"start": "NODE_ENV=production babel-node src/server.js",
"build": "npm run build:client && npm run build:server",
"build:client": "NODE_ENV=production webpack -p",
"build:server": "NODE_ENV=production babel src -d dist",
"buildrun": "npm run build && npm run serve",
"clean": "rm -rf ./dist && rm -rf ./babel_cache && rm ./public/js/bundle.js",
"serve": "node dist/server.js"
}
}

View File

@ -0,0 +1,246 @@
/*! CSS reset from benfrain/app-reset */
*,:after,:before{box-sizing:inherit}html{box-sizing:border-box}
*{user-select:none;-webkit-tap-highlight-color:rgba(255,255,255,0);-webkit-tap-highlight-color:transparent}
[contenteditable],input[type]{user-select:text}body,h1,h2,h3,h4,h5,h6,p{margin:0;font-size:1rem;font-weight:400}
a{text-decoration:none;color:inherit}b{font-weight:400}em,i{font-style:normal}a:focus,input:focus{outline:0}
fieldset,input{appearance:none;border:0;padding:0;margin:0;min-width:0;font-size:1rem;font-family:inherit}
input::-ms-clear{display:none}input[type=number]{-moz-appearance:textfield}
input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{appearance:none}
svg{display:inline-flex}img{max-width:100%;display:block}
html {
color: #030303;
font: caption;
padding-bottom: 2em;
}
a {
color: #1f4ba0;
text-decoration: underline;
}
header {
padding: 2em;
}
header .logo {
display: block;
margin: 0 auto;
max-width: 400px;
width: 100%;
}
footer {
margin: 4em auto;
padding: 2em;
text-align: center;
max-width: 800px;
width: 100%;
}
footer p {
line-height: 1.2em;
margin: 0 0 1em 0;
}
.not-found {
margin: 0 auto;
max-width: 800px;
padding: 2em;
text-align: center;
width: 100%;
}
.not-found h1 {
font-size: 3em;
font-weight: bold;
}
.not-found h2 {
font-size: 2em;
font-weight: bold;
}
.home .athletes-selector {
clear: both;
padding: 2em;
text-align: center;
}
.home .athletes-selector .athlete-preview {
border: 1px solid #ccc;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);
display: inline-block;
margin: 0 1em 1em 0;
max-width: 200px;
padding: 0;
position: relative;
width: 100%;
}
.home .athletes-selector .athlete-preview img {
margin: 0;
width: 100%;
}
.home .athletes-selector .athlete-preview .name {
color: #030303;
display: inline-block;
font-size: 1.6em;
overflow: hidden;
padding: .2em;
text-align: center;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
.home .athletes-selector .athlete-preview .medals-count {
background: #fff;
border-radius: 2px;
display: inline-block;
font-weight: bold;
margin: .2em;
padding: .2em .4em;
position: absolute;
right: 0;
text-align: center;
top: 0;
vertical-align: middle;
}
nav.atheletes-menu {
margin: 0 auto;
max-width: 800px;
padding: 2em;
text-align: center;
width: 100%;
}
nav.atheletes-menu a {
font-size: 1.6em;
margin: 0 1em 1em 0;
}
nav.atheletes-menu a.active {
color: #030303;
text-decoration: none;
}
.athlete {
border: 1px solid #ccc;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);
margin: 0 auto;
max-width: 800px;
width: 100%;
}
.athlete header {
background: #ccc;
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
width: 100%;
height: 200px;
}
.athlete .picture-container {
margin: -160px 0 0 0;
padding: 0 1em 0 1em;
}
.athlete .picture-container img {
border-radius: 4px;
border: 8px solid #fff;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);
display: inline;
margin: 0 0 0 1em;
width: 200px;
}
.athlete .picture-container h2 {
display: inline;
font-size: 3em;
padding: 0 0 0 .5em;
width: auto;
}
.athlete .flag .icon {
display: inline-block;
padding-bottom: .1em;
width: auto;
}
.athlete .description,
.athlete .medals {
font-size: 1.6em;
padding: 1em;
}
.athlete .medals p {
font-size: 1em;
}
.athlete .medals li {
list-style: none;
}
.athlete .medals .symbol {
border-radius: 50%;
display: inline-block;
font-size: .8em;
height: 1.6em;
justify-content: center;
margin: 5px;
text-align: center;
width: 1.6em;
}
.athlete .medals .symbol.symbol-G {
color: #daa520;
background-color: #fff6de;
border: 2px solid #daa520;
}
.athlete .medals .symbol.symbol-S {
color: #383738;
background-color: #b9b5b5;
border: 2px solid #383738;
}
.athlete .medals .symbol.symbol-B {
color: #6b1919;
background-color: #ea96a1;
border: 2px solid #6E1924;
}
.navigateBack {
font-size: 1.6em;
padding: 2em;
text-align: center;
text-decoration: none;
}
#abar {
bottom: 0;
left: 0;
position: fixed;
width: 100%;
}
#abar a {
background: #ffa500;
color: #000;
display: block;
font-weight: bold;
margin: 0;
padding: 1em;
text-align: center;
text-decoration: none;
width: 100%;
}
#abar a:hover {
text-decoration: underline;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Judo Heroes - A Universal Javascript demo application with React</title>
<link rel="stylesheet" href="/css/style.css" />
</head>
<body>
<div id="main"></div>
<script src="/js/bundle.js"></script>
</body>
</html>

View File

@ -0,0 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import AppRoutes from './components/AppRoutes';
ReactDOM.render(<AppRoutes />, document.getElementById('main'));

View File

@ -0,0 +1,13 @@
import React from 'react';
import { Router, browserHistory } from 'react-router';
import routes from '../routes';
class AppRoutes extends React.Component {
render() {
return (
<Router history={browserHistory} routes={routes} onUpdate={() => window.scrollTo(0, 0)} />
);
}
}
export default AppRoutes;

View File

@ -0,0 +1,19 @@
import React from 'react';
import { Link } from 'react-router';
import athletes from '../data/athletes';
class AthleteMenu extends React.Component {
render() {
return (
<nav className="atheletes-menu">
{athletes.map(menuAthlete => {
return <Link key={menuAthlete.id} to={`/athlete/${menuAthlete.id}`} activeClassName="active">
{menuAthlete.name}
</Link>;
})}
</nav>
);
}
}
export default AthleteMenu;

View File

@ -0,0 +1,48 @@
import React from 'react';
import { Link } from 'react-router';
import NotFoundPage from './NotFoundPage';
import AthleteMenu from './AthleteMenu';
import Medal from './Medal';
import Flag from './Flag';
import athletes from '../data/athletes';
class AthletePage extends React.Component {
render() {
const id = this.props.params.id;
const athlete = athletes.filter(athlete => athlete.id === id)[0];
if ( ! athlete) {
return <NotFoundPage />
}
const headerStyle = {
backgroundImage: `url(/img/${athlete.cover})`
};
return (
<div className="athlete-full">
<AthleteMenu />
<div className="athlete">
<header style={headerStyle} />
<div className="picture-container">
<img src={`/img/${athlete.image}`} />
<h2 className="name">{athlete.name}</h2>
</div>
<section className="description">
Olympic medalist from <strong><Flag code={athlete.country} showName="true" /></strong>,
born in {athlete.birth} (Find out more on <a href={athlete.link}>Wikipedia</a>).
</section>
<section className="medals">
<p>Winner of <string>{athlete.medals.length}</string> medals:</p>
<ul>{
athlete.medals.map((medal, i) => <Medal key={i} {...medal} />)
}</ul>
</section>
</div>
<div className="navigateBack">
<Link to="/">« Back to the index</Link>
</div>
</div>
);
}
}
export default AthletePage;

View File

@ -0,0 +1,21 @@
import React from 'react';
import { Link } from 'react-router';
class AthletePreview extends React.Component {
render() {
return (
<Link to={`/athlete/${this.props.id}`}>
<div className="athlete-preview">
<img src={`img/${this.props.image}`} />
<h2 className="name">{this.props.name}</h2>
<span className="medals-count">
<img src="/img/medal.png" />
{this.props.medals.length}
</span>
</div>
</Link>
);
}
}
export default AthletePreview;

View File

@ -0,0 +1,40 @@
import React from 'react';
const data = {
cu: {
name: 'Cuba',
icon: 'flag-cu.png'
},
fr: {
name: 'France',
icon: 'flag-fr.png'
},
jp: {
name: 'Japan',
icon: 'flag-jp.png'
},
nl: {
name: 'Netherlands',
icon: 'flag-nl.png'
},
uz: {
name: 'Uzbekistan',
icon: 'flag-uz.png'
}
};
class Flag extends React.Component {
render() {
const name = data[this.props.code].name;
const icon = data[this.props.code].icon;
return (
<span className="flag">
<img className="icon" title={name} src={`/img/${icon}`} />
&nbsp;{this.props.showName && <span className="name">{name}</span>}
</span>
);
}
}
export default Flag;

View File

@ -0,0 +1,17 @@
import React from 'react';
import AthletePreview from './AthletePreview';
import athletes from '../data/athletes';
class IndexPage extends React.Component {
render() {
return (
<div className="home">
<div className="athletes-selector">
{athletes.map(athleteData => <AthletePreview key={athleteData.id} {...athleteData} />)}
</div>
</div>
);
}
}
export default IndexPage;

View File

@ -0,0 +1,24 @@
import React from 'react';
import { Link } from 'react-router';
class Layout extends React.Component {
render() {
return (
<div className="app-container">
<header>
<Link to="/">
<img className="logo" src="/img/logo-judo-heroes.png" />
</Link>
</header>
<div className="app-content">{this.props.children}</div>
<footer>
<p>
This is a demo app to showcase universal rendering and routing with <strong>React</strong> and <strong>Express</strong>.
</p>
</footer>
</div>
);
}
}
export default Layout;

View File

@ -0,0 +1,23 @@
import React from 'react';
const typeMap = {
G: 'Gold',
S: 'Silver',
B: 'Bronze'
};
class Medal extends React.Component {
render() {
return (
<li className="medal">
<span className={`symbol symbol-${this.props.type}`} title={typeMap[this.props.type]}>{this.props.type}</span>
<span className="year">{this.props.year}</span>
<span className="city">{this.props.city}</span>
<span className="event">({this.props.event})</span>
<span className="category">{this.props.category}</span>
</li>
);
}
}
export default Medal;

View File

@ -0,0 +1,18 @@
import React from 'react';
import { Link } from 'react-router';
class NotFoundPage extends React.Component {
render() {
return (
<div className="not-found">
<h1>404</h1>
<h2>Page not found!</h2>
<p>
<Link to="/">Go back to the main page</Link>
</p>
</div>
);
}
}
export default NotFoundPage;

View File

@ -0,0 +1,134 @@
"use strict";
const athletes = [
{
'id': 'driulis-gonzalez',
'name': 'Driulis González',
'country': 'cu',
'birth': '1973',
'image': 'driulis-gonzalez.jpg',
'cover': 'driulis-gonzalez-cover.jpg',
'link': 'https://en.wikipedia.org/wiki/Driulis_González',
'medals': [
{ 'year': '1992', 'type': 'B', 'city': 'Barcelona', 'event': 'Olympic Games', 'category': '-57kg' },
{ 'year': '1993', 'type': 'B', 'city': 'Hamilton', 'event': 'World Championships', 'category': '-57kg' },
{ 'year': '1995', 'type': 'G', 'city': 'Chiba', 'event': 'World Championships', 'category': '-57kg' },
{ 'year': '1995', 'type': 'G', 'city': 'Mar del Plata', 'event': 'Pan American Games', 'category': '-57kg' },
{ 'year': '1996', 'type': 'G', 'city': 'Atlanta', 'event': 'Olympic Games', 'category': '-57kg' },
{ 'year': '1997', 'type': 'S', 'city': 'Osaka', 'event': 'World Championships', 'category': '-57kg' },
{ 'year': '1999', 'type': 'G', 'city': 'Birmingham', 'event': 'World Championships', 'category': '-57kg' },
{ 'year': '2000', 'type': 'S', 'city': 'Sydney', 'event': 'Olympic Games', 'category': '-57kg' },
{ 'year': '2003', 'type': 'G', 'city': 'S Domingo', 'event': 'Pan American Games', 'category': '-63kg' },
{ 'year': '2003', 'type': 'S', 'city': 'Osaka', 'event': 'World Championships', 'category': '-63kg' },
{ 'year': '2004', 'type': 'B', 'city': 'Athens', 'event': 'Olympic Games', 'category': '-63kg' },
{ 'year': '2005', 'type': 'B', 'city': 'Cairo', 'event': 'World Championships', 'category': '-63kg' },
{ 'year': '2006', 'type': 'G', 'city': 'Cartagena', 'event': 'Central American and Caribbean Games', 'category': '-63kg' },
{ 'year': '2006', 'type': 'G', 'city': 'Cartagena', 'event': 'Central American and Caribbean Games', 'category': 'Tema' },
{ 'year': '2007', 'type': 'G', 'city': 'Rio de Janeiro', 'event': 'Pan American Games', 'category': '-63kg' },
{ 'year': '2007', 'type': 'G', 'city': 'Rio de Janeiro', 'event': 'World Championships', 'category': '-63kg' },
],
},
{
'id': 'mark-huizinga',
'name': 'Mark Huizinga',
'country': 'nl',
'birth': '1973',
'image': 'mark-huizinga.jpg',
'cover': 'mark-huizinga-cover.jpg',
'link': 'https://en.wikipedia.org/wiki/Mark_Huizinga',
'medals': [
{ 'year': '1994', 'type': 'B', 'city': 'Gdansk', 'event': 'European Championships', 'category': '-78kg' },
{ 'year': '1996', 'type': 'B', 'city': 'Atlanta', 'event': 'Olympic Games', 'category': '-86kg' },
{ 'year': '1996', 'type': 'G', 'city': 'The Hague', 'event': 'European Championships', 'category': '-86kg' },
{ 'year': '1997', 'type': 'G', 'city': 'Oostende', 'event': 'European Championships', 'category': '-86kg' },
{ 'year': '1998', 'type': 'G', 'city': 'Oviedo', 'event': 'European Championships', 'category': '-90kg' },
{ 'year': '1999', 'type': 'B', 'city': 'Bratislava', 'event': 'European Championships', 'category': '-90kg' },
{ 'year': '2000', 'type': 'G', 'city': 'Sydney', 'event': 'Olympic Games', 'category': '-90kg' },
{ 'year': '2000', 'type': 'S', 'city': 'Wroclaw', 'event': 'European Championships', 'category': '-90kg' },
{ 'year': '2001', 'type': 'G', 'city': 'Paris', 'event': 'European Championships', 'category': '-90kg' },
{ 'year': '2002', 'type': 'B', 'city': 'Maribor', 'event': 'European Championships', 'category': '-90kg' },
{ 'year': '2003', 'type': 'B', 'city': 'Düsseldorf', 'event': 'European Championships', 'category': '-90kg' },
{ 'year': '2004', 'type': 'B', 'city': 'Athens', 'event': 'Olympic Games', 'category': '-90kg' },
{ 'year': '2004', 'type': 'S', 'city': 'Bucharest', 'event': 'European Championships', 'category': '-90kg' },
{ 'year': '2005', 'type': 'B', 'city': 'Cairo', 'event': 'World Championships', 'category': '-90kg' },
{ 'year': '2005', 'type': 'B', 'city': 'Rotterdam', 'event': 'European Championships', 'category': '-90kg' },
{ 'year': '2008', 'type': 'G', 'city': 'Lisbon', 'event': 'European Championships', 'category': '-90kg' },
],
},
{
'id': 'rishod-sobirov',
'name': 'Rishod Sobirov',
'country': 'uz',
'birth': '1986',
'image': 'rishod-sobirov.jpg',
'cover': 'rishod-sobirov-cover.jpg',
'link': 'https://en.wikipedia.org/wiki/Rishod_Sobirov',
'medals': [
{ 'year': '2007', 'type': 'S', 'city': 'Kuwait City', 'event': 'Asian Championships', 'category': '-60kg' },
{ 'year': '2008', 'type': 'B', 'city': 'Beijing', 'event': 'Olympic Games', 'category': '-60kg' },
{ 'year': '2010', 'type': 'G', 'city': 'Tokyo', 'event': 'World Championships', 'category': '-60kg' },
{ 'year': '2011', 'type': 'G', 'city': 'Paris', 'event': 'World Championships', 'category': '-60kg' },
{ 'year': '2012', 'type': 'B', 'city': 'London', 'event': 'Olympic Games', 'category': '-60kg' },
{ 'year': '2015', 'type': 'B', 'city': 'Astana', 'event': 'World Championships', 'category': '-66kg' },
{ 'year': '2016', 'type': 'B', 'city': 'Rio de Janeiro', 'event': 'Olympic Games', 'category': '-66kg' },
],
},
{
'id': 'ryoko-tani',
'name': 'Ryoko Tani',
'country': 'jp',
'birth': '1975',
'image': 'ryoko-tani.jpg',
'cover': 'ryoko-tani-cover.jpg',
'link': 'https://en.wikipedia.org/wiki/Ryoko_Tani',
'medals': [
{ 'year': '1991', 'type': 'B', 'city': 'Barcelona', 'event': 'World Championships', 'category': '-48kg' },
{ 'year': '1991', 'type': 'B', 'city': 'Osaka', 'event': 'Asian Championships', 'category': '-48kg' },
{ 'year': '1992', 'type': 'S', 'city': 'Barcelona', 'event': 'Olympic Games', 'category': '-48kg' },
{ 'year': '1993', 'type': 'G', 'city': 'Hamilton', 'event': 'World Championships', 'category': '-48kg' },
{ 'year': '1994', 'type': 'G', 'city': 'Hiroshima', 'event': 'Asian Games', 'category': '-48kg' },
{ 'year': '1995', 'type': 'G', 'city': 'Chiba', 'event': 'World Championships', 'category': '-48kg' },
{ 'year': '1995', 'type': 'G', 'city': 'Fukuoka', 'event': 'Universiade', 'category': '-48kg' },
{ 'year': '1996', 'type': 'S', 'city': 'Atlanta', 'event': 'Olympic Games', 'category': '-48kg' },
{ 'year': '1997', 'type': 'G', 'city': 'Paris', 'event': 'World Championships', 'category': '-48kg' },
{ 'year': '1999', 'type': 'G', 'city': 'Birmingham', 'event': 'World Championships', 'category': '-48kg' },
{ 'year': '2000', 'type': 'G', 'city': 'Sydney', 'event': 'Olympic Games', 'category': '-48kg' },
{ 'year': '2001', 'type': 'G', 'city': 'Munich', 'event': 'World Championships', 'category': '-48kg' },
{ 'year': '2003', 'type': 'G', 'city': 'Osaka', 'event': 'World Championships', 'category': '-48kg' },
{ 'year': '2004', 'type': 'G', 'city': 'Athens', 'event': 'Olympic Games', 'category': '-48kg' },
{ 'year': '2007', 'type': 'G', 'city': 'Rio de Janeiro', 'event': 'World Championships', 'category': '-48kg' },
{ 'year': '2008', 'type': 'B', 'city': 'Beijing', 'event': 'Olympic Games', 'category': '-48kg' },
],
},
{
'id': 'teddy-riner',
'name': 'Teddy Riner',
'country': 'fr',
'birth': '1989',
'image': 'teddy-riner.jpg',
'cover': 'teddy-riner-cover.jpg',
'link': 'https://en.wikipedia.org/wiki/Teddy_Riner',
'medals': [
{ 'year': '2007', 'type': 'G', 'city': 'Belgrade', 'event': 'European Championships', 'category': '+100kg' },
{ 'year': '2007', 'type': 'G', 'city': 'Rio de Janeiro', 'event': 'World Championships', 'category': '+100kg' },
{ 'year': '2008', 'type': 'B', 'city': 'Beijing', 'event': 'Olympic Games', 'category': '+100kg' },
{ 'year': '2008', 'type': 'G', 'city': 'Levallois-Perret', 'event': 'World Openweight Championships', 'category': 'Open' },
{ 'year': '2009', 'type': 'G', 'city': 'Pescara', 'event': 'Mediterranean Games', 'category': '+100kg' },
{ 'year': '2009', 'type': 'G', 'city': 'Rotterdam', 'event': 'World Championships', 'category': '+100kg' },
{ 'year': '2010', 'type': 'G', 'city': 'Tokyo', 'event': 'World Championships', 'category': '+100kg' },
{ 'year': '2010', 'type': 'S', 'city': 'Tokyo', 'event': 'World Championships', 'category': 'Open' },
{ 'year': '2011', 'type': 'G', 'city': 'Istanbul', 'event': 'European Championships', 'category': '+100kg' },
{ 'year': '2011', 'type': 'G', 'city': 'Paris', 'event': 'World Championships', 'category': '+100kg' },
{ 'year': '2012', 'type': 'G', 'city': 'London', 'event': 'Olympic Games', 'category': '+100kg' },
{ 'year': '2013', 'type': 'G', 'city': 'Budapest', 'event': 'European Championships', 'category': '+100kg' },
{ 'year': '2013', 'type': 'G', 'city': 'Rio de Janeiro', 'event': 'World Championships', 'category': '+100kg' },
{ 'year': '2014', 'type': 'G', 'city': 'Chelyabinsk', 'event': 'World Championships', 'category': '+100kg' },
{ 'year': '2014', 'type': 'G', 'city': 'Montpellier', 'event': 'European Championships', 'category': '+100kg' },
{ 'year': '2015', 'type': 'G', 'city': 'Astana', 'event': 'World Championships', 'category': '+100kg' },
{ 'year': '2016', 'type': 'G', 'city': 'Kazan', 'event': 'European Championships', 'category': '+100kg' },
{ 'year': '2016', 'type': 'G', 'city': 'Rio de Janeiro', 'event': 'Olympic Games', 'category': '+100kg' },
],
}
];
export default athletes;

View File

@ -0,0 +1,16 @@
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import Layout from './components/Layout';
import IndexPage from './components/IndexPage';
import AthletePage from './components/AthletePage';
import NotFoundPage from './components/NotFoundPage';
const routes = (
<Route path="/" component={Layout}>
<IndexRoute component={IndexPage} />
<Route path="athlete/:id" component={AthletePage} />
<Route path="*" component={NotFoundPage} />
</Route>
);
export default routes;

View File

@ -0,0 +1,56 @@
import path from 'path';
import { Server } from 'http';
import Express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import routes from './routes';
import NotFoundPage from './components/NotFoundPage';
// initialize the server and configure support for ejs templates
const app = new Express();
const server = new Server(app);
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '..', 'views'));
// define the folder that will be used for public assets
app.use(Express.static(path.join(__dirname, '..', 'public')));
// universal routing and rendering
app.get('*', (req, res) => {
match({routes, location: req.url}, (err, redirectLocation, renderProps) => {
// Directly send errors
if (err) {
return res.status(500).send(err.message);
}
// Propagate redirects to the browser
if (redirectLocation) {
return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
}
// generate the React markup for the current route
let markup;
if (renderProps) {
// if the current route matched, we have renderProps
markup = renderToString(<RouterContext {...renderProps} />);
} else {
markup = renderToString(<NotFoundPage />);
res.status(404);
}
// render the index template with the embedded React markup
return res.render('index', { markup:markup });
});
});
// start the server
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
server.listen(port, err => {
if (err) {
return console.error(err);
}
console.info(`Server running on http://localhost:${port} [${env}]`);
});

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Judo Heroes - A Universal Javascript demo application with React</title>
<link rel="stylesheet" href="/css/style.css" />
</head>
<body>
<noscript>JavaScript is disabled.</noscript>
<div id="main"><%- markup -%></div>
<script src="/js/bundle.js"></script>
</body>
</html>

View File

@ -0,0 +1,36 @@
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: path.join(__dirname, 'src', 'app-client.js'),
output: {
path: path.join(__dirname, 'public', 'js'),
filename: 'bundle.js'
},
module: {
loaders: [{
test: path.join(__dirname, 'src'),
loader: ['babel-loader'],
query: {
cacheDirectory: 'babel_cache',
presets: ['react', 'es2015']
}
}]
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
mangle: true,
sourcemap: false,
beautify: false,
dead_code: true
}),
]
};