├── .gitignore
├── README.md
├── app
├── App.js
├── actions
│ └── quotes.js
├── components
│ ├── Footer.js
│ ├── Main.js
│ ├── Navbar.js
│ ├── QuoteCell.js
│ ├── QuoteList.js
│ └── SubmitQuote.js
├── config
│ ├── routes.js
│ └── store.js
├── containers
│ ├── QuoteList.js
│ └── SubmitQuote.js
├── epics
│ ├── index.js
│ └── quotes.js
├── reducers
│ ├── index.js
│ └── quotes.js
└── utils
│ └── API.js
├── controllers
└── quotesController.js
├── models
└── quote.js
├── nodemon.json
├── package.json
├── public
├── bundle.js
├── css
│ └── style.css
├── image
│ ├── react-quotes-1.png
│ └── react-quotes-2.png
└── index.html
├── routes
├── apiRoutes.js
└── routes.js
├── server.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | /node_modules/
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React redux-observable exmaple app
2 | React + [redux-observable](https://redux-observable.js.org/) example app.
3 |
4 | [Live app](https://react-quotes-app.herokuapp.com)
5 |
6 | ## Quick Start
7 | ```bash
8 | #clone the repo
9 | git clone https://github.com/monad98/redux-observable-example.git
10 |
11 | #change directory to repo
12 | cd redux-observable-example
13 |
14 | # install dependencies
15 | npm install
16 |
17 | #start
18 | npm run serve
19 | //and
20 | npm run dev
21 | ```
22 |
23 | ## Packages used
24 | - React
25 | - redux
26 | - [redux-observable](https://redux-observable.js.org/)
27 | - [react-router 4.x](https://github.com/ReactTraining/react-router)
28 | - react-router-redux
29 |
30 |
31 | ## Screenshots
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/App.js:
--------------------------------------------------------------------------------
1 | // Importing ReactDOM and our routes
2 | import ReactDOM from "react-dom";
3 | import routes from "./config/routes";
4 |
5 | // Rendering our router to the "app" div in index.html
6 | ReactDOM.render(routes, document.getElementById("app"));
7 |
--------------------------------------------------------------------------------
/app/actions/quotes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Hyungwu Pae on 6/12/17.
3 | */
4 |
5 | /**
6 | * Action types
7 | */
8 | export const FETCH_QUOTES = 'FETCH_QUOTES';
9 | export const LOAD_QUOTES = 'LOAD_QUOTES';
10 | export const SAVE_QUOTE = 'SAVE_QUOTE';
11 | export const LOAD_QUOTE = 'LOAD_QUOTE';
12 | export const TOGGLE_FAVORITE = 'TOGGLE_FAVORITE';
13 | export const DELETE_QUOTE = 'DELETE_QUOTE';
14 | export const DELETE_SUCCESS = 'DELETE_SUCCESS';
15 |
16 |
17 | /**
18 | * Actions
19 | */
20 | export function fetchQuotes () {
21 | return {
22 | type: FETCH_QUOTES,
23 | payload: null
24 | }
25 | }
26 |
27 | export function loadQuotes (quotes) {
28 | return {
29 | type: LOAD_QUOTES,
30 | payload: quotes
31 | }
32 | }
33 |
34 | export function saveQuote (quote) {
35 | return {
36 | type: SAVE_QUOTE,
37 | payload: quote
38 | };
39 | }
40 |
41 | export function loadQuote (quote) {
42 | return {
43 | type: LOAD_QUOTE,
44 | payload: quote
45 | };
46 | }
47 |
48 | export function toggleFavorite (quote) {
49 | return {
50 | type: TOGGLE_FAVORITE,
51 | payload: quote
52 | };
53 | }
54 |
55 | export function deleteQuote (id) {
56 | return {
57 | type: DELETE_QUOTE,
58 | payload: id
59 | };
60 | }
61 |
62 | export function deleteSuccess (id) {
63 | return {
64 | type: DELETE_SUCCESS,
65 | payload: id
66 | };
67 | }
--------------------------------------------------------------------------------
/app/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | const Footer = () => (
3 |
9 | );
10 |
11 | export default Footer;
--------------------------------------------------------------------------------
/app/components/Main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from 'prop-types'
3 | import { Route } from 'react-router';
4 | import Navbar from '../components/Navbar';
5 | import Footer from '../components/Footer';
6 | import QuoteListContainer from '../containers/QuoteList';
7 | import SubmitQuoteContainer from '../containers/SubmitQuote';
8 | import {fetchQuotes} from '../actions/quotes';
9 | import { connect } from 'react-redux';
10 | import { NavLink } from 'react-router-dom'
11 |
12 | class Main extends React.Component{
13 |
14 | constructor(props) {
15 | super(props);
16 | props.fetchQuotes();
17 | }
18 |
19 | render() {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 | SUBMIT
27 | QUOTES
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 | }
39 | Main.propTypes = {
40 | fetchQuotes: PropTypes.func.isRequired,
41 | location: PropTypes.any
42 | };
43 |
44 | export default connect(
45 | ({routing}) => ({location: routing.location}), // NavLink update
46 | {fetchQuotes}
47 | )(Main);
--------------------------------------------------------------------------------
/app/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink, Link } from 'react-router-dom'
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 |
6 | const Navbar = ({count}) => (
7 |
20 | );
21 |
22 | Navbar.propTypes = {
23 | count: PropTypes.number.isRequired,
24 | location: PropTypes.any
25 | };
26 |
27 | //
28 | export default connect(
29 | ({ quotes, routing }) => ({ count: quotes.ids.length, location: routing.location }) //location for NavLink activeClass update
30 | )(Navbar);
--------------------------------------------------------------------------------
/app/components/QuoteCell.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const QuoteCellComponent = ({deleteQuote, toggleFavorite, quote}) => (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {quote.text}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
22 | QuoteCellComponent.propTypes = {
23 | deleteQuote: PropTypes.func.isRequired,
24 | toggleFavorite: PropTypes.func.isRequired,
25 | quote: PropTypes.shape({
26 | _id: PropTypes.string.isRequired,
27 | text: PropTypes.string.isRequired,
28 | favorited: PropTypes.bool.isRequired
29 | }).isRequired
30 | };
31 |
32 |
33 | export default QuoteCellComponent;
--------------------------------------------------------------------------------
/app/components/QuoteList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types'
3 | import QuoteCellComponent from './QuoteCell';
4 |
5 | const QuoteListComponent = ({deleteQuote, toggleFavorite, quotes}) => (
6 |
7 |
8 |
Quote List
9 |
10 |
11 |
12 |
Quote List
13 |
14 |
15 | {quotes.map((quote, idx) =>
16 | (
17 |
22 | )
23 | )}
24 |
25 |
26 |
27 | );
28 |
29 | QuoteListComponent.propTypes = {
30 | deleteQuote: PropTypes.func.isRequired,
31 | toggleFavorite: PropTypes.func.isRequired,
32 | quotes: PropTypes.arrayOf(
33 | PropTypes.shape({
34 | _id: PropTypes.string.isRequired,
35 | text: PropTypes.string.isRequired,
36 | favorited: PropTypes.bool.isRequired
37 | }).isRequired
38 | )
39 | };
40 |
41 | export default QuoteListComponent;
--------------------------------------------------------------------------------
/app/components/SubmitQuote.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types'
3 |
4 | export default class SubmitQuote extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {text: ''};
8 | }
9 |
10 | update(e) {
11 | this.setState({
12 | text: e.target.value,
13 | favorited: false
14 | })
15 | }
16 |
17 | submit(e) {
18 | e.preventDefault();
19 | this.state.text = this.state.text.trim();
20 | this.props.saveQuote(this.state);
21 | this.setState({text: ''});
22 |
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
29 |
Submit Quotes
30 |
31 |
40 |
41 | );
42 | }
43 | }
44 |
45 | SubmitQuote.propTypes = {
46 | saveQuote: PropTypes.func.isRequired
47 | };
48 |
--------------------------------------------------------------------------------
/app/config/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Provider } from 'react-redux'
3 | import configureStore, {history} from './store';
4 | import { Route } from 'react-router'
5 | import { ConnectedRouter } from 'react-router-redux'
6 | const store = configureStore();
7 | import Main from '../components/Main'
8 |
9 |
10 | const routes = (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 |
20 | export default routes;
21 |
--------------------------------------------------------------------------------
/app/config/store.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Hyungwu Pae on 6/12/17.
3 | */
4 | import { createStore, applyMiddleware, compose } from 'redux';
5 | import { createEpicMiddleware } from 'redux-observable';
6 | import { routerMiddleware } from 'react-router-redux';
7 | import rootReducer from '../reducers';
8 | import rootEpic from '../epics';
9 | import createHistory from 'history/createBrowserHistory';
10 | const epicMiddleware = createEpicMiddleware(rootEpic);
11 | import logger from 'redux-logger';
12 |
13 | // Create a history of your choosing (we're using a browser history in this case)
14 | export const history = createHistory();
15 |
16 | export default function configureStore() {
17 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
18 | return createStore(
19 | rootReducer,
20 | composeEnhancers(
21 | applyMiddleware(
22 | epicMiddleware,
23 | routerMiddleware(history),
24 | logger
25 | )
26 | )
27 | );
28 | }
--------------------------------------------------------------------------------
/app/containers/QuoteList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import {deleteQuote, toggleFavorite} from '../actions/quotes';
3 | import {getQuotesArray} from '../reducers/index';
4 | import QuoteListComponent from '../components/QuoteList';
5 |
6 | export default connect(
7 | (state) => ({quotes: getQuotesArray(state)}),
8 | { deleteQuote, toggleFavorite }
9 | )(QuoteListComponent);
10 |
11 |
--------------------------------------------------------------------------------
/app/containers/SubmitQuote.js:
--------------------------------------------------------------------------------
1 | import {saveQuote} from '../actions/quotes';
2 | import { connect } from 'react-redux';
3 | import SubmitQuoteComponent from '../components/SubmitQuote';
4 |
5 | export default connect(
6 | null,
7 | { saveQuote }
8 | )(SubmitQuoteComponent);
--------------------------------------------------------------------------------
/app/epics/index.js:
--------------------------------------------------------------------------------
1 | import { combineEpics } from 'redux-observable';
2 | import {fetchQuotes, saveQuote, toggleFavorite, deleteQuote} from "./quotes";
3 | // import * as quotes from "./quotes";
4 |
5 |
6 | export default combineEpics(
7 | fetchQuotes,
8 | saveQuote,
9 | toggleFavorite,
10 | deleteQuote
11 | );
--------------------------------------------------------------------------------
/app/epics/quotes.js:
--------------------------------------------------------------------------------
1 | import { ajax } from 'rxjs/observable/dom/ajax';
2 | import * as fromQuotes from '../actions/quotes';
3 | import 'rxjs/add/operator/map';
4 | import 'rxjs/add/operator/switchMap';
5 | import 'rxjs/add/operator/catch';
6 |
7 | export const fetchQuotes = (action$) =>
8 | action$.ofType(fromQuotes.FETCH_QUOTES)
9 | .switchMap(() =>
10 | ajax.get(`/api/quotes`)
11 | .map(ajaxRes => ajaxRes.response)
12 | .map(fromQuotes.loadQuotes)
13 | .catch(e => console.log(e))
14 | );
15 |
16 | export const saveQuote = (action$) =>
17 | action$.ofType(fromQuotes.SAVE_QUOTE)
18 | .map(action => action.payload)
19 | .switchMap(quote =>
20 | ajax.post(`/api/quotes`, quote)
21 | .map(ajaxRes => ajaxRes.response)
22 | .map(fromQuotes.loadQuote)
23 | );
24 |
25 |
26 | export const toggleFavorite = (action$) =>
27 | action$.ofType(fromQuotes.TOGGLE_FAVORITE)
28 | .map(action => action.payload)
29 | .switchMap(({_id, text, favorited}) =>
30 | ajax.patch(`/api/quotes/${_id}`, {_id, text, favorited: !favorited})
31 | .map(ajaxRes => ajaxRes.response)
32 | .map(() => fromQuotes.loadQuote({_id, text, favorited: !favorited}))
33 | );
34 |
35 | export const deleteQuote = (action$) =>
36 | action$.ofType(fromQuotes.DELETE_QUOTE)
37 | .map(action => action.payload)
38 | .switchMap(quote =>
39 | ajax.delete(`/api/quotes/${quote._id}`)
40 | .map(ajaxRes => ajaxRes.response)
41 | .map(() => fromQuotes.deleteSuccess(quote._id))
42 | );
43 |
44 |
--------------------------------------------------------------------------------
/app/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { routerReducer } from 'react-router-redux';
3 | import * as fromQuotes from './quotes';
4 | import { createSelector } from 'reselect'
5 |
6 | /**
7 | * root reduces
8 | */
9 | export default combineReducers({
10 | quotes: fromQuotes.reducer,
11 | routing: routerReducer
12 | });
13 |
14 |
15 | /**
16 | * selectors
17 | */
18 | export const getQuotes = state => state.quotes;
19 | export const getQuotesArray = createSelector(getQuotes, fromQuotes.getQuotesArray);
--------------------------------------------------------------------------------
/app/reducers/quotes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Hyungwu Pae on 6/12/17.
3 | */
4 | import * as fromQuotes from '../actions/quotes';
5 | import { createSelector } from 'reselect'
6 |
7 | const initialState = {
8 | ids: [],
9 | entities: {},
10 | };
11 |
12 | /**
13 | * Reduces
14 | */
15 | export function reducer(state = initialState, action) {
16 | switch (action.type) {
17 | case fromQuotes.LOAD_QUOTES: {
18 | const quotes = action.payload;
19 | const ids = quotes.map(q => q._id);
20 | const entities = quotes.reduce((acc, q) => Object.assign(acc, {[q._id]: q}), {});
21 | return {ids, entities};
22 | }
23 |
24 | case fromQuotes.LOAD_QUOTE: {
25 | const quote = action.payload;
26 | const entities = Object.assign({}, state.entities, {[quote._id]: quote});
27 | const ids = Object.keys(entities);
28 | return {ids, entities};
29 | }
30 |
31 | case fromQuotes.DELETE_SUCCESS: {
32 | const deletedId = action.payload;
33 | const ids = state.ids.filter(id => id !== deletedId);
34 | const entities = ids.reduce((acc, id) => Object.assign(acc, {[id]: state.entities[id]}), {});
35 | return {ids, entities};
36 | }
37 | default:
38 | return state;
39 | }
40 | }
41 |
42 | /**
43 | * Selectors
44 | */
45 | const getIds = state => state.ids;
46 | const getEntities = state => state.entities;
47 | export const getQuotesArray = createSelector([getIds, getEntities], (ids, entities) => ids.map(id => entities[id]));
48 |
--------------------------------------------------------------------------------
/app/utils/API.js:
--------------------------------------------------------------------------------
1 | // import axios from "axios";
2 |
3 | const API = {
4 | // getQuotes returns all quotes from out db
5 | // getQuotes: function() {
6 | // return axios.get("/api/quotes");
7 | // },
8 | // // Save quote saves a quote to the db,
9 | // // expects to be passed the new quotes text as an argument
10 | // saveQuote: function(text) {
11 | // return axios.post("/api/quotes", { text });
12 | // },
13 | // // deleteQuote deletes a quote from the db,
14 | // // expects the id of the quote to delete as an argument
15 | // deleteQuote: function(id) {
16 | // return axios.delete(`/api/quotes/${id}`);
17 | // },
18 | // // favorite quote toggle's a quote's 'favorite' status in the db,
19 | // // expects the quote object as an argument
20 | // favoriteQuote: function(quote) {
21 | // quote.favorited = !quote.favorited;
22 | // const { _id, favorited } = quote;
23 | // return axios.patch(`/api/quotes/${_id}`, { favorited });
24 | // }
25 | };
26 |
27 | export default API;
28 |
--------------------------------------------------------------------------------
/controllers/quotesController.js:
--------------------------------------------------------------------------------
1 | var Quote = require("../models/quote");
2 |
3 | module.exports = {
4 | // This method handles retrieving quotes from the db
5 | index: function(req, res) {
6 | var query;
7 | if (req.query) {
8 | query = req.query;
9 | }
10 | else {
11 | query = req.params.id ? { _id: req.params.id } : {};
12 | }
13 | Quote.find(query)
14 | .then(function(doc) {
15 | res.json(doc);
16 | }).catch(function(err) {
17 | res.json(err);
18 | });
19 | },
20 | // This method handles creating new quotes
21 | create: function(req, res) {
22 | Quote.create(req.body).then(function(doc) {
23 | res.json(doc);
24 | }).catch(function(err) {
25 | res.json(err);
26 | });
27 | },
28 | // This method handles updating quotes
29 | update: function(req, res) {
30 | Quote.update({
31 | _id: req.params.id
32 | },
33 | req.body
34 | ).then(function(doc) {
35 | res.json(doc);
36 | }).catch(function(err) {
37 | res.json(err);
38 | });
39 | },
40 | // This method handles deleting quotes
41 | destroy: function(req, res) {
42 | Quote.remove({
43 | _id: req.params.id
44 | }).then(function(doc) {
45 | res.json(doc);
46 | }).catch(function(err) {
47 | res.json(err);
48 | });
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/models/quote.js:
--------------------------------------------------------------------------------
1 | var mongoose = require("mongoose");
2 |
3 | var Schema = mongoose.Schema;
4 |
5 | var quoteSchema = new Schema({
6 | text: String,
7 | favorited: {
8 | type: Boolean,
9 | default: false
10 | }
11 | });
12 |
13 | var Quote = mongoose.model("Quote", quoteSchema);
14 |
15 | module.exports = Quote;
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "verbose": true,
3 | "ignore": ["app/*", "node_modules/**/node_modules"]
4 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Solved",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "dev": "webpack -w",
8 | "start": "node server.js",
9 | "serve": "nodemon server.js"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "bluebird": "^3.4.7",
16 | "body-parser": "^1.15.2",
17 | "express": "^4.14.0",
18 | "history": "^4.6.1",
19 | "mongoose": "^4.7.5",
20 | "prop-types": "^15.5.10",
21 | "react": "^15.5.4",
22 | "react-dom": "^15.5.4",
23 | "react-redux": "^5.0.4",
24 | "react-router": "^4.1.1",
25 | "react-router-dom": "^4.1.1",
26 | "react-router-redux": "^5.0.0-alpha.6",
27 | "redux": "^3.6.0",
28 | "redux-logger": "^3.0.6",
29 | "redux-observable": "^0.14.1",
30 | "reselect": "^3.0.1",
31 | "rxjs": "^5.4.0"
32 | },
33 | "devDependencies": {
34 | "babel-core": "^6.25.0",
35 | "babel-loader": "^7.0.0",
36 | "babel-preset-es2015": "^6.24.1",
37 | "babel-preset-react": "^6.24.1",
38 | "html-webpack-plugin": "^2.28.0",
39 | "nodemon": "^1.11.0",
40 | "webpack": "^2.6.1",
41 | "webpack-dev-server": "^2.4.5"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/public/css/style.css:
--------------------------------------------------------------------------------
1 | /* Styles go here */
2 | html {
3 | position: relative;
4 | min-height: 100%;
5 | }
6 | body .main.container {
7 | padding: 60px 15px 0;
8 | position: relative;
9 | margin-bottom: 60px;
10 | }
11 |
12 | footer {
13 | position: absolute;
14 | bottom: 0;
15 | width: 100%;
16 | height: 60px;
17 | display: block;
18 | }
19 |
20 | .navbar-default .navbar-nav > li > a.active {
21 | color: #4da5f4;
22 | background-color: transparent;
23 | font-weight: bold;
24 | }
25 |
26 | .btn-space {
27 | margin-right: 5px;
28 | }
29 |
30 | .label.label-primary {
31 | border-radius: 50%;
32 | }
33 |
34 | .fa.fa-star, .fa.fa-star-o {
35 | cursor: pointer;
36 | font-size: 18px;
37 | }
38 | .btn.btn-lg {
39 | margin-right: 10px;
40 | }
--------------------------------------------------------------------------------
/public/image/react-quotes-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monad98/redux-observable-example/ed267dccdc8258075ece4d65de960efe4cdcf30a/public/image/react-quotes-1.png
--------------------------------------------------------------------------------
/public/image/react-quotes-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monad98/redux-observable-example/ed267dccdc8258075ece4d65de960efe4cdcf30a/public/image/react-quotes-2.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LearnReact!
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/routes/apiRoutes.js:
--------------------------------------------------------------------------------
1 | var express = require("express");
2 |
3 | var quotesController = require("../controllers/quotesController");
4 |
5 | var router = new express.Router();
6 |
7 | // Get all quotes (or optionally a specific quote with an id)
8 | router.get("/quotes/:id?", quotesController.index);
9 | // Create a new quote using data passed in req.body
10 | router.post("/quotes", quotesController.create);
11 | // Update an existing quote with a speicified id param, using data in req.body
12 | router.patch("/quotes/:id", quotesController.update);
13 | // Delete a specific quote using the id in req.params.id
14 | router.delete("/quotes/:id", quotesController.destroy);
15 |
16 | module.exports = router;
17 |
--------------------------------------------------------------------------------
/routes/routes.js:
--------------------------------------------------------------------------------
1 | var express = require("express");
2 | var path = require("path");
3 |
4 | var apiRoutes = require("./apiRoutes");
5 |
6 | var router = new express.Router();
7 |
8 | // Use the apiRoutes module for any routes starting with "/api"
9 | router.use("/api", apiRoutes);
10 |
11 | // Otherwise send all other requests the index.html page
12 | // React router will handle routing withing the app
13 | router.get("*", function(req, res) {
14 | res.sendFile(path.join(__dirname, "../public/index.html"));
15 | });
16 |
17 | module.exports = router;
18 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | // Require our dependecies
2 | var express = require("express");
3 | var mongoose = require("mongoose");
4 | var bluebird = require("bluebird");
5 | var bodyParser = require("body-parser");
6 | var routes = require("./routes/routes");
7 |
8 | // Set up a default port, configure mongoose, configure our middleware
9 | var PORT = process.env.PORT || 3000;
10 | mongoose.Promise = bluebird;
11 | var app = express();
12 | app.use(bodyParser.urlencoded({ extended: true }));
13 | app.use(bodyParser.json());
14 | app.use(express.static(__dirname + "/public"));
15 | app.use("/", routes);
16 |
17 | var db = process.env.MONGODB_URI || "mongodb://heroku_fb4hnl6j:sjldcihu9031t59269ko8pk4t@ds163360.mlab.com:63360/heroku_fb4hnl6j";
18 |
19 | // Connect mongoose to our database
20 | mongoose.connect(db, function(error) {
21 | // Log any errors connecting with mongoose
22 | if (error) {
23 | console.error(error);
24 | }
25 | // Or log a success message
26 | else {
27 | console.log("mongoose connection is successful");
28 | }
29 | });
30 |
31 | // Start the server
32 | app.listen(PORT, function() {
33 | console.log("Now listening on port %s! Visit localhost:%s in your browser.", PORT, PORT);
34 | });
35 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 | // This is the entry point or start of our react applicaton
4 | entry: "./app/app.js",
5 |
6 | // The plain compiled Javascript will be output into this file
7 | output: {
8 | filename: "public/bundle.js"
9 | },
10 |
11 | // This section desribes the transformations we will perform
12 | module: {
13 | loaders: [
14 | {
15 | // Only working with files that in in a .js or .jsx extension
16 | test: /\.jsx?$/,
17 | // Webpack will only process files in our app folder. This avoids processing
18 | // node modules and server files unnecessarily
19 | include: /app/,
20 | loader: "babel-loader",
21 | query: {
22 | // These are the specific transformations we'll be using.
23 | presets: ["react", "es2015"]
24 | }
25 | }
26 | ]
27 | },
28 | // This lets us debug our react code in chrome dev tools. Errors will have lines and file names
29 | // Without this the console says all errors are coming from just coming from bundle.js
30 | devtool: "eval-source-map"
31 | };
32 |
--------------------------------------------------------------------------------