├── .gitignore ├── README.md ├── bower.json ├── fonts ├── Lato-Light.eot ├── Lato-Light.html ├── Lato-Light.ttf ├── Lato-Light.woff ├── Lato-Regular.eot ├── Lato-Regular.html ├── Lato-Regular.ttf ├── Lato-Regular.woff ├── Lato-Thin.eot ├── Lato-Thin.html ├── Lato-Thin.ttf └── Lato-Thin.woff ├── gulpfile.js ├── index.html ├── package.json ├── scripts ├── actions │ ├── RouteActionCreators.react.jsx │ ├── ServerActionCreators.react.jsx │ ├── SessionActionCreators.react.jsx │ └── StoryActionCreators.react.jsx ├── app.jsx ├── components │ ├── Header.react.jsx │ ├── SmallApp.react.jsx │ ├── common │ │ └── ErrorNotice.react.jsx │ ├── session │ │ ├── LoginPage.react.jsx │ │ └── SignupPage.react.jsx │ └── stories │ │ ├── StoriesPage.react.jsx │ │ ├── StoryNew.react.jsx │ │ └── StoryPage.react.jsx ├── constants │ └── SmallConstants.js ├── dispatcher │ └── SmallAppDispatcher.js ├── routes.jsx ├── stores │ ├── RouteStore.react.jsx │ ├── SessionStore.react.jsx │ └── StoryStore.react.jsx └── utils │ └── WebAPIUtils.js └── styles ├── fonts.scss ├── globals.scss ├── main.scss └── spinner.scss /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .sass-cache 3 | dist 4 | bower_components 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Small 2 | This is a sample project used to illustrate the Flux architecture with React. Checkout the full article [here](http://fancypixel.github.io/blog/2015/01/28/react-plus-flux-backed-by-rails-api/) 3 | 4 | ## Setup 5 | ``` 6 | npm install 7 | bower install 8 | gulp watch 9 | ``` 10 | 11 | # MIT License 12 | 13 | Copyright (c) 2015 Fancy Pixel. All rights reserved. 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a 16 | copy of this software and associated documentation files (the "Software"), 17 | to deal in the Software without restriction, including 18 | without limitation the rights to use, copy, modify, merge, publish, 19 | distribute, sublicense, and/or sell copies of the Software, and to 20 | permit persons to whom the Software is furnished to do so, subject to 21 | the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included 24 | in all copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 27 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 29 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 30 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 31 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 32 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | 34 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "carrot-frontend", 3 | "version": "0.0.2", 4 | "dependencies": { 5 | "normalize.css": "~3.0.2", 6 | "foundation": "~5.5.0", 7 | "modernizr": "^2.8.3" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /fonts/Lato-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FancyPixel/small-frontend/8369b6088893b32d24bcef65c7ad5a8f69d2eda7/fonts/Lato-Light.eot -------------------------------------------------------------------------------- /fonts/Lato-Light.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Light - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /fonts/Lato-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FancyPixel/small-frontend/8369b6088893b32d24bcef65c7ad5a8f69d2eda7/fonts/Lato-Light.ttf -------------------------------------------------------------------------------- /fonts/Lato-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FancyPixel/small-frontend/8369b6088893b32d24bcef65c7ad5a8f69d2eda7/fonts/Lato-Light.woff -------------------------------------------------------------------------------- /fonts/Lato-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FancyPixel/small-frontend/8369b6088893b32d24bcef65c7ad5a8f69d2eda7/fonts/Lato-Regular.eot -------------------------------------------------------------------------------- /fonts/Lato-Regular.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Regular - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FancyPixel/small-frontend/8369b6088893b32d24bcef65c7ad5a8f69d2eda7/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /fonts/Lato-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FancyPixel/small-frontend/8369b6088893b32d24bcef65c7ad5a8f69d2eda7/fonts/Lato-Regular.woff -------------------------------------------------------------------------------- /fonts/Lato-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FancyPixel/small-frontend/8369b6088893b32d24bcef65c7ad5a8f69d2eda7/fonts/Lato-Thin.eot -------------------------------------------------------------------------------- /fonts/Lato-Thin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Thin - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /fonts/Lato-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FancyPixel/small-frontend/8369b6088893b32d24bcef65c7ad5a8f69d2eda7/fonts/Lato-Thin.ttf -------------------------------------------------------------------------------- /fonts/Lato-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FancyPixel/small-frontend/8369b6088893b32d24bcef65c7ad5a8f69d2eda7/fonts/Lato-Thin.woff -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'), 4 | gulpFilter = require('gulp-filter'), 5 | flatten = require('gulp-flatten'), 6 | mainBowerFiles = require('main-bower-files'), 7 | rename = require("gulp-rename"), 8 | minifycss = require('gulp-minify-css'), 9 | changed = require('gulp-changed'), 10 | sass = require('gulp-sass'), 11 | csso = require('gulp-csso'), 12 | autoprefixer = require('gulp-autoprefixer'), 13 | browserify = require('browserify'), 14 | watchify = require('watchify'), 15 | source = require('vinyl-source-stream'), 16 | buffer = require('vinyl-buffer'), 17 | reactify = require('reactify'), 18 | uglify = require('gulp-uglify'), 19 | del = require('del'), 20 | notify = require('gulp-notify'), 21 | browserSync = require('browser-sync'), 22 | reload = browserSync.reload, 23 | p = { 24 | jsx: './scripts/app.jsx', 25 | scss: 'styles/main.scss', 26 | scssSource: 'styles/*', 27 | font: 'fonts/*', 28 | bundle: 'app.js', 29 | distJs: 'dist/js', 30 | distCss: 'dist/css', 31 | distFont: 'dist/fonts' 32 | }; 33 | 34 | gulp.task('clean', function(cb) { 35 | return del(['dist'], cb); 36 | }); 37 | 38 | gulp.task('browserSync', function() { 39 | browserSync({ 40 | notify: false, 41 | server: { 42 | baseDir: './' 43 | } 44 | }) 45 | }); 46 | 47 | gulp.task('watchify', function() { 48 | var bundler = watchify(browserify(p.jsx, watchify.args)); 49 | 50 | function rebundle() { 51 | return bundler 52 | .bundle() 53 | .on('error', notify.onError()) 54 | .pipe(source(p.bundle)) 55 | .pipe(gulp.dest(p.distJs)) 56 | .pipe(reload({stream: true})); 57 | } 58 | 59 | bundler.transform(reactify) 60 | .on('update', rebundle); 61 | return rebundle(); 62 | }); 63 | 64 | gulp.task('browserify', function() { 65 | browserify(p.jsx) 66 | .transform(reactify) 67 | .bundle() 68 | .pipe(source(p.bundle)) 69 | .pipe(buffer()) 70 | .pipe(uglify()) 71 | .pipe(gulp.dest(p.distJs)); 72 | }); 73 | 74 | gulp.task('fonts', function() { 75 | return gulp.src(p.font) 76 | .pipe(gulp.dest(p.distFont)); 77 | }); 78 | 79 | gulp.task('styles', function() { 80 | return gulp.src(p.scss) 81 | .pipe(changed(p.distCss)) 82 | .pipe(sass({errLogToConsole: true})) 83 | .on('error', notify.onError()) 84 | .pipe(autoprefixer({ 85 | browsers: ['last 1 version'] 86 | })) 87 | .pipe(csso()) 88 | .pipe(gulp.dest(p.distCss)) 89 | .pipe(reload({stream: true})); 90 | }); 91 | 92 | // Ugly hack to bring resources in 93 | gulp.task('modernizr', function() { 94 | return gulp.src('bower_components/modernizr/modernizr.js') 95 | .pipe(gulp.dest(p.distJs)); 96 | }); 97 | gulp.task('foundation-js', function() { 98 | return gulp.src('bower_components/foundation/js/foundation.min.js') 99 | .pipe(gulp.dest(p.distJs)); 100 | }); 101 | gulp.task('foundation-css', function() { 102 | return gulp.src('bower_components/foundation/css/foundation.min.css') 103 | .pipe(gulp.dest(p.distCss)); 104 | }); 105 | 106 | gulp.task('bower-libs', function() { 107 | var jsFilter = gulpFilter('*.js', {restore: true}); 108 | var cssFilter = gulpFilter('*.css', {restore: true}); 109 | var fontFilter = gulpFilter(['*.eot', '*.woff', '*.svg', '*.ttf']); 110 | 111 | return gulp.src(mainBowerFiles()) 112 | 113 | // JS from bower_components 114 | .pipe(jsFilter) 115 | .pipe(gulp.dest(p.distJs)) 116 | .pipe(uglify()) 117 | .pipe(rename({ 118 | suffix: ".min" 119 | })) 120 | .pipe(gulp.dest(p.distJs)) 121 | .pipe(jsFilter.restore) 122 | 123 | // css from bower_components, minified 124 | .pipe(cssFilter) 125 | .pipe(gulp.dest(p.distCss)) 126 | .pipe(minifycss()) 127 | .pipe(rename({ 128 | suffix: ".min" 129 | })) 130 | .pipe(gulp.dest(p.distCss)) 131 | .pipe(cssFilter.restore) 132 | 133 | // font files from bower_components 134 | .pipe(fontFilter) 135 | .pipe(flatten()) 136 | .pipe(gulp.dest(p.distFont)); 137 | }); 138 | 139 | gulp.task('libs', function() { 140 | gulp.start(['modernizr', 'foundation-css', 'foundation-js', 'bower-libs', 'fonts']); 141 | }); 142 | 143 | gulp.task('watchTask', function() { 144 | gulp.watch(p.scssSource, ['styles']); 145 | }); 146 | 147 | gulp.task('watch', ['clean'], function() { 148 | gulp.start(['libs', 'browserSync', 'watchTask', 'watchify', 'styles']); 149 | }); 150 | 151 | gulp.task('build', ['clean'], function() { 152 | process.env.NODE_ENV = 'production'; 153 | gulp.start(['libs', 'browserify', 'styles']); 154 | }); 155 | 156 | gulp.task('default', function() { 157 | console.log('Run "gulp watch or gulp build"'); 158 | }); 159 | 160 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Small 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "small-frontend", 3 | "version": "0.0.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/FancyPixel/small-frontend.git" 7 | }, 8 | "license": "MIT", 9 | "dependencies": { 10 | "flux": "^2.0.0", 11 | "keymirror": "~0.1.0", 12 | "moment": "^2.10.6", 13 | "object-assign": "^4.0.1", 14 | "react": "^0.13.0", 15 | "superagent": "^1.4.0" 16 | }, 17 | "devDependencies": { 18 | "browser-sync": "~2.9.6", 19 | "browserify": "^11.2.0", 20 | "del": "^2.0.2", 21 | "es6ify": "^1.6.0", 22 | "gulp": "^3.8.10", 23 | "gulp-autoprefixer": "~2.3.1", 24 | "gulp-bower": "^0.0.10", 25 | "gulp-cache": "~0.3.0", 26 | "gulp-changed": "^1.0.0", 27 | "gulp-csso": "^1.0.0", 28 | "gulp-filter": "~3.0.1", 29 | "gulp-flatten": "0.2.0", 30 | "gulp-imagemin": "latest", 31 | "gulp-jest": "~0.4.0", 32 | "gulp-minify-css": "^1.2.1", 33 | "gulp-notify": "^2.1.0", 34 | "gulp-rename": "^1.2.0", 35 | "gulp-sass": "^2.0.4", 36 | "gulp-size": "~2.0.0", 37 | "gulp-uglify": "^1.0.1", 38 | "gulp-util": "~3.0.1", 39 | "jest": "latest", 40 | "main-bower-files": "^2.5.0", 41 | "react-router": "^0.13.3", 42 | "reactify": "^1.1.1", 43 | "vinyl-buffer": "^1.0.0", 44 | "vinyl-source-stream": "^1.0.0", 45 | "watchify": "^3.4.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /scripts/actions/RouteActionCreators.react.jsx: -------------------------------------------------------------------------------- 1 | var SmallAppDispatcher = require('../dispatcher/SmallAppDispatcher.js'); 2 | var SmallConstants = require('../constants/SmallConstants.js'); 3 | 4 | var ActionTypes = SmallConstants.ActionTypes; 5 | 6 | module.exports = { 7 | 8 | redirect: function(route) { 9 | SmallAppDispatcher.handleViewAction({ 10 | type: ActionTypes.REDIRECT, 11 | route: route 12 | }); 13 | } 14 | 15 | }; 16 | 17 | 18 | -------------------------------------------------------------------------------- /scripts/actions/ServerActionCreators.react.jsx: -------------------------------------------------------------------------------- 1 | var SmallAppDispatcher = require('../dispatcher/SmallAppDispatcher.js'); 2 | var SmallConstants = require('../constants/SmallConstants.js'); 3 | 4 | var ActionTypes = SmallConstants.ActionTypes; 5 | 6 | module.exports = { 7 | 8 | receiveLogin: function(json, errors) { 9 | SmallAppDispatcher.handleServerAction({ 10 | type: ActionTypes.LOGIN_RESPONSE, 11 | json: json, 12 | errors: errors 13 | }); 14 | }, 15 | 16 | receiveStories: function(json) { 17 | SmallAppDispatcher.handleServerAction({ 18 | type: ActionTypes.RECEIVE_STORIES, 19 | json: json 20 | }); 21 | }, 22 | 23 | receiveStory: function(json) { 24 | SmallAppDispatcher.handleServerAction({ 25 | type: ActionTypes.RECEIVE_STORY, 26 | json: json 27 | }); 28 | }, 29 | 30 | receiveCreatedStory: function(json, errors) { 31 | SmallAppDispatcher.handleServerAction({ 32 | type: ActionTypes.RECEIVE_CREATED_STORY, 33 | json: json, 34 | errors: errors 35 | }); 36 | } 37 | 38 | }; 39 | 40 | -------------------------------------------------------------------------------- /scripts/actions/SessionActionCreators.react.jsx: -------------------------------------------------------------------------------- 1 | var SmallAppDispatcher = require('../dispatcher/SmallAppDispatcher.js'); 2 | var SmallConstants = require('../constants/SmallConstants.js'); 3 | var WebAPIUtils = require('../utils/WebAPIUtils.js'); 4 | 5 | var ActionTypes = SmallConstants.ActionTypes; 6 | 7 | module.exports = { 8 | 9 | signup: function(email, username, password, passwordConfirmation) { 10 | SmallAppDispatcher.handleViewAction({ 11 | type: ActionTypes.SIGNUP_REQUEST, 12 | email: email, 13 | username: username, 14 | password: password, 15 | passwordConfirmation: passwordConfirmation 16 | }); 17 | WebAPIUtils.signup(email, username, password, passwordConfirmation); 18 | }, 19 | 20 | login: function(email, password) { 21 | SmallAppDispatcher.handleViewAction({ 22 | type: ActionTypes.LOGIN_REQUEST, 23 | email: email, 24 | password: password 25 | }); 26 | WebAPIUtils.login(email, password); 27 | }, 28 | 29 | logout: function() { 30 | SmallAppDispatcher.handleViewAction({ 31 | type: ActionTypes.LOGOUT 32 | }); 33 | } 34 | 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /scripts/actions/StoryActionCreators.react.jsx: -------------------------------------------------------------------------------- 1 | var SmallAppDispatcher = require('../dispatcher/SmallAppDispatcher.js'); 2 | var SmallConstants = require('../constants/SmallConstants.js'); 3 | var WebAPIUtils = require('../utils/WebAPIUtils.js'); 4 | 5 | var ActionTypes = SmallConstants.ActionTypes; 6 | 7 | module.exports = { 8 | 9 | loadStories: function() { 10 | SmallAppDispatcher.handleViewAction({ 11 | type: ActionTypes.LOAD_STORIES 12 | }); 13 | WebAPIUtils.loadStories(); 14 | }, 15 | 16 | loadStory: function(storyId) { 17 | SmallAppDispatcher.handleViewAction({ 18 | type: ActionTypes.LOAD_STORY, 19 | storyId: storyId 20 | }); 21 | WebAPIUtils.loadStory(storyId); 22 | }, 23 | 24 | createStory: function(title, body) { 25 | SmallAppDispatcher.handleViewAction({ 26 | type: ActionTypes.CREATE_STORY, 27 | title: title, 28 | body: body 29 | }); 30 | WebAPIUtils.createStory(title, body); 31 | } 32 | 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /scripts/app.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var router = require('./stores/RouteStore.react.jsx').getRouter(); 3 | window.React = React; 4 | 5 | router.run(function (Handler, state) { 6 | React.render(, document.getElementById('content')); 7 | }); 8 | -------------------------------------------------------------------------------- /scripts/components/Header.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var Link = Router.Link; 4 | var ReactPropTypes = React.PropTypes; 5 | var SessionActionCreators = require('../actions/SessionActionCreators.react.jsx'); 6 | 7 | var Header = React.createClass({ 8 | 9 | propTypes: { 10 | isLoggedIn: ReactPropTypes.bool, 11 | email: ReactPropTypes.string 12 | }, 13 | logout: function(e) { 14 | e.preventDefault(); 15 | SessionActionCreators.logout(); 16 | }, 17 | render: function() { 18 | var rightNav = this.props.isLoggedIn ? ( 19 | 27 | ) : ( 28 | 32 | ); 33 | 34 | var leftNav = this.props.isLoggedIn ? ( 35 | 38 | ) : ( 39 |
40 | ); 41 | 42 | return ( 43 | 56 | ); 57 | } 58 | }); 59 | 60 | module.exports = Header; 61 | 62 | -------------------------------------------------------------------------------- /scripts/components/SmallApp.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var RouteHandler = require('react-router').RouteHandler; 3 | var Header = require('../components/Header.react.jsx'); 4 | var SessionStore = require('../stores/SessionStore.react.jsx'); 5 | var RouteStore = require('../stores/RouteStore.react.jsx'); 6 | 7 | function getStateFromStores() { 8 | return { 9 | isLoggedIn: SessionStore.isLoggedIn(), 10 | email: SessionStore.getEmail() 11 | }; 12 | } 13 | 14 | var SmallApp = React.createClass({ 15 | 16 | getInitialState: function() { 17 | return getStateFromStores(); 18 | }, 19 | 20 | componentDidMount: function() { 21 | SessionStore.addChangeListener(this._onChange); 22 | }, 23 | 24 | componentWillUnmount: function() { 25 | SessionStore.removeChangeListener(this._onChange); 26 | }, 27 | 28 | _onChange: function() { 29 | this.setState(getStateFromStores()); 30 | }, 31 | 32 | render: function() { 33 | return ( 34 |
35 |
38 | 39 |
40 | ); 41 | } 42 | 43 | }); 44 | 45 | module.exports = SmallApp; 46 | 47 | -------------------------------------------------------------------------------- /scripts/components/common/ErrorNotice.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var ErrorNotice = React.createClass({ 4 | render: function() { 5 | return ( 6 |
7 | 12 |
13 | ); 14 | } 15 | }); 16 | 17 | module.exports = ErrorNotice; 18 | 19 | -------------------------------------------------------------------------------- /scripts/components/session/LoginPage.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var SessionActionCreators = require('../../actions/SessionActionCreators.react.jsx'); 3 | var SessionStore = require('../../stores/SessionStore.react.jsx'); 4 | var ErrorNotice = require('../../components/common/ErrorNotice.react.jsx'); 5 | 6 | var LoginPage = React.createClass({ 7 | 8 | getInitialState: function() { 9 | return { errors: [] }; 10 | }, 11 | 12 | componentDidMount: function() { 13 | SessionStore.addChangeListener(this._onChange); 14 | }, 15 | 16 | componentWillUnmount: function() { 17 | SessionStore.removeChangeListener(this._onChange); 18 | }, 19 | 20 | _onChange: function() { 21 | this.setState({ errors: SessionStore.getErrors() }); 22 | }, 23 | 24 | _onSubmit: function(e) { 25 | e.preventDefault(); 26 | this.setState({ errors: [] }); 27 | var email = this.refs.email.getDOMNode().value; 28 | var password = this.refs.password.getDOMNode().value; 29 | SessionActionCreators.login(email, password); 30 | }, 31 | 32 | render: function() { 33 | var errors = (this.state.errors.length > 0) ? :
; 34 | return ( 35 |
36 | {errors} 37 |
38 |
39 |
40 |
41 | 42 | 43 |
44 |
45 | 46 | 47 |
48 | 49 |
50 |
51 |
52 |
53 | ); 54 | } 55 | }); 56 | 57 | module.exports = LoginPage; 58 | 59 | -------------------------------------------------------------------------------- /scripts/components/session/SignupPage.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var SessionActionCreators = require('../../actions/SessionActionCreators.react.jsx'); 3 | var SessionStore = require('../../stores/SessionStore.react.jsx'); 4 | var ErrorNotice = require('../../components/common/ErrorNotice.react.jsx'); 5 | 6 | var SignupPage = React.createClass({ 7 | 8 | getInitialState: function() { 9 | return { errors: [] }; 10 | }, 11 | 12 | componentDidMount: function() { 13 | SessionStore.addChangeListener(this._onChange); 14 | }, 15 | 16 | componentWillUnmount: function() { 17 | SessionStore.removeChangeListener(this._onChange); 18 | }, 19 | 20 | _onChange: function() { 21 | this.setState({ errors: SessionStore.getErrors() }); 22 | }, 23 | 24 | _onSubmit: function(e) { 25 | e.preventDefault(); 26 | this.setState({ errors: [] }); 27 | var email = this.refs.email.getDOMNode().value; 28 | var username = this.refs.username.getDOMNode().value; 29 | var password = this.refs.password.getDOMNode().value; 30 | var passwordConfirmation = this.refs.passwordConfirmation.getDOMNode().value; 31 | if (password !== passwordConfirmation) { 32 | this.setState({ errors: ['Password and password confirmation should match']}); 33 | } else { 34 | SessionActionCreators.signup(email, username, password, passwordConfirmation); 35 | } 36 | }, 37 | 38 | render: function() { 39 | var errors = (this.state.errors.length > 0) ? :
; 40 | return ( 41 |
42 | {errors} 43 |
44 |
45 |
46 |
47 | 48 | 49 |
50 |
51 | 52 | 53 |
54 |
55 | 56 | 57 |
58 |
59 | 60 | 61 |
62 | 63 |
64 |
65 |
66 |
67 | ); 68 | } 69 | }); 70 | 71 | module.exports = SignupPage; 72 | 73 | -------------------------------------------------------------------------------- /scripts/components/stories/StoriesPage.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var WebAPIUtils = require('../../utils/WebAPIUtils.js'); 3 | var StoryStore = require('../../stores/StoryStore.react.jsx'); 4 | var ErrorNotice = require('../../components/common/ErrorNotice.react.jsx'); 5 | var StoryActionCreators = require('../../actions/StoryActionCreators.react.jsx'); 6 | var Router = require('react-router'); 7 | var Link = Router.Link; 8 | var moment = require('moment'); 9 | 10 | var StoriesPage = React.createClass({ 11 | 12 | getInitialState: function() { 13 | return { 14 | stories: StoryStore.getAllStories(), 15 | errors: [] 16 | }; 17 | }, 18 | 19 | componentDidMount: function() { 20 | StoryStore.addChangeListener(this._onChange); 21 | StoryActionCreators.loadStories(); 22 | }, 23 | 24 | componentWillUnmount: function() { 25 | StoryStore.removeChangeListener(this._onChange); 26 | }, 27 | 28 | _onChange: function() { 29 | this.setState({ 30 | stories: StoryStore.getAllStories(), 31 | errors: StoryStore.getErrors() 32 | }); 33 | }, 34 | 35 | render: function() { 36 | var errors = (this.state.errors.length > 0) ? :
; 37 | return ( 38 |
39 | {errors} 40 |
41 | 42 |
43 |
44 | ); 45 | } 46 | }); 47 | 48 | var StoryItem = React.createClass({ 49 | render: function() { 50 | return ( 51 |
  • 52 |
    53 | 54 | {this.props.story.title} 55 | 56 |
    57 |
    {this.props.story['abstract']}...
    58 | {this.props.story.user.username} 59 | - {moment(this.props.story.created_at).fromNow()} 60 |
  • 61 | ); 62 | } 63 | }); 64 | 65 | var StoriesList = React.createClass({ 66 | render: function() { 67 | return ( 68 |
      69 | {this.props.stories.map(function(story, index){ 70 | return 71 | })} 72 |
    73 | ); 74 | } 75 | }); 76 | 77 | module.exports = StoriesPage; 78 | 79 | -------------------------------------------------------------------------------- /scripts/components/stories/StoryNew.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var SmallAppDispatcher = require('../../dispatcher/SmallAppDispatcher.js'); 3 | var SmallConstants = require('../../constants/SmallConstants.js'); 4 | var WebAPIUtils = require('../../utils/WebAPIUtils.js'); 5 | var SessionStore = require('../../stores/SessionStore.react.jsx'); 6 | var StoryActionCreators = require('../../actions/StoryActionCreators.react.jsx'); 7 | var RouteActionCreators = require('../../actions/RouteActionCreators.react.jsx'); 8 | 9 | var StoryNew = React.createClass({ 10 | 11 | componentDidMount: function() { 12 | if (!SessionStore.isLoggedIn()) { 13 | RouteActionCreators.redirect('app'); 14 | } 15 | }, 16 | 17 | _onSubmit: function(e) { 18 | e.preventDefault(); 19 | var title = this.refs.title.getDOMNode().value; 20 | var body = this.refs.body.getDOMNode().value; 21 | StoryActionCreators.createStory(title, body); 22 | }, 23 | 24 | render: function() { 25 | return ( 26 |
    27 |
    28 |
    29 | 30 |
    31 |
    32 |