├── README.md
├── api
├── routes.json
├── db.json
└── db.json.example
├── ui
├── common
│ ├── components
│ │ ├── App
│ │ │ ├── App.scss
│ │ │ ├── App.js
│ │ │ ├── Header.scss
│ │ │ └── Header.js
│ │ ├── Home.scss
│ │ ├── index.js
│ │ ├── Home.js
│ │ └── Pages
│ │ │ ├── Show.js
│ │ │ ├── Page.js
│ │ │ ├── Index.js
│ │ │ └── Form.js
│ ├── containers
│ │ ├── index.js
│ │ ├── Pages
│ │ │ ├── New.js
│ │ │ ├── Index.js
│ │ │ ├── Form.js
│ │ │ └── Show.js
│ │ └── Root.js
│ ├── constants
│ │ ├── endpoints.js
│ │ └── actionTypes.js
│ ├── theme
│ │ ├── _variables.scss
│ │ └── elements.scss
│ ├── reducers
│ │ ├── index.js
│ │ └── pages.js
│ ├── routes.js
│ ├── store
│ │ └── configureStore.js
│ └── actions
│ │ └── page.js
├── config.js
├── server
│ ├── index.js
│ ├── fetchComponent.js
│ ├── server.js
│ └── ssr.js
├── .babelrc
├── lib
│ └── processSass.js
├── client
│ └── index.js
└── webpack
│ └── webpack.config.js
├── .gitignore
└── package.json
/README.md:
--------------------------------------------------------------------------------
1 | # series-wiki
--------------------------------------------------------------------------------
/api/routes.json:
--------------------------------------------------------------------------------
1 | {
2 | "/api/v1/": "/"
3 | }
4 |
--------------------------------------------------------------------------------
/ui/common/components/App/App.scss:
--------------------------------------------------------------------------------
1 | .content {
2 | margin-top: 4rem;
3 | }
4 |
--------------------------------------------------------------------------------
/ui/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | host: '127.0.0.1',
3 | apiPort: 5000,
4 | serverPort: 8080,
5 | clientPort: 8081
6 | }
7 |
--------------------------------------------------------------------------------
/ui/common/components/Home.scss:
--------------------------------------------------------------------------------
1 | .title {
2 | position: fixed;
3 | top: 50%;
4 | left: 50%;
5 | transform: translate(-50%, -50%);
6 | }
7 |
--------------------------------------------------------------------------------
/ui/common/containers/index.js:
--------------------------------------------------------------------------------
1 | export Pages from './Pages/Index'
2 | export ShowPage from './Pages/Show'
3 | export NewPage from './Pages/New'
4 |
--------------------------------------------------------------------------------
/ui/server/index.js:
--------------------------------------------------------------------------------
1 | require('babel-core/register')({
2 | ignore: [/processSass\.js/, /node_modules/]
3 | })
4 |
5 | module.exports = require('./server.js')
6 |
--------------------------------------------------------------------------------
/api/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | {
4 | "id": 1,
5 | "title": "test page#1",
6 | "content": "TEST PAGE CONTENT"
7 | }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/api/db.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | {
4 | "id": 1,
5 | "title": "test page#1",
6 | "content": "TEST PAGE CONTENT"
7 | }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/ui/common/constants/endpoints.js:
--------------------------------------------------------------------------------
1 | import config from '../../config'
2 | const API_ROOT = `http://${config.host}:${config.serverPort}/api/v1`
3 |
4 | export const PAGES_ENDPOINT = `${API_ROOT}/pages`
5 |
--------------------------------------------------------------------------------
/ui/common/components/index.js:
--------------------------------------------------------------------------------
1 | export App from './App/App'
2 | export Home from './Home'
3 | export Pages from './Pages/Index'
4 | export ShowPage from './Pages/Show'
5 | export PageForm from './Pages/Form'
6 |
--------------------------------------------------------------------------------
/ui/common/theme/_variables.scss:
--------------------------------------------------------------------------------
1 | $gray1-color: #cbcbcb;
2 | $gray2-color: #e0e0e0;
3 |
4 | $dark-gray1-color: #2d3e50;
5 |
6 | $green1-color: #18d8a9;
7 |
8 | $red1-color: #da4453;
9 |
10 | $white-color: #fff;
11 | $black-color: #000;
12 |
--------------------------------------------------------------------------------
/ui/common/containers/Pages/New.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import Form from './Form'
3 |
4 | export default class NewPageContainer extends Component {
5 | render() {
6 | return (
7 |
8 | )
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ui/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"],
3 | "plugins": [
4 | "react-hot-loader/babel",
5 | [
6 | "css-modules-transform", {
7 | "preprocessCss": "./ui/lib/processSass.js",
8 | "extensions": [".css", ".scss"]
9 | }
10 | ]
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/ui/lib/processSass.js:
--------------------------------------------------------------------------------
1 | var sass = require('node-sass');
2 | var path = require('path');
3 |
4 | module.exports = function processSass(data, filename) {
5 | var result;
6 |
7 | result = sass.renderSync({
8 | data: data,
9 | file: filename
10 | }).css;
11 |
12 | return result.toString('utf8');
13 | };
14 |
--------------------------------------------------------------------------------
/ui/common/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import { reducer as formReducer } from 'redux-form'
3 | import { routerReducer } from 'react-router-redux'
4 | import pages from './pages'
5 |
6 | export default combineReducers({
7 | form: formReducer,
8 | routing: routerReducer,
9 | pages
10 | })
11 |
--------------------------------------------------------------------------------
/ui/common/components/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styles from './Home.scss'
3 |
4 | class Home extends Component {
5 | render() {
6 | return (
7 |
8 | Welcome to BabelCoder Wiki!
9 |
10 | )
11 | }
12 | }
13 |
14 | export default Home
15 |
--------------------------------------------------------------------------------
/ui/common/components/Pages/Show.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 |
3 | const ShowPage = ({
4 | title,
5 | content
6 | }) => {
7 | return (
8 |
9 | {title}
10 |
11 | {content}
12 |
13 |
14 | )
15 | }
16 |
17 | ShowPage.propTypes = {
18 | title: PropTypes.string.isRequired,
19 | content: PropTypes.string.isRequired
20 | }
21 |
22 | export default ShowPage
23 |
--------------------------------------------------------------------------------
/ui/common/components/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import Header from './Header'
3 | import styles from './App.scss'
4 |
5 | export default class App extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
12 | {this.props.children}
13 |
14 |
15 |
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ui/common/containers/Root.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Provider } from 'react-redux'
3 | import configureStore from '../store/configureStore'
4 | import routes from '../routes'
5 |
6 | export default class Root extends Component {
7 | render() {
8 | const { history, initialState } = this.props
9 | const store = configureStore(history, initialState)
10 |
11 | return (
12 |
13 | {routes(store, history)}
14 |
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ui/server/fetchComponent.js:
--------------------------------------------------------------------------------
1 | export function fetchComponent(dispatch, components, params) {
2 | const needs =
3 | components
4 | .filter(component => component)
5 | .reduce((prev, current) => {
6 | const wrappedComponent = current.WrappedComponent
7 |
8 | return (current.need || [])
9 | .concat(
10 | (wrappedComponent && wrappedComponent.need) || []
11 | )
12 | .concat(prev)
13 | }, []
14 | )
15 |
16 | return Promise.all(needs.map(need => dispatch(need(params))))
17 | }
18 |
--------------------------------------------------------------------------------
/ui/common/constants/actionTypes.js:
--------------------------------------------------------------------------------
1 | export const LOAD_PAGES_REQUEST = 'LOAD_PAGES_REQUEST'
2 | export const LOAD_PAGES_SUCCESS = 'LOAD_PAGES_SUCCESS'
3 | export const LOAD_PAGES_FAILURE = 'LOAD_PAGES_FAILURE'
4 |
5 | export const LOAD_PAGE_REQUEST = 'LOAD_PAGE_REQUEST'
6 | export const LOAD_PAGE_SUCCESS = 'LOAD_PAGE_SUCCESS'
7 | export const LOAD_PAGE_FAILURE = 'LOAD_PAGE_FAILURE'
8 |
9 | export const CREATE_PAGE_REQUEST = 'CREATE_PAGE_REQUEST'
10 | export const CREATE_PAGE_SUCCESS = 'CREATE_PAGE_SUCCESS'
11 | export const CREATE_PAGE_FAILURE = 'CREATE_PAGE_FAILURE'
12 |
--------------------------------------------------------------------------------
/ui/common/reducers/pages.js:
--------------------------------------------------------------------------------
1 | import {
2 | LOAD_PAGES_SUCCESS,
3 | LOAD_PAGE_SUCCESS
4 | } from '../constants/actionTypes'
5 |
6 | const initialState = []
7 |
8 | export default (state = initialState, action) => {
9 | switch(action.type) {
10 | case LOAD_PAGES_SUCCESS:
11 | return action.payload
12 | case LOAD_PAGE_SUCCESS:
13 | return [action.payload]
14 | default:
15 | return state
16 | }
17 | }
18 |
19 | export const getPageById = (state, id) => (
20 | state.pages.find((page) => page.id === +id) || { title: '', content: '' }
21 | )
22 |
--------------------------------------------------------------------------------
/ui/common/components/Pages/Page.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import { Link } from 'react-router'
3 |
4 | export default class Page extends Component {
5 | static propTypes = {
6 | id: PropTypes.number.isRequired,
7 | title: PropTypes.string.isRequired
8 | }
9 |
10 | render() {
11 | const { id, title } = this.props
12 |
13 | return (
14 |
15 | | {id} |
16 | {title} |
17 |
18 | Show
19 | |
20 |
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ui/server/server.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import httpProxy from 'http-proxy'
3 | import ssr from './ssr'
4 | import config from '../config'
5 |
6 | const PORT = config.serverPort
7 | const app = express()
8 | const targetUrl = `http://${config.host}:${config.apiPort}`
9 | const proxy = httpProxy.createProxyServer({
10 | target: targetUrl
11 | })
12 |
13 | app.use('/api', (req, res) => {
14 | proxy.web(req, res, { target: `${targetUrl}/api` });
15 | })
16 | app.use(ssr)
17 |
18 | app.listen(PORT, error => {
19 | if (error) {
20 | console.error(error)
21 | } else {
22 | console.info(`==> Listening on port ${PORT}.`)
23 | }
24 | })
25 |
--------------------------------------------------------------------------------
/ui/common/components/App/Header.scss:
--------------------------------------------------------------------------------
1 | @import '../../theme/variables';
2 |
3 | .header {
4 | background: $dark-gray1-color;
5 | padding: 1rem;
6 | width: 100%;
7 | position: fixed;
8 | left: 0;
9 | top: 0;
10 | box-sizing: border-box;
11 | }
12 |
13 | .brand {
14 | color: $white-color;
15 | text-decoration: none;
16 | }
17 |
18 | .menu {
19 | float: right;
20 | display: inline-block;
21 | list-style: none;
22 | margin: 0;
23 | padding: 0;
24 |
25 | &__item {
26 | display: inline-block;
27 | vertical-align: middle;
28 | }
29 |
30 | &__link {
31 | padding: 1rem;
32 | color: $green1-color;
33 | text-decoration: none;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
35 | # build directory
36 | static
37 |
38 | # API database
39 | api/db.json
40 |
--------------------------------------------------------------------------------
/ui/common/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { syncHistoryWithStore } from 'react-router-redux'
3 | import { Router, Route, IndexRoute } from 'react-router'
4 | import {
5 | Pages,
6 | ShowPage,
7 | NewPage
8 | } from './containers'
9 | import {
10 | App,
11 | Home
12 | } from './components'
13 |
14 | export default (store, history) => {
15 | return (
16 |
17 |
19 |
20 |
21 |
22 |
24 |
26 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/ui/client/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { render } from 'react-dom'
3 | import { browserHistory } from 'react-router'
4 | import { AppContainer } from 'react-hot-loader'
5 | import Root from '../common/containers/Root'
6 |
7 | const initialState = window.__INITIAL_STATE__
8 | const rootEl = document.getElementById('app')
9 |
10 | render(
11 |
12 |
15 | ,
16 | rootEl
17 | )
18 |
19 | if (module.hot) {
20 | module.hot.accept('../common/containers/Root', () => {
21 | const NextRootApp = require('../common/containers/Root').default
22 |
23 | render(
24 |
25 |
28 | ,
29 | rootEl
30 | )
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/ui/common/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux'
2 | import { routerMiddleware } from 'react-router-redux'
3 | import thunk from 'redux-thunk'
4 | import { apiMiddleware } from 'redux-api-middleware'
5 | import createLogger from 'redux-logger'
6 | import rootReducer from '../reducers'
7 |
8 | export default (history, initialState) => {
9 | const middlewares = [thunk, apiMiddleware, routerMiddleware(history)]
10 |
11 | if(process.env.NODE_ENV !== 'production')
12 | middlewares.push(createLogger())
13 |
14 | const store = createStore(
15 | rootReducer,
16 | initialState,
17 | applyMiddleware(...middlewares)
18 | )
19 |
20 | if (module.hot) {
21 | module.hot.accept('../reducers', () => {
22 | System.import('../reducers').then(nextRootReducer =>
23 | store.replaceReducer(nextRootReducer.default)
24 | )
25 | })
26 | }
27 |
28 | return store
29 | }
30 |
--------------------------------------------------------------------------------
/ui/common/containers/Pages/Index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import { connect } from 'react-redux'
3 | import { loadPages } from '../../actions/page'
4 | import { Pages } from '../../components'
5 |
6 | class PagesContainer extends Component {
7 | static propTypes = {
8 | pages: PropTypes.array.isRequired,
9 | onLoadPages: PropTypes.func.isRequired
10 | }
11 |
12 | static need = [
13 | loadPages
14 | ]
15 |
16 | shouldComponentUpdate(nextProps) {
17 | return this.props.pages !== nextProps.pages;
18 | }
19 |
20 | onReloadPages = () => {
21 | this.props.onLoadPages()
22 | }
23 |
24 | componentDidMount() {
25 | this.onReloadPages()
26 | }
27 |
28 | render() {
29 | return (
30 |
33 | )
34 | }
35 | }
36 |
37 | export default connect(
38 | (state) => ({ pages: state.pages }),
39 | { onLoadPages: loadPages }
40 | )(PagesContainer)
41 |
--------------------------------------------------------------------------------
/ui/common/containers/Pages/Form.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import { reduxForm } from 'redux-form'
3 | import { createPage } from '../../actions/page'
4 | import { PageForm } from '../../components'
5 |
6 | const FIELDS = ['title', 'content']
7 |
8 | class PageFormContainer extends Component {
9 | static propTypes = {
10 | fields: PropTypes.object.isRequired,
11 | handleSubmit: PropTypes.func.isRequired
12 | }
13 |
14 | render() {
15 | const { fields, handleSubmit } = this.props
16 |
17 | return (
18 |
21 | )
22 | }
23 | }
24 |
25 | export default reduxForm({
26 | form: 'page',
27 | fields: FIELDS,
28 | validate: (values, props) =>
29 | FIELDS.reduce((errors, field) =>
30 | values[field] ? errors : { ...errors, [field]: 'Required' }, {})
31 | },
32 | (state) => ({}),
33 | (dispatch) => ({
34 | onSubmit: (values) =>
35 | dispatch(createPage(values))
36 | })
37 | )(PageFormContainer)
38 |
--------------------------------------------------------------------------------
/ui/common/components/App/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Link } from 'react-router'
3 | import styles from './Header.scss'
4 |
5 | export default class Header extends Component {
6 | render() {
7 | return (
8 |
9 |
32 |
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/ui/common/components/Pages/Index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import { Link } from 'react-router'
3 | import fetch from 'isomorphic-fetch'
4 | import Page from './Page'
5 |
6 | const Pages = ({
7 | pages,
8 | onReloadPages
9 | }) => (
10 |
11 |
16 |
Create New Page
17 |
18 |
19 |
20 |
21 | | ID |
22 | Title |
23 | Action |
24 |
25 |
26 |
27 | {
28 | pages.map((page) => (
29 |
33 | ))
34 | }
35 |
36 |
37 |
38 | )
39 |
40 | Pages.propTypes = {
41 | pages: PropTypes.array.isRequired,
42 | onReloadPages: PropTypes.func.isRequired
43 | }
44 |
45 | export default Pages
46 |
--------------------------------------------------------------------------------
/ui/common/containers/Pages/Show.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import { connect } from 'react-redux'
3 | import { loadPage } from '../../actions/page'
4 | import { getPageById } from '../../reducers/pages'
5 | import { ShowPage } from '../../components'
6 |
7 | class ShowPageContainer extends Component {
8 | static propTypes = {
9 | page: PropTypes.object.isRequired,
10 | onLoadPage: PropTypes.func.isRequired
11 | }
12 |
13 | static need = [
14 | (params) => (loadPage(params.id))
15 | ]
16 |
17 | shouldComponentUpdate(nextProps) {
18 | return this.props.page !== nextProps.page;
19 | }
20 |
21 | componentDidMount() {
22 | const { onLoadPage, params: { id } } = this.props
23 |
24 | onLoadPage(id)
25 | }
26 |
27 | render() {
28 | const { id, title, content } = this.props.page
29 |
30 | return (
31 |
35 | )
36 | }
37 | }
38 |
39 | export default connect(
40 | (state, ownProps) => ({ page: getPageById(state, ownProps.params.id) }),
41 | { onLoadPage: loadPage }
42 | )(ShowPageContainer)
43 |
--------------------------------------------------------------------------------
/ui/common/components/Pages/Form.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 |
3 | const errorMessageElement = (field) => (
4 | field['touched'] &&
5 | field['error'] &&
6 | {field['error']}
7 | )
8 | const PageForm = ({
9 | fields,
10 | handleSubmit
11 | }) => {
12 | const { title, content } = fields
13 |
14 | return (
15 |
37 | )
38 | }
39 |
40 | PageForm.propTypes = {
41 | fields: PropTypes.shape({
42 | title: PropTypes.object.isRequired,
43 | content: PropTypes.object.isRequired
44 | }).isRequired,
45 | handleSubmit: PropTypes.func.isRequired
46 | }
47 |
48 | export default PageForm
49 |
--------------------------------------------------------------------------------
/ui/common/theme/elements.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | $border-style: 1px solid $gray1-color;
4 |
5 | :global {
6 | body {
7 | font-size: 16px;
8 | }
9 |
10 | .container {
11 | width: 50%;
12 | margin: 0 auto;
13 | }
14 |
15 | // Table
16 | .table {
17 | border-collapse: collapse;
18 | border-spacing: 0;
19 | empty-cells: show;
20 | border: $border-style;
21 |
22 | td, th {
23 | border-left: $border-style;
24 | border-width: 0 0 0 1px;
25 | font-size: inherit;
26 | margin: 0;
27 | overflow: visible;
28 | padding: 0.5rem 1rem;
29 | }
30 |
31 | thead {
32 | background-color: $gray2-color;
33 | color: $black-color;
34 | text-align: left;
35 | vertical-align: bottom;
36 | }
37 | }
38 |
39 | // Button
40 | .button {
41 | display: inline-block;
42 | vertical-align: middle;
43 | text-align: center;
44 | cursor: pointer;
45 | user-select: none;
46 | box-sizing: border-box;
47 | padding: 0.5rem 1rem;
48 | text-decoration: none;
49 | background-color: $gray2-color;
50 | color: $dark-gray1-color;
51 | border: none;
52 | }
53 |
54 | // Form
55 | .form {
56 | fieldset {
57 | margin: 0;
58 | padding: 0.35rem 0 0.75rem;
59 | border: 0;
60 | }
61 |
62 | input[type='text'],
63 | textarea,
64 | label {
65 | display: block;
66 | margin: 0.25rem 0;
67 | width: 100%;
68 | }
69 |
70 | .error {
71 | color: $red1-color;
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/ui/common/actions/page.js:
--------------------------------------------------------------------------------
1 | import { CALL_API } from 'redux-api-middleware'
2 | import { push } from 'react-router-redux'
3 | import { PAGES_ENDPOINT } from '../constants/endpoints'
4 | import {
5 | LOAD_PAGES_REQUEST,
6 | LOAD_PAGES_SUCCESS,
7 | LOAD_PAGES_FAILURE,
8 |
9 | LOAD_PAGE_REQUEST,
10 | LOAD_PAGE_SUCCESS,
11 | LOAD_PAGE_FAILURE,
12 |
13 | CREATE_PAGE_REQUEST,
14 | CREATE_PAGE_SUCCESS,
15 | CREATE_PAGE_FAILURE
16 | } from '../constants/actionTypes'
17 |
18 | export const loadPages = () => ({
19 | [CALL_API]: {
20 | endpoint: PAGES_ENDPOINT,
21 | method: 'GET',
22 | types: [LOAD_PAGES_REQUEST, LOAD_PAGES_SUCCESS, LOAD_PAGES_FAILURE]
23 | }
24 | })
25 |
26 | export const loadPage = (id) => ({
27 | [CALL_API]: {
28 | endpoint: `${PAGES_ENDPOINT}/${id}`,
29 | method: 'GET',
30 | types: [LOAD_PAGE_REQUEST, LOAD_PAGE_SUCCESS, LOAD_PAGE_FAILURE]
31 | }
32 | })
33 |
34 | export const createPage = (values) => (
35 | (dispatch) =>
36 | dispatch({
37 | [CALL_API]: {
38 | endpoint: PAGES_ENDPOINT,
39 | headers: {
40 | 'Accept': 'application/json',
41 | 'Content-Type': 'application/json'
42 | },
43 | method: 'POST',
44 | body: JSON.stringify(values),
45 | types: [
46 | CREATE_PAGE_REQUEST,
47 | {
48 | type: CREATE_PAGE_SUCCESS,
49 | payload: (_action, _state, res) => {
50 | return res.json().then((page) => {
51 | dispatch(push(`/pages/${page.id}`))
52 | return page
53 | })
54 | }
55 | },
56 | CREATE_PAGE_FAILURE
57 | ]
58 | }
59 | }
60 | )
61 | )
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "babelcoder-wiki",
3 | "version": "1.0.0",
4 | "description": "BabelCoder Wiki!",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "npm-run-all --parallel start-dev-api start-dev-ui start-dev-ssr",
8 | "start-dev-api": "json-server --watch api/db.json --routes api/routes.json --port 5000",
9 | "start-dev-ui": "webpack-dev-server --config ui/webpack/webpack.config.js",
10 | "start-dev-ssr": "nodemon ./ui/server/index.js"
11 | },
12 | "author": "Nuttavut Thongjor",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "autoprefixer": "^6.3.6",
16 | "babel-core": "^6.8.0",
17 | "babel-loader": "^6.2.4",
18 | "babel-plugin-css-modules-transform": "^0.1.1",
19 | "babel-plugin-transform-runtime": "^6.8.0",
20 | "babel-preset-es2015": "^6.6.0",
21 | "babel-preset-react": "^6.5.0",
22 | "babel-preset-stage-0": "^6.5.0",
23 | "css-loader": "^0.23.1",
24 | "express": "^4.14.0",
25 | "json-server": "^0.8.12",
26 | "node-sass": "^3.7.0",
27 | "nodemon": "^1.9.2",
28 | "npm-run-all": "^1.8.0",
29 | "postcss-loader": "^0.9.1",
30 | "react-hot-loader": "^3.0.0-beta.1",
31 | "redux-logger": "^2.6.1",
32 | "sass-loader": "^3.2.0",
33 | "style-loader": "^0.13.1",
34 | "webpack": "^2.1.0-beta.7",
35 | "webpack-dev-server": "^2.0.0-beta"
36 | },
37 | "dependencies": {
38 | "babel-runtime": "^6.6.1",
39 | "http-proxy": "^1.14.0",
40 | "isomorphic-fetch": "^2.2.1",
41 | "react": "^15.0.2",
42 | "react-dom": "^15.0.2",
43 | "react-redux": "^4.4.5",
44 | "react-router": "^3.0.0-alpha.1 ",
45 | "react-router-redux": "^4.0.4",
46 | "redux": "^3.5.2",
47 | "redux-api-middleware": "^1.0.2",
48 | "redux-form": "^5.2.5",
49 | "redux-thunk": "^2.1.0"
50 | },
51 | "peerDependencies": {
52 | "react": "^15.0.2",
53 | "react-dom": "^15.0.2"
54 | },
55 | "engines": {
56 | "node": "6.0.0",
57 | "npm": "3.8.6"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/ui/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const autoprefixer = require('autoprefixer');
4 | const config = require('../config');
5 |
6 | module.exports = {
7 | devtool: 'eval',
8 | entry: [
9 | 'react-hot-loader/patch',
10 | `webpack-dev-server/client?http://${config.host}:${config.clientPort}`,
11 | 'webpack/hot/only-dev-server',
12 | './ui/common/theme/elements.scss',
13 | './ui/client/index.js'
14 | ],
15 | output: {
16 | publicPath: `http://${config.host}:${config.clientPort}/static/`,
17 | path: path.join(__dirname, 'static'),
18 | filename: 'bundle.js'
19 | },
20 | plugins: [
21 | new webpack.HotModuleReplacementPlugin()
22 | ],
23 | module: {
24 | loaders: [
25 | {
26 | test: /\.jsx?$/,
27 | exclude: /node_modules/,
28 | loaders: [
29 | {
30 | loader: 'babel-loader',
31 | query: {
32 | babelrc: false,
33 | presets: ["es2015", "stage-0", "react"]
34 | }
35 | }
36 | ]
37 | },
38 | {
39 | test: /\.css$/,
40 | loaders: [
41 | 'style-loader',
42 | 'css-loader'
43 | ]
44 | }, {
45 | test: /\.scss$/,
46 | exclude: /node_modules/,
47 | loaders: [
48 | 'style-loader',
49 | {
50 | loader: 'css-loader',
51 | query: {
52 | sourceMap: true,
53 | module: true,
54 | localIdentName: '[name]__[local]___[hash:base64:5]'
55 | }
56 | },
57 | {
58 | loader: 'sass-loader',
59 | query: {
60 | outputStyle: 'expanded',
61 | sourceMap: true
62 | }
63 | },
64 | 'postcss-loader'
65 | ]
66 | }
67 | ]
68 | },
69 | postcss: function () {
70 | return [autoprefixer];
71 | },
72 | devServer: {
73 | port: config.clientPort,
74 | hot: true,
75 | inline: false,
76 | historyApiFallback: true
77 | }
78 | };
79 |
--------------------------------------------------------------------------------
/ui/server/ssr.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { match, RouterContext } from 'react-router'
3 | import { renderToString } from 'react-dom/server'
4 | import { Provider } from 'react-redux'
5 | import createMemoryHistory from 'react-router/lib/createMemoryHistory'
6 | import { syncHistoryWithStore } from 'react-router-redux'
7 | import configureStore from '../common/store/configureStore'
8 | import Root from '../common/containers/Root'
9 | import getRoutes from '../common/routes'
10 | import { fetchComponent } from './fetchComponent.js'
11 |
12 | const renderHtml = (html, initialState) => (`
13 |
14 |
15 |
16 |
17 | Wiki!
18 |
19 |
20 | ${html}
21 |
24 |
25 |
26 |
27 | `)
28 |
29 | export default function(req, res) {
30 | const memoryHistory = createMemoryHistory(req.originalUrl)
31 | const store = configureStore(memoryHistory)
32 | const history = syncHistoryWithStore(memoryHistory, store)
33 |
34 | match({
35 | routes: getRoutes(store, history),
36 | location: req.originalUrl
37 | }, (error, redirectLocation, renderProps) => {
38 | if (error) {
39 | console.log(error)
40 | res.status(500).send('Internal Server Error')
41 | } else if (redirectLocation) {
42 | res.redirect(302, `${redirectLocation.pathname}${redirectLocation.search}`)
43 | } else if (renderProps) {
44 | const { components, params } = renderProps
45 |
46 | fetchComponent(store.dispatch, components, params)
47 | .then(html => {
48 | const componentHTML = renderToString(
49 |
50 |
51 |
52 | )
53 | const initialState = store.getState()
54 |
55 | res.status(200).send(
56 | renderHtml(componentHTML, initialState)
57 | )
58 | })
59 | .catch(error => {
60 | console.log(error)
61 | res.status(500).send('Internal Server Error')
62 | })
63 | } else {
64 | res.status(404).send('Not found')
65 | }
66 | })
67 | }
68 |
--------------------------------------------------------------------------------