├── .babelrc
├── .coveralls.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .scss-lint.yaml
├── .travis.yml
├── README.md
├── app
├── actions
│ ├── auth.js
│ ├── locale.js
│ ├── page-title.js
│ └── requests.js
├── beanManager
│ ├── actions
│ │ └── bean.js
│ ├── components
│ │ └── list.jsx
│ ├── stores
│ │ └── bean.js
│ └── styles
│ │ └── bean.scss
├── commentManager
│ ├── actions
│ │ └── comment.js
│ ├── components
│ │ └── list.jsx
│ ├── stores
│ │ └── comment.js
│ └── styles
│ │ └── comment.scss
├── components
│ ├── app.jsx
│ ├── footer.jsx
│ ├── guides.jsx
│ ├── header.jsx
│ ├── protected.jsx
│ └── shared
│ │ ├── alloyeditor.jsx
│ │ ├── dropImg.jsx
│ │ ├── img.jsx
│ │ ├── lang-picker.jsx
│ │ ├── require-auth.jsx
│ │ └── spinner.jsx
├── data
│ ├── en.js
│ └── fr.js
├── fonts
│ ├── Roboto-Medium-webfont.eot
│ ├── Roboto-Medium-webfont.svg
│ ├── Roboto-Medium-webfont.ttf
│ └── Roboto-Medium-webfont.woff
├── images
│ ├── favicon.ico
│ ├── no-preview.png
│ ├── react-logo.png
│ └── spinner.svg
├── index.js
├── main.js
├── pages
│ ├── login-info.jsx
│ ├── not-found.jsx
│ └── server-error.jsx
├── postManager
│ ├── actions
│ │ └── posts.js
│ ├── components
│ │ ├── create.jsx
│ │ ├── edit.jsx
│ │ ├── list.jsx
│ │ └── single.jsx
│ ├── stores
│ │ └── posts.js
│ └── styles
│ │ └── post.scss
├── routes.jsx
├── stores
│ ├── auth.js
│ ├── locale.js
│ ├── page-title.js
│ └── requests.js
├── styles
│ ├── _application.scss
│ ├── _fonts.scss
│ ├── footer.scss
│ ├── header.scss
│ ├── lang-picker.scss
│ ├── main.scss
│ ├── mixins
│ │ ├── _class-helper.scss
│ │ ├── _hide-text.scss
│ │ └── _reset-list.scss
│ ├── spinner.scss
│ └── test.scss
├── userManager
│ ├── actions
│ │ ├── resetpwd.js
│ │ ├── role.js
│ │ └── users.js
│ ├── components
│ │ ├── create.jsx
│ │ ├── profile.jsx
│ │ ├── resetpwd.jsx
│ │ └── users.jsx
│ ├── stores
│ │ ├── resetpwd.js
│ │ ├── role.js
│ │ └── users.js
│ └── styles
│ │ ├── profile.scss
│ │ └── users.scss
└── utils
│ ├── alt-resolver.js
│ ├── flux.js
│ ├── image-resolver.js
│ ├── intl-loader.js
│ ├── intl-polyfill.js
│ ├── localized-routes.js
│ └── promisify.js
├── karma.conf.js
├── package.json
├── processes.json
├── server
├── auth.js
├── bootstrap.js
├── config
│ ├── all.json
│ ├── development.js
│ ├── init.js
│ ├── production.js
│ └── test.js
├── controllers
│ ├── auth.js
│ ├── bean.js
│ ├── comment.js
│ ├── index.js
│ ├── post.js
│ ├── role.js
│ └── user.js
├── index.js
├── koa.js
├── models
│ ├── bean.js
│ ├── comment.js
│ ├── index.js
│ ├── post.js
│ ├── role.js
│ ├── tag.js
│ └── user.js
├── router.jsx
├── services
│ ├── index.js
│ └── user.js
└── views
│ ├── layout.jade
│ ├── login.jade
│ └── main.jade
├── test
├── server
│ ├── beforeAll.js
│ ├── controllers
│ │ ├── auth.spec.js
│ │ ├── bean.spec.js
│ │ ├── comment.spec.js
│ │ ├── post.spec.js
│ │ └── user.spec.js
│ ├── mocha.opts
│ └── resources
│ │ └── mobious.png
├── spec
│ ├── components
│ │ ├── app.test.jsx
│ │ ├── header.test.jsx
│ │ ├── lang-picker.test.jsx
│ │ ├── profile.test.jsx
│ │ └── users.test.jsx
│ ├── pages
│ │ ├── not-found.test.jsx
│ │ └── server-error.test.jsx
│ ├── stores
│ │ └── users.test.js
│ └── utils
│ │ ├── alt-resolver.test.js
│ │ ├── image-resolver.test.js
│ │ ├── intl-loader.test.js
│ │ └── localized-routes.test.js
└── utils
│ ├── inject-lang.js
│ └── stub-router-context.jsx
├── tests.webpack.js
└── webpack
├── base.config.js
├── dev-server.js
├── dev.config.js
├── prod.config.js
└── utils
├── clean-dist.js
├── start-koa.js
└── write-stats.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0
3 | }
4 |
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | repo_token: 3eAArXSaxEd4JCV4030oyx4Ex7QhS5M3e
2 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | # Change these settings to your own preference
10 | indent_style = space
11 | indent_size = 2
12 |
13 | # We recommend you to keep these unchanged
14 | end_of_line = lf
15 | charset = utf-8
16 | trim_trailing_whitespace = true
17 | insert_final_newline = true
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | node_modules/
3 | dist/
4 | test/
5 | app/
6 | server/
7 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "eslint-config-airbnb",
4 | "plugins": ["react"],
5 | "env": {
6 | "browser": true,
7 | "node": true,
8 | "mocha": true
9 | },
10 | "globals": {
11 | "chai": true
12 | },
13 | "rules": {
14 | "react/jsx-uses-react": 1,
15 | "react/jsx-uses-vars": 1,
16 | "react/no-did-mount-set-state": 1,
17 | "react/no-did-update-set-state": 1,
18 | "react/no-multi-comp": 2,
19 | "react/prop-types": 2,
20 | "react/react-in-jsx-scope": 2,
21 | "react/self-closing-comp": 1,
22 | "react/wrap-multilines": 2,
23 |
24 | "quotes": [2, "single", "avoid-escape"],
25 | "comma-dangle": [2, "never"],
26 | "brace-style": [2, "stroustrup", {"allowSingleLine": true}],
27 | "no-underscore-dangle": 0,
28 | "space-in-brackets": 0,
29 | "func-names": 0,
30 | "no-else-return": 0,
31 | "no-param-reassign": 0,
32 | "no-reserved-keys": 0
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | text eol=lf
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/webpack-stats.json
2 | coverage
3 | node_modules
4 | npm-debug.log
5 | dist
6 | .tmp
7 | .DS_Store
8 | .sass-cache
9 | .env
10 | .c9
11 |
12 | db.development.sqlite
13 |
--------------------------------------------------------------------------------
/.scss-lint.yaml:
--------------------------------------------------------------------------------
1 | # Default application configuration that all configurations inherit from.
2 |
3 | scss_files: "**/*.scss"
4 |
5 | linters:
6 | BangFormat:
7 | enabled: true
8 | space_before_bang: true
9 | space_after_bang: false
10 |
11 | BorderZero:
12 | enabled: true
13 | convention: zero # or `none`
14 |
15 | ColorKeyword:
16 | enabled: true
17 |
18 | ColorVariable:
19 | enabled: true
20 |
21 | Comment:
22 | enabled: true
23 |
24 | DebugStatement:
25 | enabled: true
26 |
27 | DeclarationOrder:
28 | enabled: true
29 |
30 | DuplicateProperty:
31 | enabled: true
32 |
33 | ElsePlacement:
34 | enabled: true
35 | style: same_line # or 'new_line'
36 |
37 | EmptyLineBetweenBlocks:
38 | enabled: true
39 | ignore_single_line_blocks: true
40 |
41 | EmptyRule:
42 | enabled: true
43 |
44 | FinalNewline:
45 | enabled: true
46 | present: true
47 |
48 | HexLength:
49 | enabled: true
50 | style: short # or 'long'
51 |
52 | HexNotation:
53 | enabled: true
54 | style: uppercase # or 'lowercase'
55 |
56 | HexValidation:
57 | enabled: true
58 |
59 | IdSelector:
60 | enabled: false
61 |
62 | ImportantRule:
63 | enabled: true
64 |
65 | ImportPath:
66 | enabled: true
67 | leading_underscore: false
68 | filename_extension: false
69 |
70 | Indentation:
71 | enabled: true
72 | allow_non_nested_indentation: true
73 | character: space # or 'tab'
74 | width: 2
75 |
76 | LeadingZero:
77 | enabled: true
78 | style: exclude_zero # or 'include_zero'
79 |
80 | MergeableSelector:
81 | enabled: true
82 | force_nesting: true
83 |
84 | NameFormat:
85 | enabled: false
86 | allow_leading_underscore: true
87 | convention: BEM # or 'BEM', or a regex pattern
88 |
89 | NestingDepth:
90 | enabled: true
91 | max_depth: 3
92 |
93 | PlaceholderInExtend:
94 | enabled: true
95 |
96 | PropertyCount:
97 | enabled: false
98 | include_nested: false
99 | max_properties: 10
100 |
101 | PropertySortOrder:
102 | enabled: true
103 | order: ["position", "top", "right", "bottom", "left", "z-index", "display", "flex-direction", "flex-flow", "flex-wrap", "justify-content", "align-items", "align-content", "order", "flex", "flex-grow", "flex-shrink", "flex-basis", "align-self", "visibility", "overflow", "overflow-x", "overflow-y", "float", "clear", "table-layout", "border-collapse", "empty-cells", "box-sizing", "width", "min-width", "max-width", "height", "min-height", "max-height", "margin", "margin-top", "margin-right", "margin-bottom", "margin-left", "padding", "padding-top", "padding-right", "padding-bottom", "padding-left", "border", "border-width", "border-style", "border-color", "border-radius", "border-top", "border-top-width", "border-top-style", "border-top-color", "border-right", "border-right-width", "border-right-style", "border-right-color", "border-bottom", "border-bottom-width", "border-bottom-style", "border-bottom-color", "border-left", "border-left-width", "border-left-style", "border-left-color", "border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius", "white-space", "content", "color", "background", "background-color", "background-image", "background-repeat", "background-attachment", "background-position", "background-size", "opacity", "font", "font-weight", "font-style", "font-variant", "font-size", "font-family", "letter-spacing", "line-height", "list-style", "list-style-type", "list-style-position", "list-style-image", "outline", "outline-width", "outline-style", "outline-color", "text-align", "text-decoration", "text-indent", "text-transform", "text-shadow", "animation", "transform", "transition", "box-shadow"]
104 | ignore_unspecified: true
105 |
106 | PropertySpelling:
107 | enabled: true
108 | extra_properties: []
109 |
110 | QualifyingElement:
111 | enabled: false
112 | allow_element_with_attribute: false
113 | allow_element_with_class: false
114 | allow_element_with_id: false
115 |
116 | SelectorDepth:
117 | enabled: true
118 | max_depth: 3
119 |
120 | SelectorFormat:
121 | enabled: false
122 | convention: BEM # or 'BEM', or 'hyphenated_BEM', or 'snake_case', or 'camel_case', or a regex pattern
123 |
124 | Shorthand:
125 | enabled: true
126 |
127 | SingleLinePerProperty:
128 | enabled: true
129 | allow_single_line_rule_sets: true
130 |
131 | SingleLinePerSelector:
132 | enabled: true
133 |
134 | SpaceAfterComma:
135 | enabled: true
136 |
137 | SpaceAfterPropertyColon:
138 | enabled: true
139 | style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned'
140 |
141 | SpaceAfterPropertyName:
142 | enabled: true
143 |
144 | SpaceBeforeBrace:
145 | enabled: true
146 | style: space # or 'new_line'
147 | allow_single_line_padding: false
148 |
149 | SpaceBetweenParens:
150 | enabled: true
151 | spaces: 0
152 |
153 | StringQuotes:
154 | enabled: true
155 | style: single_quotes # or double_quotes
156 |
157 | TrailingSemicolon:
158 | enabled: true
159 |
160 | TrailingZero:
161 | enabled: false
162 |
163 | UnnecessaryMantissa:
164 | enabled: true
165 |
166 | UnnecessaryParentReference:
167 | enabled: true
168 |
169 | UrlFormat:
170 | enabled: true
171 |
172 | UrlQuotes:
173 | enabled: true
174 |
175 | VariableForProperty:
176 | enabled: false
177 | properties: []
178 |
179 | VendorPrefixes:
180 | enabled: false
181 | identifier_list: base
182 | additional_identifiers: []
183 | excluded_identifiers: []
184 |
185 | ZeroUnit:
186 | enabled: true
187 |
188 | Compass::*:
189 | enabled: false
190 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "iojs-v2.1.0"
4 | before_install:
5 | - "export DISPLAY=:99.0"
6 | - "sh -e /etc/init.d/xvfb start"
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | mobious framework
2 | =================
3 |
4 | base on [isomorphic-flux-boilerplate](https://github.com/iam4x/isomorphic-flux-boilerplate)
5 |
6 | - [sequelizejs - ORM](http://docs.sequelizejs.com/en/latest/)
7 |
8 | server side
9 | ===========
10 |
11 | bootstarp
12 | ---------
13 |
14 | create test data.
15 |
16 | define test data: server/bootstarp.js
17 |
18 | models
19 | ------
20 |
21 | define models: server/models/XXX.js
22 |
23 | controllers
24 | -----------
25 |
26 | define router: server/controllers.index.js
27 |
28 | run spec
29 | --------
30 |
31 | command: `npm run rest-test`
32 |
33 | Isomorphic app module useage
34 | ============================
35 |
36 | path
37 | ----
38 |
39 | - app/#{module_name}
40 |
41 | ex: app/userManager
42 |
43 | Isomorphic app share resource
44 | =============================
45 |
46 | app/pages
47 | ---------
48 |
49 | - 404: not find page
50 | - 500: server error page
51 |
52 | app/utils
53 | ---------
54 |
55 | use for general process
56 |
57 | > below information is Reference.
58 |
59 | ES6 Isomorphic Flux/ReactJS Boilerplate
60 | =======================================
61 |
62 | [](https://travis-ci.org/iam4x/isomorphic-flux-boilerplate)[](https://coveralls.io/r/iam4x/isomorphic-flux-boilerplate)[](https://david-dm.org/iam4x/isomorphic-flux-boilerplate)[](https://david-dm.org/iam4x/isomorphic-flux-boilerplate#info=devDependencies)[](https://www.npmjs.com/package/isomorphic-flux-boilerplate)
63 |
64 | ES6 Isomorphic Flux/ReactJS Boilerplate
65 | =======================================
66 |
67 | > A wonderfull boilerplate for **Flux/ReactJS** [isomorphic](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/) applications, running on **Koa**.
68 |
69 | **Demo:** http://isomorphic.iam4x.fr
70 |
71 | Libraries Included
72 | ------------------
73 |
74 | - [react](https://facebook.github.io/react/)
75 | - [react-router](https://github.com/rackt/react-router)
76 | - [react-hot-loader](https://github.com/gaearon/react-hot-loader)
77 | - [react-intl](https://github.com/yahoo/react-intl)
78 | - [alt](https://github.com/goatslacker/alt)
79 | - [iso](https://github.com/goatslacker/iso)
80 | - [koa](http://koajs.com/)
81 | - [webpack](http://webpack.github.io/)
82 | - [babeljs](https://babeljs.io/)
83 |
84 | TL;DR
85 | -----
86 |
87 | Use with `iojs^1.8.0` or `nodejs^0.12.0`, clone the repo, `npm install` and `npm run dev`.
88 |
89 | Learn React ([react-prime-draft](https://github.com/mikechau/react-primer-draft)), learn Flux and Alt ([alt guide](http://alt.js.org/guide/)).
90 |
91 | Wrap you async actions into promises, send them to `altResolver` with `altResolver.resolve(xxx)` for async server side rendering (see [app/actions/users.js:31](https://github.com/iam4x/isomorphic-flux-boilerplate/blob/master/app/actions/users.js#L31)).
92 |
93 | Build for production with `npm run build`, don't forget to run the tests before `npm test`.
94 |
95 | Concepts
96 | --------
97 |
98 | **Koa** will be our server for the server side rendering, we use **alt** for our Flux architecture and **react-router** for routing in our app.
99 |
100 | With **iso** as helper we can populate **alt** flux stores before the first rendering and have a complete async isomorphic React application.
101 |
102 | Run this boilerplate, you will see the server is fetching some fake users and will populate the `UserStore` with this data. **Koa** will render the first markup, serve the JavaScript and then it will entirely run on the client.
103 |
104 | Flux
105 | ----
106 |
107 | We use [alt](http://alt.js.org) instance as [Flux](http://facebook.github.io/react/blog/2014/05/06/flux.html) implementation.
108 |
109 | We need to use instances for isomorphic applications, to have a unique store/actions per requests on the server.
110 |
111 | On the client, Flux is initialized in `app/main.js` and sent to our first React Component via props (`this.props.flux`). Everytime you want to uses stores or actions in a component you need to give it access through props.
112 |
113 | On the server, it's similar but Flux is initialized in `server/router.jsx`. The instance is sent to `alt-resolver` for rendering components with the correct props.
114 |
115 | Learn more about [alt instances](http://alt.js.org/docs/altInstances) in the alt documentation.
116 |
117 | Internationalization (i18n)
118 | ---------------------------
119 |
120 | We use [react-intl](https://github.com/yahoo/react-intl) for internationalization, it uses browser implementation of [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl). For older browser and for node, we load the polyfill.
121 |
122 | - Support localized strings (see [data/en.js](https://github.com/iam4x/isomorphic-flux-boilerplate/blob/master/app%2Fdata%2Fen.js)\)
123 | - Support localized dates, times and currencies.
124 |
125 | Lang files and Intl polyfill are compiled into webpack chunks, for lazy-loading depending the locale of the user.
126 |
127 | If user changes locale, it is saved into a cookie `_lang` and used by the server to know the locale of rendering. If there's no `_lang` cookie, server will rely on `Accept-Language` request header. Server will set `` on rendering.
128 |
129 | Thank's to [gpbl/react-locale-hot-switch](https://github.com/gpbl/react-locale-hot-switch) for the implementation example!
130 |
131 | Async data-fetching
132 | -------------------
133 |
134 | Alt-resolver is the magic thing about the boilerplate, it will be our tool for resolving promises (data-fetching) before server side rendering.
135 |
136 | Wrap data-fetching requests from actions into promises and send them to `altResolver` like:
137 |
138 | ```
139 | fetch() {
140 | const promise = (resolve) => {
141 | request
142 | .get('http://example.com/api/users')
143 | .end((response) => {
144 | // fire new action to send data to store
145 | this.actions.fetchSuccess(response.body);
146 | return resolve();
147 | });
148 | };
149 | // Send the `promise` to altResolver
150 | this.alt.resolve(promise);
151 | }
152 | ```
153 |
154 | Call the fetch action from component in the `componentWillMount` method:
155 |
156 | ```
157 | static propTypes: {
158 | flux: React.PropTypes.object.isRequired
159 | }
160 |
161 | componentWillMount() {
162 | const usersActions = this.props.flux.getActions('users');
163 | return usersActions.fetch();
164 | }
165 | ```
166 |
167 | On browser side, the rendering won't be stopped and will resolve the promise instantly.
168 |
169 | On server side, `altResolver.render` will fire a first render to collect all the promises needed for a complete rendering. It will then resolve them, and try to re-render the application for a complete markup.
170 |
171 | Open `app/actions/users.js`, `app/utils/alt-resolver.js`, `app/stores/users.js` for more information about data-fetching.
172 |
173 | How to `require()` images on server side
174 | ----------------------------------------
175 |
176 | On client with webpack, you can directly `require()` images for your images DOM element like:
177 |
178 | ```
179 |
180 | ```
181 |
182 | Webpack will load them through the `url-loader` and if it's too big it will sent through `file-loader` for minification/compilation. The results is an image with a new filename for cache busting.
183 |
184 | But on node, `require()` an image will just throw an exception. There's an util for loading image on server side to achieve this:
185 |
186 | ```
187 | import imageResolver from 'utils/image-resolver'
188 |
189 | let image;
190 | // On browser just require() the image as usual
191 | if (process.env.BROWSER) {
192 | image = require('images/logo.png');
193 | }
194 | else {
195 | image = imageResolver('images/logo.png');
196 | }
197 |
198 | ...
199 | render () {
200 | return (
201 |
202 | );
203 | }
204 | ...
205 | ```
206 |
207 | The utils/image-resolver with match the original image name with the compiled one.
208 |
209 | Voilà! You can `require()` images on server side too.
210 |
211 | Installation / How-to
212 | ---------------------
213 |
214 | I recommend to use [io.js](https://iojs.org/) to take advantages of `ES6` without `--harmony` flag on `NodeJS`.
215 |
216 | It's super easy to do with [nvm](https://github.com/creationix/nvm):
217 |
218 | - `$ nvm install iojs`
219 | - `$ nvm use iojs`
220 | - `$ nvm alias default iojs` (to make `node` default to `iojs`\)
221 |
222 | But it works well with `nodejs^0.12.0` as well :)
223 |
224 | After that, you will just need to clone the repo and install dependancies:
225 |
226 | - `$ git clone -o upstream https://github.com/iam4x/isomorphic-flux-boilerplate.git app`
227 | - `$ cd app && npm install`
228 |
229 | (Don't forget to add your remote origin: `$ git remote origin git@github.com:xxx/xxx.git`\)
230 |
231 | ### Run the project in development:
232 |
233 | - `$ npm run dev`
234 |
235 | Open your browser to `http://localhost:3002` and you will see the magic happens! Try to disable JavaScript in your browser, you will still be able to navigate between pages of the application. Enjoy the power of isomorphic applications!
236 |
237 | (Note: ports 3000-3002 are needed, you can change this with `$ PORT=3050 npm run dev` it will run on 3050-3052)
238 |
239 | ### Run tests
240 |
241 | - `$ npm test` will run the tests once
242 | - `$ ./node_modules/.bin/karma start` will watch for changes and run the tests on change
243 |
244 | ### Build project:
245 |
246 | Just run `$ npm run build`, it will produce these tasks:
247 |
248 | - Run tests from `test/spec/**/*.jsx`
249 | - Concat & minify styles to `/dist/app-[hash].css`
250 | - Concat & minify scripts to `/dist/js/app-[hash].js`
251 |
252 | ### Update the boilerplate
253 |
254 | You can fetch the upstream branch and merge it into your master:
255 |
256 | - `$ git checkout master`
257 | - `$ git fetch upstream`
258 | - `$ git merge upstream/master`
259 | - `$ npm install`
260 |
261 | ### Run in production
262 |
263 | Build the project first:
264 |
265 | - `$ npm run build`
266 |
267 | Then start the koa server:
268 |
269 | - `$ NODE_ENV=production node server/index.js` (iojs)
270 | - `$ NODE_ENV=production node --harmony server/index.js` (nodejs 0.12.x)
271 |
272 | You can also use `processes.json` to run the application with [PM2 Monitor](https://github.com/Unitech/pm2) on your production server (customize it for your use):
273 |
274 | - `$ pm2 start processes.json`
275 |
276 | ### Learn more
277 |
278 | - [Official ReactJS website](http://facebook.github.io/react/)
279 | - [Official ReactJS wiki](https://github.com/facebook/react/wiki)
280 | - [Official Flux website](http://facebook.github.io/flux/)
281 | - [ReactJS Conf 2015 links](https://gist.github.com/yannickcr/148110d3ca658ad96c2b)
282 | - [Learn ES6](https://babeljs.io/docs/learn-es6/)
283 | - [ES6 Features](https://github.com/lukehoban/es6features#readme)
284 |
285 | ### Common errors
286 |
287 | - SASS compilation hang when importing same file more than once (see [#62](https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62)\)
288 |
--------------------------------------------------------------------------------
/app/actions/auth.js:
--------------------------------------------------------------------------------
1 | import {baseUrl} from '../../server/config/init';
2 | import request from 'superagent';
3 |
4 | class AuthActions {
5 | constructor() {
6 | this.generateActions('localLoginSuccess', 'fetchStatusSuccess', 'localLoginFail');
7 | }
8 |
9 | localLogin(params) {
10 | const promise = (resolve) => {
11 | this.alt.getActions('requests').start();
12 | request.post(`${baseUrl}auth/login`)
13 | .send(params)
14 | .end((error, res) => {
15 | if (error) {
16 | if (error.status === 401) {
17 | this.actions.localLoginFail(res.body);
18 | this.alt.getActions('requests').success();
19 | return resolve();
20 | }
21 | return resolve(error);
22 | }
23 | this.actions.localLoginSuccess(res.body);
24 | this.alt.getActions('requests').success();
25 | return resolve();
26 | });
27 | };
28 | this.alt.resolve(promise);
29 | }
30 |
31 | fetchStatus() {
32 | const promise = (resolve) => {
33 | this.alt.getActions('requests').start();
34 | request.get(`${baseUrl}auth/status`)
35 | .end((error, res) => {
36 | if (error) return resolve(error);
37 | this.actions.fetchStatusSuccess(res.body);
38 | this.alt.getActions('requests').success();
39 | return resolve(res.body);
40 | });
41 | };
42 |
43 | this.alt.resolve(promise);
44 | }
45 | }
46 |
47 | export default AuthActions;
48 |
--------------------------------------------------------------------------------
/app/actions/locale.js:
--------------------------------------------------------------------------------
1 | import intlLoader from 'utils/intl-loader';
2 |
3 | class LocaleActions {
4 | constructor() {
5 | this.generateActions('switchLocaleSuccess');
6 | }
7 |
8 | async switchLocale(locale) {
9 | if (locale) {
10 | const {messages} = await intlLoader(locale);
11 | return this.actions.switchLocaleSuccess({locale, messages});
12 | }
13 | }
14 | }
15 |
16 | export default LocaleActions;
17 |
--------------------------------------------------------------------------------
/app/actions/page-title.js:
--------------------------------------------------------------------------------
1 | class PageTitleActions {
2 | constructor() {
3 | this.generateActions('set');
4 | }
5 | }
6 |
7 | export default PageTitleActions;
8 |
--------------------------------------------------------------------------------
/app/actions/requests.js:
--------------------------------------------------------------------------------
1 | class RequestsActions {
2 | constructor() {
3 | this.generateActions('start', 'success', 'fail');
4 | }
5 | }
6 |
7 | export default RequestsActions;
8 |
--------------------------------------------------------------------------------
/app/beanManager/actions/bean.js:
--------------------------------------------------------------------------------
1 | import {baseUrl} from '../../../server/config/init';
2 | import request from 'superagent';
3 |
4 | class BeanActions {
5 | constructor() {
6 | this.generateActions(
7 | 'fetchSuccess'
8 | );
9 | }
10 |
11 | fetch() {
12 | const promise: Function = (resolve) => {
13 | let that = this;
14 | that.alt.getActions('requests').start();
15 |
16 | request.get(baseUrl + 'rest/bean')
17 | .end((error, res) => {
18 | if (error) return resolve(error);
19 | that.actions.fetchSuccess(res.body.beans);
20 | that.alt.getActions('requests').success();
21 | return resolve();
22 | });
23 | };
24 | this.alt.resolve(promise);
25 | }
26 | }
27 |
28 | export default BeanActions;
29 |
--------------------------------------------------------------------------------
/app/beanManager/components/list.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import ListenerMixin from 'alt/mixins/ListenerMixin';
4 | import {IntlMixin} from 'react-intl';
5 |
6 | if (process.env.BROWSER) {
7 | require('beanManager/styles/bean.scss');
8 | }
9 |
10 | export default React.createClass({
11 | mixins: [ListenerMixin, IntlMixin],
12 | contextTypes: {
13 | router: React.PropTypes.func
14 | },
15 | propTypes: {
16 | flux: React.PropTypes.object.isRequired
17 | },
18 | getInitialState() {
19 | return this.props.flux.getStore('bean').getState();
20 | },
21 | componentWillMount() {
22 | return this.props.flux.getActions('bean').fetch();
23 | },
24 | componentDidMount() {
25 | this.listenTo(this.props.flux.getStore('bean'), this.handleStoreChange);
26 | },
27 | handleStoreChange() {
28 | this.setState(this.getInitialState());
29 | },
30 |
31 | renderBeans() {
32 | return this.state.beans.map((bean, index) => {
33 | return (
34 |
35 | {bean.name} |
36 |
37 | );
38 | });
39 | },
40 | render() {
41 | return (
42 |
43 |
{this.getIntlMessage('beanManager.title')}
44 |
45 |
46 |
47 | {this.getIntlMessage('beanManager.name')} |
48 |
49 |
50 |
51 | {this.renderBeans()}
52 |
53 |
54 |
55 | );
56 | }
57 | });
58 |
--------------------------------------------------------------------------------
/app/beanManager/stores/bean.js:
--------------------------------------------------------------------------------
1 | class BeanStore {
2 |
3 | constructor() {
4 | this.bindActions(this.alt.getActions('bean'));
5 | this.beans = [];
6 | }
7 |
8 | onFetchSuccess(beans) {
9 | return this.setState({beans});
10 | }
11 | }
12 |
13 | export default BeanStore;
14 |
--------------------------------------------------------------------------------
/app/beanManager/styles/bean.scss:
--------------------------------------------------------------------------------
1 | $black: #000;
2 |
3 | .app--users {
4 | margin: 0 auto;
5 |
6 | td,
7 | th {
8 | padding: 5px 10px;
9 | }
10 |
11 | thead {
12 | border-bottom: 1px solid $black;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/commentManager/actions/comment.js:
--------------------------------------------------------------------------------
1 | import {baseUrl} from '../../../server/config/init';
2 | import request from 'superagent';
3 |
4 | class CommentActions {
5 | constructor() {
6 | this.generateActions(
7 | 'fetchSuccess', 'createSuccess', 'fetchOneSuccess'
8 | );
9 | }
10 | create(params) {
11 | const promise: Function = (resolve) => {
12 | // fake xhr
13 | this.alt.getActions('requests').start();
14 |
15 | request.post(baseUrl + 'rest/comment/')
16 | .send(params)
17 | .end((error, res) => {
18 | if (error) return resolve(error);
19 |
20 | let createdComment = res.body.comment;
21 | this.actions.createSuccess(createdComment);
22 | this.alt.getActions('requests').success();
23 | return resolve();
24 | }, 300);
25 | };
26 | this.alt.resolve(promise);
27 | }
28 |
29 | fetch() {
30 | const promise: Function = (resolve) => {
31 | let that = this;
32 | that.alt.getActions('requests').start();
33 |
34 | request.get(baseUrl + 'rest/comment/')
35 | // .set('Accept', 'application/json')
36 | .end((error, res) => {
37 | if (error) return resolve(error);
38 | that.actions.fetchSuccess(res.body.comments);
39 | that.alt.getActions('requests').success();
40 | return resolve();
41 | });
42 | };
43 | this.alt.resolve(promise);
44 | }
45 |
46 | fetchOne(id: string) {
47 | const promise = (resolve) => {
48 | this.alt.getActions('requests').start();
49 | request.get(baseUrl + 'rest/comment/' + `${id}`)
50 | .end((error, res) => {
51 | if (error) return resolve(error);
52 | const comment: Object = res.body.comment;
53 | this.actions.fetchOneSuccess(comment);
54 | this.alt.getActions('requests').success();
55 | return resolve();
56 | });
57 | };
58 |
59 | this.alt.resolve(promise);
60 | }
61 |
62 |
63 | }
64 |
65 | export default CommentActions;
66 |
--------------------------------------------------------------------------------
/app/commentManager/components/list.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react';
2 | import {IntlMixin} from 'react-intl';
3 | import {Button, Panel, Col, Input} from 'react-bootstrap';
4 |
5 | if (process.env.BROWSER) {
6 | require('commentManager/styles/comment.scss');
7 | }
8 |
9 | class CommentList extends Component {
10 |
11 | static propTypes = {
12 | flux: PropTypes.object.isRequired
13 | }
14 |
15 | _getIntlMessage = IntlMixin.getIntlMessage
16 |
17 | state = {
18 | comments: this.props.flux
19 | .getStore('comment')
20 | .getState().comments
21 | };
22 |
23 | componentWillMount() {
24 | return this.props.flux.getActions('comment').fetch();
25 | }
26 |
27 | componentDidMount() {
28 | this.props.flux
29 | .getStore('comment')
30 | .listen(this._handleStoreChange);
31 | }
32 |
33 | componentWillUnmount() {
34 | this.props.flux
35 | .getStore('comment')
36 | .unlisten(this._handleStoreChange);
37 | }
38 |
39 | _handleStoreChange = (state) => {
40 | return this.setState(state);
41 | }
42 |
43 | _handleSubmit(event) {
44 | event.preventDefault();
45 |
46 | let newComment = {
47 | author: React.findDOMNode(this.refs.c_author.refs.input).value.trim(),
48 | content: React.findDOMNode(this.refs.c_content.refs.input).value.trim()
49 | };
50 |
51 | this.props.flux.getActions('comment').create(newComment);
52 | React.findDOMNode(this.refs.c_author.refs.input).value = '';
53 | React.findDOMNode(this.refs.c_content.refs.input).value = '';
54 | }
55 |
56 | renderComments() {
57 | return this.state.comments.map((comment, index) => {
58 | return (
59 |
60 |
61 | {comment.content}
62 |
63 |
64 | );
65 | });
66 | }
67 |
68 | renderCommentInput() {
69 | return (
70 |
71 |
72 |
73 |
78 |
79 |
80 |
81 | );
82 | }
83 |
84 | render() {
85 | return (
86 |
87 |
88 |
{this._getIntlMessage('commentManager.title')}
89 |
90 | {this.renderComments()}
91 |
92 |
93 |
94 | {this.renderCommentInput()}
95 |
96 |
97 | );
98 | }
99 | }
100 |
101 | export default CommentList;
102 |
--------------------------------------------------------------------------------
/app/commentManager/stores/comment.js:
--------------------------------------------------------------------------------
1 | class CommentStore {
2 |
3 | constructor() {
4 | this.bindActions(this.alt.getActions('comment'));
5 | this.comments = [];
6 | }
7 |
8 | onFetchSuccess(comments) {
9 | return this.setState({comments});
10 | }
11 |
12 | onCreateSuccess(comment) {
13 | const comments: Array