Skip to content

Commit

Permalink
Add bike share example
Browse files Browse the repository at this point in the history
  • Loading branch information
doug-wade committed Jun 10, 2016
1 parent 13f692a commit d0f9b8a
Show file tree
Hide file tree
Showing 22 changed files with 534 additions and 0 deletions.
3 changes: 3 additions & 0 deletions packages/react-server-examples/bike-share/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["react-server"]
}
3 changes: 3 additions & 0 deletions packages/react-server-examples/bike-share/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
build
__clientTemp
5 changes: 5 additions & 0 deletions packages/react-server-examples/bike-share/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM node:slim

EXPOSE 3000
ENV NODE_ENV=docker-dev
VOLUME /www
82 changes: 82 additions & 0 deletions packages/react-server-examples/bike-share/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# react-server-examples/bike-share

An example project for `react-server` which demos server rendering, interactivity
on the client side, frameback, ReactServerAgent, url parameters and logging.
Uses [api.citybik.es](http://api.citybik.es/v2/) to get data about bike shares
and their availability around the world.

To start in development mode:

```shell
npm start
```

Then go to [localhost:3000](http://localhost:3000/). You will see an index page
that shows the covered bike share networks around the world. Each bike share
network is a link to a details page that, when clicked, loads an iframe in front
of the index page containing a network page for that bike share network,
including information about each station in that network, and the number of
available bikes the last time that data is available for.

If you want to optimize the client code at the expense of startup time, type
`NODE_ENV=production npm start`. You can also use
[any react-server-cli arguments](../../react-server-cli#setting-options-manually)
after `--`. For example:

```shell
# start in dev mode on port 4000
npm start -- --port=4000
```

# Developing using Docker and Docker Compose

These steps assume you are familiar with docker and already have it installed.
Some basics:

1. Download [Docker Toolbox](https://www.docker.com/products/docker-toolbox) and
install it.
2. Start `docker quick start shell`
3. Navigate to where you generated the project
4. Add a configuration to set the `host` option to the ip given by
`docker-machine ip`. An example configuration might be like:
```json
{
"port": "3000",
"env": {
"docker": {
"host": "Your ip from `docker-machine ip` here"
},
"staging": {
"port": "3000"
},
"production": {
"port": "80"
}
}
}
```
5. Now that your system is ready to go, start the containers:
```shell
docker-compose build --pull
docker-compose up
```

The containers will now be running. At any time, press ctrl+c to stop them.

To clean up, run the following commands:

```shell
docker-compose stop
docker-compose rm --all
docker volume ls # and get the name of the volume ending in react_server_node_modules
# this name will be different depending on the name of the project
docker volume rm _react_server_node_modules
```

The configuration included stores the node_modules directory in a "named volume".
This is a special persistent data-store that Docker uses to keep around the
node_modules directory so that they don't have to be built on each run of the
container. If you need to get into the container in order to investigate what
is in the volume, you can run `docker-compose exec react_server bash` which will
open a shell in the container. Be aware that the exec functionality doesn't
exist in Windows (as of this writing).
32 changes: 32 additions & 0 deletions packages/react-server-examples/bike-share/api/network.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {ReactServerAgent, logging} from 'react-server';

const logger = logging.getLogger(__LOGGER__);

export default class NetworkApi {
setConfigValues() {
return {isRawResponse: true};
}

handleRoute(next) {
this.network = this.getRequest().getQuery().network;
logger.info(`got network api request${this.network ? ' for network ' + this.network : ''}`);
return next();
}

getContentType() {
return 'application/json';
}

getResponseData() {
let url = 'http://api.citybik.es/v2/networks';
if (this.network) {
url += `/${this.network}`;
}
return new Promise(resolve => {
ReactServerAgent.get(url).then(data => {
logger.info(`got data ${JSON.stringify(data)} from url ${url}`);
resolve(JSON.stringify(data));
});
});
}
}
14 changes: 14 additions & 0 deletions packages/react-server-examples/bike-share/components/footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import {logging} from 'react-server';

const logger = logging.getLogger(__LOGGER__);

export default () => {
logger.info('rendering the footer');
return (<div className="footer">
<span>Brought to you by </span>
<a href="http://github.com/redfin/react-server">React Server</a>
<span> and </span>
<a href="http://api.citybik.es/v2/">citybik.es</a>
</div>);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import {logging} from 'react-server';

const logger = logging.getLogger(__LOGGER__);

export default () => {
logger.info('rendering the header');
return (<h1 className="header">React Server city bikes page</h1>);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import {logging, Link} from 'react-server';

const logger = logging.getLogger(__LOGGER__);

const NetworkCard = ({id, name, location, company}) => {
logger.info(`rendering card for network ${name}`);
return (
<div><Link path={`/network?network=${id}`} frameback>{name}</Link> in {location.city}, {location.country}, run by {company}</div>
);
};

NetworkCard.propTypes = {
company: React.PropTypes.string,
href: React.PropTypes.string,
id: React.PropTypes.string,
location: React.PropTypes.shape({
city: React.PropTypes.string,
country: React.PropTypes.string,
latitude: React.PropTypes.number,
longitude: React.PropTypes.number
}),
name: React.PropTypes.string,
stations: React.PropTypes.array
};

NetworkCard.displayName = 'NetworkCard';

export default NetworkCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import {logging} from 'react-server';
import NetworkCard from './network-card';

const logger = logging.getLogger(__LOGGER__);

const NetworkList = ({networks}) => {
logger.info(`rendering list of ${networks.length} networks`);
const networkCards = networks.map(network => {
return <NetworkCard key={network.id} {...network}/>;
});
return <div>{networkCards}</div>;
};

NetworkList.propTypes = {
networks: React.PropTypes.array
};

NetworkList.displayName = 'NetworkList';

export default NetworkList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import {logging} from 'react-server';

const logger = logging.getLogger(__LOGGER__);
const timeSinceTimestamp = s => {
const parsed = Date.parse(s);
const timeSince = (new Date()) - parsed;
const minutesSince = Math.floor(timeSince / 60000);
const secondsSince = Math.floor((timeSince / 1000) % 60);
return `${minutesSince} min, ${secondsSince} sec`;
};

const StationCard = ({station}) => {
logger.info(`rendering card for station ${JSON.stringify(station)}`);
return (
<div>{station.name} had {station.empty_slots} empty slots {timeSinceTimestamp(station.timestamp)} ago.</div>
);
};

StationCard.propTypes = {
station: React.PropTypes.shape({
empty_slots: React.PropTypes.number, // eslint-disable-line
extra: React.PropTypes.object,
free_bikes: React.PropTypes.number, // eslint-disable-line
id: React.PropTypes.string,
latitude: React.PropTypes.number,
longitude: React.PropTypes.number,
name: React.PropTypes.string,
timestamp: React.PropTypes.string
})
};

StationCard.displayName = 'StationCard';

export default StationCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import {logging} from 'react-server';
import StationCard from './station-card';

const logger = logging.getLogger(__LOGGER__);

const StationList = ({stations}) => {
logger.info(`rendering list of ${stations.length} stations`);
const stationCards = stations.map(station => <StationCard station={station} key={station.id}/>);
return <div>{stationCards}</div>;
};

StationList.propTypes = {
stations: React.PropTypes.array
};

StationList.displayName = 'StationList';

export default StationList;
22 changes: 22 additions & 0 deletions packages/react-server-examples/bike-share/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: '2'
services:
react_server: # The label of the service
build: . # The location of the Dockerfile to build
volumes: # Files to share with the container and the host
- .:/www # Share the project in /www in the container
- react_server_node_modules:/www/node_modules # A special volume so that OS specific node_modules are built correctly
working_dir: /www # The location all commands should run from
environment:
NODE_ENV: 'docker' # Set the NODE_ENV environment variable
command: /bin/bash -c "npm install && npm start" # The command to run in when the container starts
ports: # Ports to expose to your host
- '3000:3000'
- '3001:3001'
# An example database
# my_db:
# image: rethinkdb:latest
# You can reference the db/service by using the dns name `my_db` and docker will do the rest

# Volume to store separate from the container runtime and host
volumes:
react_server_node_modules:
12 changes: 12 additions & 0 deletions packages/react-server-examples/bike-share/gulpfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const gulp = require('gulp');
const tagger = require('react-server-gulp-module-tagger');
const babel = require('gulp-babel');

gulp.task('default', () => {
gulp.src(['api/*.js', 'components/*.js', 'middleware/*.js', 'pages/*.js', 'routes.js'], {base: '.'})
.pipe(tagger())
.pipe(babel({
presets: ['react-server']
}))
.pipe(gulp.dest('build/'));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {ReactServerAgent} from 'react-server';
export default class RequestToPortMiddleware {
handleRoute(next) {
if (typeof window === 'undefined') { //eslint-disable-line
ReactServerAgent.plugRequest(req => {
req.urlPrefix('localhost:3000');
});
}
return next();
}
}
47 changes: 47 additions & 0 deletions packages/react-server-examples/bike-share/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "react-server-bike-share",
"version": "0.0.1",
"private": true,
"description": "A react-server instance",
"main": "HelloWorld.js",
"author": "Doug Wade <[email protected]>",
"scripts": {
"clean": "rm -rf build __clientTemp",
"start": "npm run clean && gulp && npm run styles && react-server-cli --routesFile build/routes.js",
"styles": "node-sass styles/index.scss build/styles/index.css && node-sass styles/network.scss build/styles/network.css",
"test": "xo && nsp check"
},
"license": "Apache-2.0",
"dependencies": {
"babel-plugin-transform-runtime": "^6.8.0",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-runtime": "^6.6.1",
"react": "~0.14.2",
"react-dom": "~0.14.2",
"react-server": "^0.3.1",
"react-server-cli": "^0.3.1",
"superagent": "1.2.0"
},
"devDependencies": {
"babel-preset-react-server": "^0.3.0",
"eslint-config-xo-react": "^0.7.0",
"eslint-plugin-react": "^5.1.1",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"node-sass": "^3.7.0",
"nsp": "^2.3.3",
"react-server-gulp-module-tagger": "^0.3.0",
"xo": "^0.15.1"
},
"xo": {
"esnext": true,
"extends": "xo-react",
"globals": [
"__LOGGER__"
],
"ignores": [
"__clientTemp/**/*"
]
}
}
Loading

0 comments on commit d0f9b8a

Please sign in to comment.