├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmrc
├── .scss-lint.yml
├── .travis.yml
├── LICENSE
├── README.md
├── config
├── dev.js
└── prod.js
├── index.js
├── nodemon.json
├── package.json
├── src
├── actions
│ ├── IntlActionCreators.js
│ └── PhotoActionCreators.js
├── app.js
├── client.js
├── components
│ ├── Footer.js
│ ├── LocaleSwitcher.js
│ ├── Logo.js
│ ├── NavBar.js
│ ├── Page.js
│ ├── Photo.js
│ ├── PhotoAttribution.js
│ ├── PhotoCreatedAt.js
│ ├── PhotoMeta.js
│ ├── PhotoRating.js
│ └── Thumbnail.js
├── config.js
├── constants
│ ├── Actions.js
│ └── features.js
├── containers
│ ├── ErrorPage.js
│ ├── FeaturedPage.js
│ ├── HomePage.js
│ ├── Html.js
│ ├── InitActions.js
│ ├── LoadingPage.js
│ ├── NotFoundPage.js
│ ├── PhotoPage.js
│ └── Root.js
├── intl
│ ├── en.js
│ ├── fr.js
│ ├── it.js
│ └── pt.js
├── routes.js
├── server.js
├── server
│ ├── ga.js
│ ├── handleServerRendering.js
│ ├── intl-polyfill.js
│ └── setLocale.js
├── services
│ ├── photo.js
│ └── photos.js
├── stores
│ ├── FeaturedStore.js
│ ├── HtmlHeadStore.js
│ ├── IntlStore.js
│ └── PhotoStore.js
├── style
│ ├── Animate.scss
│ ├── Footer.scss
│ ├── Loader.scss
│ ├── LocaleSwitcher.scss
│ ├── NavBar.scss
│ ├── Page.scss
│ ├── PhotoMeta.scss
│ ├── Root.scss
│ ├── Thumbnail.scss
│ ├── ThumbnailCollection.scss
│ ├── constants
│ │ ├── animation.scss
│ │ ├── breaks.scss
│ │ ├── colors.scss
│ │ └── typography.scss
│ ├── mixins
│ │ └── break.scss
│ └── utils
│ │ └── normalize.scss
└── utils
│ ├── APIUtils.js
│ ├── CookieUtils.js
│ ├── IntlComponents.js
│ ├── IntlUtils.js
│ ├── connectToIntlStore.js
│ ├── getIntlMessage.js
│ └── trackPageView.js
├── static
└── assets
│ ├── favicon.png
│ └── grid-loader.svg
└── webpack
├── .eslintrc
├── dev.config.js
├── prod.config.js
└── server.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | max_line_length = 80
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | max_line_length = 0
14 | trim_trailing_whitespace = false
15 |
16 | [COMMIT_EDITMSG]
17 | max_line_length = 0
18 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "plugins": ["react"],
4 | "extends": "eslint:recommended",
5 | "rules": {
6 | "arrow-spacing": 2,
7 | "curly": 2,
8 | "eol-last": 2,
9 | "indent": [2, 2],
10 | "no-lonely-if": 2,
11 | "no-spaced-func": 2,
12 | "no-unused-vars": 2,
13 | "no-var": 2,
14 | "prefer-const": 2,
15 | "quotes": [2, "double"],
16 | "jsx-quotes": [1, "prefer-double"],
17 | "space-before-blocks": 2,
18 |
19 | "react/jsx-no-duplicate-props": 2,
20 | "react/jsx-no-undef": 2,
21 | "react/jsx-uses-react": 2,
22 | "react/no-danger": 2,
23 | "react/no-unknown-property": 2,
24 | "react/self-closing-comp": 2,
25 | "react/sort-comp": 2
26 |
27 | },
28 |
29 | "env": {
30 | "node": true,
31 | "browser": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | *.log
3 |
4 | # Dependency directory
5 | node_modules
6 |
7 | # Dist bundle generated by webpack
8 | static/dist
9 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | save-prefix = ''
2 |
--------------------------------------------------------------------------------
/.scss-lint.yml:
--------------------------------------------------------------------------------
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 | - "0.12"
4 | before_script:
5 | - "npm run lint"
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Giampaolo Bellavite
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # isomorphic500
2 |
3 | [Isomorphic500](https://isomorphic500.herokuapp.com) is a small isomorphic ([universal](https://medium.com/@mjackson/universal-javascript-4761051b7ae9)) web application featuring photos from [500px](http://500px.com).
4 |
5 | It is built on [express](http://expressjs.com) using [React](https://facebook.github.io/react) and [Flux](https://facebook.github.io/flux) with [yahoo/fluxible](http://fluxible.io). It is developed with [webpack](http://webpack.github.io) and [react-hot-loader](http://gaearon.github.io/react-hot-loader/) and written with [babeljs](http://babeljs.io) with the help of [eslint](http://eslint.org). It supports multiple languages using [react-intl](http://formatjs.io/react/).
6 |
7 |
8 |
9 | [](https://travis-ci.org/gpbl/isomorphic500)
10 |
11 | The intent of this project is to solidify my experience with these technologies and perhaps to inspire other developers in their journey with React and Flux. It works also as example of a javascript development environment with all the cool recent stuff :-)
12 |
13 | - see the demo on [isomorphic500.herokuapp.com](https://isomorphic500.herokuapp.com) (with source maps!)
14 | - clone this repo and run the server to confirm it is actually working
15 | - edit a react component or a css style, and see the updated app as you save your changes!
16 | - read on for some technical details
17 |
18 | **Get help**
19 | Join the [gitter chat](https://gitter.im/gpbl/isomorphic500?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) or the [#isomorphic500](https://reactiflux.slack.com/archives/isomorphic500) on [reactiflux](http://www.reactiflux.com) :-)
20 |
21 | **Clone this repo**
22 |
23 | **Note** This app has been tested on node 4
24 |
25 | ```
26 | git clone https://github.com/gpbl/isomorphic500.git
27 | cd isomorphic500
28 | npm install
29 | ```
30 |
31 | **Start the app**
32 |
33 | ```bash
34 | npm run dev
35 | ```
36 |
37 | and open [localhost:3000](http://localhost:3000).
38 |
39 | You can also try the built app:
40 |
41 | ```bash
42 | npm run build # First, build for production
43 | npm run prod # then, run the production version
44 | ```
45 |
46 | then open [localhost:8080](http://localhost:8080).
47 |
48 | > If you are starting the server on Windows, please read https://github.com/gpbl/isomorphic500/issues/58
49 |
50 |
51 | ## Table of Contents
52 |
53 | * [Application structure](#application-structure)
54 | * [The fluxible app](#the-fluxible-app)
55 | * [Async data](#async-data)
56 | * [Router](#router)
57 | * [Stores](#stores)
58 | * [Resource stores](#resource-stores)
59 | * [List stores](#list-stores)
60 | * [The HtmlHeadStore](#the-htmlheadstore)
61 | * [Internationalization (i18n)](#internationalization-i18n)
62 | * [How the user’s locale is detected](#how-the-user’s-locale-is-detected)
63 | * [Setting up react-intl](#setting-up-react-intl)
64 | * [Internationalization, the flux way](#internationalization-the-flux-way)
65 | * [Sending the locale to the API](#sending-the-locale-to-the-api)
66 | * [Development](#development)
67 | * [nodemon](#nodemon)
68 | * [Webpack](#webpack)
69 | * [Babeljs](#babeljs)
70 | * [.editorconfig](#editorconfig)
71 | * [Linting](#linting)
72 | * [Debugging](#debugging)
73 |
74 | ## Application structure
75 |
76 | ```bash
77 | .
78 | ├── index.js # Starts the express server and the webpack dev server
79 | ├── config # Contains the configuration for dev and prod environments
80 | ├── nodemon.json # Configure nodemon to watch some files
81 | ├── src
82 | │ ├── app.js # The fluxible app
83 | │ ├── client.js # Entry point for the client
84 | │ ├── config.js # Config loader (load the config files from /config)
85 | │ ├── routes.js # Routes used by fluxible-router
86 | │ ├── server.js # Start the express server and render the routes server-side
87 | │ │
88 | │ ├── actions # Fluxible actions
89 | │ ├── components # React components
90 | │ ├── constants # Constants
91 | │ ├── containers # Contains React containers components
92 | │ │ ├── ...
93 | │ │ ├── Html.js # Used to render the document server-side
94 | │ │ └── Root.js # Root component
95 |
96 | │ ├── intl # Contains the messages for i18n
97 | │ ├── server # Server-side only code
98 | │ │ ├── ga.js # Google Analytics script
99 | │ │ ├── intl-polyfill.js # Patch node to support `Intl` and locale-data
100 | │ │ ├── render.js # Middleware to render server-side the fluxible app
101 | │ │ └── setLocale.js # Middleware to detect and set the request's locale
102 | │ ├── services # Fetchr services
103 | │ ├── stores # Fluxible stores
104 | │ ├── style # Contains the Sass files
105 | │ └── utils
106 | │ ├── APIUtils.js # Wrapper to superagent for communicating with 500px API
107 | │ ├── CookieUtils.js # Utility to write/read cookies
108 | │ ├── IntlComponents.js # Exports wrapped react-intl components
109 | │ ├── IntlUtils.js # Utilities to load `Intl` and locale-data
110 | │ ├── connectToIntlStore.js # Connects react-intl components with the IntlStore
111 | │ ├── getIntlMessage.js # Get react-intl messages
112 | │ └── trackPageView.js # Track a page view with google analitics
113 | ├── static
114 | │ ├── assets # Static files
115 | │ └── dist # Output files for webpack on production
116 | └── webpack
117 | ├── dev.config.js # Webpack config for development
118 | ├── prod.config.js # Webpack config for building the production files
119 | └── server.js # Used to starts the webpack dev server
120 |
121 | ```
122 |
123 | ### The fluxible app
124 |
125 | The [src/app](src/app) file is the core of the Fluxible application:
126 |
127 | - it configures Fluxible with [src/containers/Root.js](src/containers/Root.js) as the root component.
128 | - it registers the stores so they can work on the same React context
129 | - it adds the [fetchr plugin]((https://github.com/yahoo/fluxible-plugin-fetchr)), to share the same API requests both client and server-side
130 | - it makes possible to dehydrate the stores [on the server](src/server/render.js) and rehydrate them [on the client](src/client.js)
131 |
132 | ### Async data
133 |
134 | I used [Fetchr](https://github.com/yahoo/fetchr) and [fluxible-plugin-fetchr](https://github.com/yahoo/fluxible-plugin-fetchr).
135 | [Fetchr services](src/services) run only on server and send [superagent](http://visionmedia.github.com/superagent) requests to 500px.
136 |
137 | ### Router
138 |
139 | This app uses [fluxible-router](https://github.com/yahoo/fluxible-router) for routing. Fluxible-router works pretty well in fluxible applications since it follows the flux paradigm. The [Application component](src/containers/Root.js) uses the `@handleHistory` decorator to bind the router to the app.
140 |
141 | ### Stores
142 |
143 | Instead of directly listening to stores, components use fluxible's `@connectToStores` decorator: a store state is passed to components as prop. See for example the [PhotoPage](src/containers/PhotoPage.js) or the [FeaturedPage](src/containers/FeaturedPage.js).
144 |
145 | `connectToStore` can also "consume" store data without actually listening to any store. This is the case of [NavBar](src/components/NavBar.js) or [LocaleSwitcher](src/components/LocaleSwitcher.js).
146 |
147 | #### Resource stores
148 |
149 | While REST APIs usually return collections as arrays, a resource store keeps items as big object – like the [PhotoStore](src/stores/PhotoStore.js). This simplifies the progressive resource updates that may happen during the app’s life.
150 |
151 | #### List stores
152 |
153 | A list store keeps references to a resource store, as the [FeaturedStore](src/stores/FeaturedStore.js) holds the ids of the photos in [PhotoStore](src/stores/PhotoStore.js).
154 |
155 | #### The HtmlHeadStore
156 |
157 | The [HtmlHeadStore](src/stores/HtmlHeadStore.js) is a special store used to set the `