├── .gitignore ├── code ├── .meteor │ ├── cordova-plugins │ ├── .gitignore │ ├── release │ ├── platforms │ ├── .id │ ├── .finished-upgraders │ ├── packages │ └── versions ├── packages.json ├── client │ ├── helpers │ │ ├── template.js │ │ └── flow-router.js │ ├── modules │ │ ├── _modules.js │ │ ├── startup.js │ │ ├── recover-password.js │ │ ├── login.js │ │ ├── signup.js │ │ └── reset-password.js │ ├── startup.js │ ├── stylesheets │ │ ├── components │ │ │ └── _login.scss │ │ ├── application.scss │ │ ├── objects │ │ │ └── _forms.scss │ │ └── tools │ │ │ └── _extends.scss │ └── components │ │ ├── globals │ │ ├── not-found.jsx │ │ ├── loading.jsx │ │ ├── public-navigation.jsx │ │ ├── authenticated-navigation.jsx │ │ └── header.jsx │ │ ├── layouts │ │ └── default.jsx │ │ ├── authenticated │ │ ├── person.jsx │ │ ├── index.jsx │ │ ├── dashboard.jsx │ │ └── people-table.jsx │ │ └── public │ │ ├── recover-password.jsx │ │ ├── signup.jsx │ │ ├── login.jsx │ │ └── reset-password.jsx ├── private │ └── email │ │ └── templates │ │ └── .gitkeep ├── .jshintrc ├── server │ ├── modules │ │ ├── _modules.js │ │ ├── set-environment-variables.js │ │ ├── startup.js │ │ ├── generate-people.js │ │ └── generate-accounts.js │ ├── startup.js │ ├── publications │ │ └── dashboard.js │ └── admin │ │ └── reset-password.js ├── both │ ├── startup.js │ ├── modules │ │ ├── _modules.js │ │ └── startup.js │ ├── methods │ │ ├── remove │ │ │ └── collection.js │ │ ├── insert │ │ │ └── collection.js │ │ ├── read │ │ │ └── collection.js │ │ └── update │ │ │ └── collection.js │ └── routes │ │ ├── configure.jsx │ │ ├── authenticated.jsx │ │ └── public.jsx ├── .gitignore ├── public │ └── favicon.ico ├── settings-development.json ├── application.html ├── .editorconfig ├── collections │ ├── users.js │ └── people.js ├── packages │ └── npm-container │ │ ├── index.js │ │ └── package.js ├── README.md └── package.json ├── README.md └── getting-started-with-react.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /code/.meteor/cordova-plugins: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code/packages.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /code/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /code/client/helpers/template.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.2.1 2 | -------------------------------------------------------------------------------- /code/private/email/templates/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true 3 | } 4 | -------------------------------------------------------------------------------- /code/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /code/client/modules/_modules.js: -------------------------------------------------------------------------------- 1 | Modules.client = {}; 2 | -------------------------------------------------------------------------------- /code/server/modules/_modules.js: -------------------------------------------------------------------------------- 1 | Modules.server = {}; 2 | -------------------------------------------------------------------------------- /code/both/startup.js: -------------------------------------------------------------------------------- 1 | Meteor.startup( () => Modules.both.startup() ); 2 | -------------------------------------------------------------------------------- /code/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | settings-production.json 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /code/both/modules/_modules.js: -------------------------------------------------------------------------------- 1 | Modules = {}; 2 | Modules.both = {}; 3 | -------------------------------------------------------------------------------- /code/client/startup.js: -------------------------------------------------------------------------------- 1 | Meteor.startup( () => Modules.client.startup() ); 2 | -------------------------------------------------------------------------------- /code/server/startup.js: -------------------------------------------------------------------------------- 1 | Meteor.startup( () => Modules.server.startup() ); 2 | -------------------------------------------------------------------------------- /code/both/modules/startup.js: -------------------------------------------------------------------------------- 1 | let startup = () => {}; 2 | 3 | Modules.both.startup = startup; 4 | -------------------------------------------------------------------------------- /code/client/modules/startup.js: -------------------------------------------------------------------------------- 1 | let startup = () => {}; 2 | 3 | Modules.client.startup = startup; 4 | -------------------------------------------------------------------------------- /code/client/stylesheets/components/_login.scss: -------------------------------------------------------------------------------- 1 | .login label { 2 | display: block; 3 | @extend %clearfix; 4 | } 5 | -------------------------------------------------------------------------------- /code/server/publications/dashboard.js: -------------------------------------------------------------------------------- 1 | Meteor.publish( 'dashboard', function() { 2 | return People.find(); 3 | }); 4 | -------------------------------------------------------------------------------- /code/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/themeteorchef/getting-started-with-react-old/HEAD/code/public/favicon.ico -------------------------------------------------------------------------------- /code/client/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "tools/extends"; 2 | 3 | @import "objects/forms"; 4 | 5 | @import "components/login"; 6 | -------------------------------------------------------------------------------- /code/settings-development.json: -------------------------------------------------------------------------------- 1 | { 2 | "public": { 3 | "key": "value" 4 | }, 5 | "private": { 6 | "MAIL_URL": "" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /code/client/stylesheets/objects/_forms.scss: -------------------------------------------------------------------------------- 1 | label.error { 2 | display: block; 3 | margin-top: 10px; 4 | font-weight: normal; 5 | color: lighten( red, 20% ); 6 | } 7 | -------------------------------------------------------------------------------- /code/client/components/globals/not-found.jsx: -------------------------------------------------------------------------------- 1 | NotFound = React.createClass({ 2 | render() { 3 | return ( 4 |

404 — Not Found.

5 | ); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /code/client/components/globals/loading.jsx: -------------------------------------------------------------------------------- 1 | Loading = React.createClass({ 2 | render() { 3 | return ( 4 | 5 | ); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /code/application.html: -------------------------------------------------------------------------------- 1 | 2 | Application Name 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /code/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /code/collections/users.js: -------------------------------------------------------------------------------- 1 | Meteor.users.allow({ 2 | insert: () => false, 3 | update: () => false, 4 | remove: () => false 5 | }); 6 | 7 | Meteor.users.deny({ 8 | insert: () => true, 9 | update: () => true, 10 | remove: () => true 11 | }); 12 | -------------------------------------------------------------------------------- /code/both/methods/remove/collection.js: -------------------------------------------------------------------------------- 1 | Meteor.methods({ 2 | removeMethod( argument ) { 3 | check( argument, String ); 4 | 5 | try { 6 | Collection.remove( argument ); 7 | } catch( exception ) { 8 | return exception; 9 | } 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /code/server/modules/set-environment-variables.js: -------------------------------------------------------------------------------- 1 | let setEnvironmentVariables = () => { 2 | if ( Meteor.settings.private ) { 3 | process.env.MAIL_URL = Meteor.settings.private.MAIL_URL; 4 | } 5 | }; 6 | 7 | Modules.server.setEnvironmentVariables = setEnvironmentVariables; 8 | -------------------------------------------------------------------------------- /code/client/components/layouts/default.jsx: -------------------------------------------------------------------------------- 1 | Default = React.createClass({ 2 | render() { 3 | return ( 4 |
5 | 6 |
7 | {this.props.yield} 8 |
9 |
10 | ); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /code/client/stylesheets/tools/_extends.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Clearfix 3 | via http://nicolasgallagher.com/micro-clearfix-hack 4 | */ 5 | %clearfix { 6 | *zoom: 1; 7 | 8 | &:before, 9 | &:after { 10 | display: table; 11 | content: ""; 12 | } 13 | 14 | &:after { 15 | clear: both; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /code/both/methods/insert/collection.js: -------------------------------------------------------------------------------- 1 | Meteor.methods({ 2 | insertMethod( argument ) { 3 | check( argument, Object ); 4 | 5 | try { 6 | var documentId = Collection.insert( argument ); 7 | return documentId; 8 | } catch( exception ) { 9 | return exception; 10 | } 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /code/packages/npm-container/index.js: -------------------------------------------------------------------------------- 1 | Meteor.npmRequire = function(moduleName) { 2 | var module = Npm.require(moduleName); 3 | return module; 4 | }; 5 | 6 | Meteor.require = function(moduleName) { 7 | console.warn('Meteor.require is deprecated. Please use Meteor.npmRequire instead!'); 8 | return Meteor.npmRequire(moduleName); 9 | }; -------------------------------------------------------------------------------- /code/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 1ipv1tp12xzfzhba088c 8 | -------------------------------------------------------------------------------- /code/both/methods/read/collection.js: -------------------------------------------------------------------------------- 1 | Meteor.methods({ 2 | readMethod( argument ) { 3 | check( argument, String ); 4 | 5 | var document = Collection.findOne( argument ); 6 | 7 | if ( !document ) { 8 | throw new Meteor.Error( 'document-not-found', 'No documents found matching this query.' ); 9 | } 10 | 11 | return document; 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /code/both/methods/update/collection.js: -------------------------------------------------------------------------------- 1 | Meteor.methods({ 2 | updateMethod( argument ) { 3 | check( argument, Object ); 4 | 5 | try { 6 | var documentId = Collection.update( argument._id, { 7 | $set: { 'key': argument.key } 8 | }); 9 | return documentId; 10 | } catch( exception ) { 11 | return exception; 12 | } 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /code/README.md: -------------------------------------------------------------------------------- 1 | # The Meteor Chef - Base 2 | A starting point for Meteor apps. 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Base Versionv3.3.0
Meteor Versionv1.2.1
16 | 17 | [Read the Documentation](http://themeteorchef.com/base) 18 | -------------------------------------------------------------------------------- /code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "application-name", 3 | "version": "1.0.0", 4 | "description": "Application description.", 5 | "scripts": { 6 | "start": "meteor --settings settings-development.json", 7 | "staging": "meteor deploy staging.meteor.com --settings settings-development.json", 8 | "production": "meteor deploy production.meteor.com --settings settings-production.json" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /code/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | -------------------------------------------------------------------------------- /code/client/components/authenticated/person.jsx: -------------------------------------------------------------------------------- 1 | Person = React.createClass({ 2 | render() { 3 | return ( 4 | 5 | 6 | {this.props.person.name} 7 | 8 | {this.props.person.name} 9 | {this.props.person.email} 10 | 11 | ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /code/client/components/authenticated/index.jsx: -------------------------------------------------------------------------------- 1 | Index = React.createClass({ 2 | render() { 3 | return ( 4 |
5 |

Base

6 |

A starting point for Meteor applications.

7 |

Read the Documentation

8 |

Currently at v3.3.0

9 |
10 | ); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /code/client/components/authenticated/dashboard.jsx: -------------------------------------------------------------------------------- 1 | Dashboard = React.createClass({ 2 | mixins: [ ReactMeteorData ], 3 | getMeteorData() { 4 | let subscription = Meteor.subscribe( 'dashboard' ); 5 | 6 | return { 7 | isLoading: !subscription.ready(), 8 | people: People.find().fetch() 9 | }; 10 | }, 11 | render() { 12 | if ( this.data.isLoading ) { 13 | return ; 14 | } else { 15 | return ( 16 | 17 | ); 18 | } 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /code/both/routes/configure.jsx: -------------------------------------------------------------------------------- 1 | FlowRouter.notFound = { 2 | action() { 3 | ReactLayout.render( Default, { yield: } ); 4 | } 5 | }; 6 | 7 | Accounts.onLogin( () => { 8 | let currentRoute = FlowRouter.current(); 9 | if ( currentRoute && currentRoute.route.group.name === 'public' ) { 10 | FlowRouter.go( 'index' ); 11 | } 12 | }); 13 | 14 | if ( Meteor.isClient ) { 15 | Tracker.autorun( () => { 16 | if ( !Meteor.userId() && FlowRouter.current().route ) { 17 | FlowRouter.go( 'login' ); 18 | } 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /code/server/modules/startup.js: -------------------------------------------------------------------------------- 1 | let startup = () => { 2 | _setBrowserPolicies(); 3 | _generateAccounts(); 4 | _generatePeople(); 5 | _setEnvironmentVariables(); 6 | }; 7 | 8 | let _setBrowserPolicies = () => { 9 | BrowserPolicy.content.allowOriginForAll( '*.amazonaws.com' ); 10 | }; 11 | 12 | let _generateAccounts = () => Modules.server.generateAccounts(); 13 | 14 | let _generatePeople = () => Modules.server.generatePeople(); 15 | 16 | let _setEnvironmentVariables = () => Modules.server.setEnvironmentVariables(); 17 | 18 | Modules.server.startup = startup; 19 | -------------------------------------------------------------------------------- /code/client/components/globals/public-navigation.jsx: -------------------------------------------------------------------------------- 1 | PublicNavigation = React.createClass({ 2 | render() { 3 | return ( 4 | 14 | ); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /code/client/helpers/flow-router.js: -------------------------------------------------------------------------------- 1 | let pathFor = ( path, params ) => { 2 | console.log( params ); 3 | 4 | let query = params && params.query ? FlowRouter._qs.parse( params.query ) : {}; 5 | return FlowRouter.path( path, params, query ); 6 | }; 7 | 8 | let urlFor = ( path, params ) => { 9 | return Meteor.absoluteUrl( pathFor( path, params ) ); 10 | }; 11 | 12 | let currentRoute = ( route ) => { 13 | FlowRouter.watchPathChange(); 14 | return FlowRouter.current().route.name === route ? 'active' : ''; 15 | }; 16 | 17 | FlowHelpers = { 18 | pathFor: pathFor, 19 | urlFor: urlFor, 20 | currentRoute: currentRoute 21 | }; 22 | -------------------------------------------------------------------------------- /code/both/routes/authenticated.jsx: -------------------------------------------------------------------------------- 1 | const authenticatedRedirect = () => { 2 | if ( !Meteor.loggingIn() && !Meteor.userId() ) { 3 | FlowRouter.go( 'login' ); 4 | } 5 | }; 6 | 7 | const authenticatedRoutes = FlowRouter.group({ 8 | name: 'authenticated', 9 | triggersEnter: [ authenticatedRedirect ] 10 | }); 11 | 12 | authenticatedRoutes.route( '/', { 13 | name: 'index', 14 | action() { 15 | ReactLayout.render( Default, { yield: } ); 16 | } 17 | }); 18 | 19 | authenticatedRoutes.route( '/dashboard', { 20 | name: 'dashboard', 21 | action() { 22 | ReactLayout.render( Default, { yield: } ); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /code/client/components/authenticated/people-table.jsx: -------------------------------------------------------------------------------- 1 | PeopleTable = React.createClass({ 2 | render() { 3 | return ( 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {this.props.people.map( ( person, index ) => { 15 | return ; 16 | })} 17 | 18 |
NameEmail Address
19 |
20 | ); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /code/collections/people.js: -------------------------------------------------------------------------------- 1 | People = new Meteor.Collection( 'people' ); 2 | 3 | People.allow({ 4 | insert: () => false, 5 | update: () => false, 6 | remove: () => false 7 | }); 8 | 9 | People.deny({ 10 | insert: () => true, 11 | update: () => true, 12 | remove: () => true 13 | }); 14 | 15 | let PeopleSchema = new SimpleSchema({ 16 | "name": { 17 | type: String, 18 | label: "The name of the person." 19 | }, 20 | "email": { 21 | type: String, 22 | label: "The email address of the person." 23 | }, 24 | "avatar": { 25 | type: String, 26 | label: "The URL for the avatar of the person." 27 | } 28 | }); 29 | 30 | People.attachSchema( PeopleSchema ); 31 | -------------------------------------------------------------------------------- /code/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # 3 | # 'meteor add' and 'meteor remove' will edit this file for you, 4 | # but you can also edit it by hand. 5 | 6 | accounts-password 7 | accounts-base 8 | jquery 9 | check 10 | audit-argument-checks 11 | themeteorchef:jquery-validation 12 | twbs:bootstrap 13 | browser-policy 14 | meteorhacks:npm 15 | themeteorchef:bert 16 | meteorhacks:ssr 17 | standard-minifiers 18 | npm-container 19 | ecmascript 20 | digilord:faker 21 | kadira:flow-router 22 | meteorhacks:fast-render 23 | meteor-base 24 | session 25 | fourseven:scss 26 | stevezhu:lodash 27 | reactive-var 28 | reactive-dict 29 | aldeed:collection2 30 | tracker 31 | react 32 | kadira:react-layout 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### The Meteor Chef 2 | ###### \#015 - Getting Started with React 3 | 4 | In this recipe, we'll learn how to get started with the React user interface library. We'll refactor Base, the starter kit used here at The Meteor Chef, replacing Blaze with React. We'll learn how to define components, handle routing with Flow Router, and get a feel for React's basic API and how to get data into our applications. 5 | 6 | [Read on The Meteor Chef](http://themeteorchef.com/recipes/getting-started-with-react) 7 | 8 | [Demo the Recipe](http://tmc-015-demo.meteor.com) 9 | 10 | [Download the Source](https://github.com/themeteorchef/getting-started-with-react/archive/master.zip) 11 | 12 | The code for this recipe is licensed under the [MIT License](http://opensource.org/licenses/MIT). -------------------------------------------------------------------------------- /code/server/admin/reset-password.js: -------------------------------------------------------------------------------- 1 | Accounts.emailTemplates.resetPassword.siteName = () => "Application Name"; 2 | Accounts.emailTemplates.resetPassword.from = () => "Application Name "; 3 | Accounts.emailTemplates.resetPassword.subject = () => "[Application Name] Reset Your Password"; 4 | 5 | Accounts.emailTemplates.resetPassword.text = ( user, url ) => { 6 | let emailAddress = user.emails[0].address, 7 | urlWithoutHash = url.replace( '#/', '' ), 8 | supportEmail = "support@application.com", 9 | emailBody = `A password reset has been requested for the account related to this address (${emailAddress}). To reset the password, visit the following link:\n\n${urlWithoutHash}\n\n If you did not request this reset, please ignore this email. If you feel something is wrong, please contact our support team: ${supportEmail}.`; 10 | 11 | return emailBody; 12 | }; 13 | -------------------------------------------------------------------------------- /code/both/routes/public.jsx: -------------------------------------------------------------------------------- 1 | const publicRedirect = () => { 2 | if ( Meteor.userId() ) { 3 | FlowRouter.go( 'index' ); 4 | } 5 | }; 6 | 7 | const publicRoutes = FlowRouter.group({ 8 | name: 'public', 9 | triggersEnter: [ publicRedirect ] 10 | }); 11 | 12 | publicRoutes.route( '/signup', { 13 | name: 'signup', 14 | action() { 15 | ReactLayout.render( Default, { yield: } ); 16 | } 17 | }); 18 | 19 | publicRoutes.route( '/login', { 20 | name: 'login', 21 | action() { 22 | ReactLayout.render( Default, { yield: } ); 23 | } 24 | }); 25 | 26 | publicRoutes.route( '/recover-password', { 27 | name: 'recover-password', 28 | action() { 29 | ReactLayout.render( Default, { yield: } ); 30 | } 31 | }); 32 | 33 | publicRoutes.route( '/reset-password/:token', { 34 | name: 'reset-password', 35 | action() { 36 | ReactLayout.render( Default, { yield: } ); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /code/packages/npm-container/package.js: -------------------------------------------------------------------------------- 1 | var path = Npm.require('path'); 2 | var fs = Npm.require('fs'); 3 | 4 | Package.describe({ 5 | summary: 'Contains all your npm dependencies', 6 | version: '1.2.0', 7 | name: 'npm-container' 8 | }); 9 | 10 | var packagesJsonFile = path.resolve('./packages.json'); 11 | try { 12 | var fileContent = fs.readFileSync(packagesJsonFile); 13 | var packages = JSON.parse(fileContent.toString()); 14 | Npm.depends(packages); 15 | } catch (ex) { 16 | console.error('ERROR: packages.json parsing error [ ' + ex.message + ' ]'); 17 | } 18 | 19 | // Adding the app's packages.json as a used file for this package will get 20 | // Meteor to watch it and reload this package when it changes 21 | Package.onUse(function(api) { 22 | api.addFiles('index.js', 'server'); 23 | if (api.addAssets) { 24 | api.addAssets('../../packages.json', 'server'); 25 | } else { 26 | api.addFiles('../../packages.json', 'server', { 27 | isAsset: true 28 | }); 29 | } 30 | }); -------------------------------------------------------------------------------- /code/client/modules/recover-password.js: -------------------------------------------------------------------------------- 1 | let recoverPassword = ( options ) => { 2 | _validate( options.form ); 3 | }; 4 | 5 | let _validate = ( form ) => { 6 | $( form ).validate( validation() ); 7 | }; 8 | 9 | let validation = () => { 10 | return { 11 | rules: { 12 | emailAddress: { 13 | required: true, 14 | email: true 15 | } 16 | }, 17 | messages: { 18 | emailAddress: { 19 | required: 'Need an email address here.', 20 | email: 'Is this email address legit?' 21 | } 22 | }, 23 | submitHandler() { _handleRecovery(); } 24 | }; 25 | }; 26 | 27 | let _handleRecovery = ( template ) => { 28 | let email = $( '[name="emailAddress"]' ).val(); 29 | 30 | Accounts.forgotPassword( { email: email }, ( error ) => { 31 | if ( error ) { 32 | Bert.alert( error.reason, 'warning' ); 33 | } else { 34 | Bert.alert( 'Check your inbox for a reset link!', 'success' ); 35 | } 36 | }); 37 | }; 38 | 39 | Modules.client.recoverPassword = recoverPassword; 40 | -------------------------------------------------------------------------------- /code/client/components/globals/authenticated-navigation.jsx: -------------------------------------------------------------------------------- 1 | AuthenticatedNavigation = React.createClass({ 2 | currentUserEmail() { 3 | return Meteor.user().emails[0].address; 4 | }, 5 | logout() { 6 | return Meteor.logout(); 7 | }, 8 | render() { 9 | return ( 10 | 24 | ); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /code/client/modules/login.js: -------------------------------------------------------------------------------- 1 | let login = ( options ) => { 2 | _validate( options.form ); 3 | }; 4 | 5 | let _validate = ( form ) => { 6 | $( form ).validate( validation() ); 7 | }; 8 | 9 | let validation = () => { 10 | return { 11 | rules: { 12 | emailAddress: { 13 | required: true, 14 | email: true 15 | }, 16 | password: { 17 | required: true 18 | } 19 | }, 20 | messages: { 21 | emailAddress: { 22 | required: 'Need an email address here.', 23 | email: 'Is this email address legit?' 24 | }, 25 | password: { 26 | required: 'Need a password here.' 27 | } 28 | }, 29 | submitHandler() { _handleLogin(); } 30 | }; 31 | }; 32 | 33 | let _handleLogin = () => { 34 | let email = $( '[name="emailAddress"]' ).val(), 35 | password = $( '[name="password"]' ).val(); 36 | 37 | Meteor.loginWithPassword( email, password, ( error ) => { 38 | if ( error ) { 39 | Bert.alert( error.reason, 'warning' ); 40 | } else { 41 | Bert.alert( 'Logged in!', 'success' ); 42 | } 43 | }); 44 | }; 45 | 46 | Modules.client.login = login; 47 | -------------------------------------------------------------------------------- /code/server/modules/generate-people.js: -------------------------------------------------------------------------------- 1 | let generate = () => { 2 | let peopleCount = 5, 3 | peopleExist = _checkIfPeopleExist( peopleCount ); 4 | 5 | if ( !peopleExist ) { 6 | _createPeople( _generatePeople( peopleCount ) ); 7 | } 8 | }; 9 | 10 | let _checkIfPeopleExist = ( count ) => { 11 | let peopleCount = People.find().count(); 12 | return peopleCount < count ? false : true; 13 | }; 14 | 15 | let _createPeople = ( people ) => { 16 | for ( let i = 0; i < people.length; i++ ) { 17 | let person = people[ i ]; 18 | _createPerson( person ); 19 | } 20 | }; 21 | 22 | let _checkIfUserExists = ( email ) => { 23 | return Meteor.users.findOne( { 'emails.address': email } ); 24 | }; 25 | 26 | let _createPerson = ( person ) => { 27 | People.insert( person ); 28 | }; 29 | 30 | let _generatePeople = ( count ) => { 31 | let people = []; 32 | 33 | for ( let i = 0; i < count; i++ ) { 34 | people.push({ 35 | name: faker.name.findName(), 36 | email: faker.internet.email(), 37 | avatar: faker.image.avatar() 38 | }); 39 | } 40 | 41 | return people; 42 | }; 43 | 44 | Modules.server.generatePeople = generate; 45 | -------------------------------------------------------------------------------- /code/client/components/public/recover-password.jsx: -------------------------------------------------------------------------------- 1 | RecoverPassword = React.createClass({ 2 | componentDidMount() { 3 | Modules.client.recoverPassword({ 4 | form: "#recover-password" 5 | }); 6 | }, 7 | handleSubmit( event ) { 8 | event.preventDefault(); 9 | }, 10 | render() { 11 | return ( 12 |
13 |
14 |

Recover Password

15 |
16 |

Enter your email address below to receive a link to reset your password.

17 |
18 | 19 | 20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 | ); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /code/client/components/globals/header.jsx: -------------------------------------------------------------------------------- 1 | AppHeader = React.createClass({ 2 | brandLink() { 3 | if ( !Meteor.loggingIn() && !Meteor.userId() ) { 4 | return FlowRouter.path( 'login' ); 5 | } 6 | 7 | return FlowRouter.path( 'index' ); 8 | }, 9 | navigationItems() { 10 | if ( !Meteor.loggingIn() && Meteor.user() ) { 11 | return ; 12 | } else { 13 | return ; 14 | } 15 | }, 16 | render() { 17 | return ( 18 | 32 | ); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /code/client/modules/signup.js: -------------------------------------------------------------------------------- 1 | let signup = ( options ) => { 2 | _validate( options.form ); 3 | }; 4 | 5 | let _validate = ( form ) => { 6 | $( form ).validate( validation() ); 7 | }; 8 | 9 | let validation = () => { 10 | return { 11 | rules: { 12 | emailAddress: { 13 | required: true, 14 | email: true 15 | }, 16 | password: { 17 | required: true, 18 | minlength: 6 19 | } 20 | }, 21 | messages: { 22 | emailAddress: { 23 | required: 'Need an email address here.', 24 | email: 'Is this email address legit?' 25 | }, 26 | password: { 27 | required: 'Need a password here.', 28 | minlength: 'Use at least six characters, please.' 29 | } 30 | }, 31 | submitHandler() { _handleSignup(); } 32 | }; 33 | }; 34 | 35 | let _handleSignup = () => { 36 | let user = { 37 | email: $( '[name="emailAddress"]' ).val(), 38 | password: $( '[name="password"]' ).val() 39 | }; 40 | 41 | Accounts.createUser( user, ( error ) => { 42 | if ( error ) { 43 | Bert.alert( error.reason, 'danger' ); 44 | } else { 45 | Bert.alert( 'Welcome!', 'success' ); 46 | } 47 | }); 48 | }; 49 | 50 | Modules.client.signup = signup; 51 | -------------------------------------------------------------------------------- /code/client/components/public/signup.jsx: -------------------------------------------------------------------------------- 1 | Signup = React.createClass({ 2 | componentDidMount() { 3 | Modules.client.signup({ 4 | form: "#signup" 5 | }); 6 | }, 7 | handleSubmit( event ) { 8 | event.preventDefault(); 9 | }, 10 | render() { 11 | return ( 12 |
13 |
14 |

Sign Up

15 |
16 |
17 | 18 | 19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 |

Already have an account? Log In.

29 |
30 |
31 | ); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /code/client/modules/reset-password.js: -------------------------------------------------------------------------------- 1 | let resetPassword = ( options ) => { 2 | _validate( options.form ); 3 | }; 4 | 5 | let _validate = ( form ) => { 6 | $( form ).validate( validation() ); 7 | }; 8 | 9 | let validation = () => { 10 | return { 11 | rules: { 12 | newPassword: { 13 | required: true, 14 | minlength: 6 15 | }, 16 | repeatNewPassword: { 17 | required: true, 18 | minlength: 6, 19 | equalTo: '[name="newPassword"]' 20 | } 21 | }, 22 | messages: { 23 | newPassword: { 24 | required: "Enter a new password, please.", 25 | minlength: "Use at least six characters, please." 26 | }, 27 | repeatNewPassword: { 28 | required: "Repeat your new password, please.", 29 | equalTo: "Hmm, your passwords don't match. Try again?" 30 | } 31 | }, 32 | submitHandler() { _handleReset(); } 33 | }; 34 | }; 35 | 36 | let _handleReset = () => { 37 | var token = FlowRouter.current().params.token, 38 | password = $( '[name="newPassword"]' ).val(); 39 | 40 | Accounts.resetPassword( token, password, ( error ) => { 41 | if ( error ) { 42 | Bert.alert( error.reason, 'danger' ); 43 | } else { 44 | Bert.alert( 'Password reset!', 'success' ); 45 | } 46 | }); 47 | }; 48 | 49 | Modules.client.resetPassword = resetPassword; 50 | -------------------------------------------------------------------------------- /code/client/components/public/login.jsx: -------------------------------------------------------------------------------- 1 | Login = React.createClass({ 2 | componentDidMount() { 3 | Modules.client.login( { form: "#login" } ); 4 | }, 5 | handleSubmit( event ) { 6 | event.preventDefault(); 7 | }, 8 | render() { 9 | return ( 10 |
11 |
12 |

Login

13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 |
25 |
26 |

Don't have an account? Sign Up.

27 |
28 |
29 | ); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /code/client/components/public/reset-password.jsx: -------------------------------------------------------------------------------- 1 | ResetPassword = React.createClass({ 2 | componentDidMount() { 3 | Modules.client.resetPassword( { form: "#reset-password" } ); 4 | }, 5 | handleSubmit( event ) { 6 | event.preventDefault(); 7 | }, 8 | render() { 9 | return ( 10 |
11 |
12 |

Reset Password

13 |
14 |

To reset your password, enter a new one below. You will be logged in with your new password.

15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 | ); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /code/server/modules/generate-accounts.js: -------------------------------------------------------------------------------- 1 | let administrators = [ 2 | { 3 | name: { first: 'Admin', last: 'McAdmin' }, 4 | email: 'admin@admin.com', 5 | password: 'password' 6 | } 7 | ]; 8 | 9 | let generateAccounts = () => { 10 | let fakeUserCount = 5, 11 | usersExist = _checkIfAccountsExist( administrators.length + fakeUserCount ); 12 | 13 | if ( !usersExist ) { 14 | _createUsers( administrators ); 15 | _createUsers( _generateFakeUsers( fakeUserCount ) ); 16 | } 17 | }; 18 | 19 | let _checkIfAccountsExist = ( count ) => { 20 | let userCount = Meteor.users.find().count(); 21 | return userCount < count ? false : true; 22 | }; 23 | 24 | let _createUsers = ( users ) => { 25 | for ( let i = 0; i < users.length; i++ ) { 26 | let user = users[ i ], 27 | userExists = _checkIfUserExists( user.email ); 28 | 29 | if ( !userExists ) { 30 | _createUser( user ); 31 | } 32 | } 33 | }; 34 | 35 | let _checkIfUserExists = ( email ) => { 36 | return Meteor.users.findOne( { 'emails.address': email } ); 37 | }; 38 | 39 | let _createUser = ( user ) => { 40 | Accounts.createUser({ 41 | email: user.email, 42 | password: user.password, 43 | profile: { 44 | name: user.name 45 | } 46 | }); 47 | }; 48 | 49 | let _generateFakeUsers = ( count ) => { 50 | let users = []; 51 | 52 | for ( let i = 0; i < count; i++ ) { 53 | users.push({ 54 | name: { first: faker.name.firstName(), last: faker.name.lastName() }, 55 | email: faker.internet.email(), 56 | password: 'password' 57 | }); 58 | } 59 | 60 | return users; 61 | }; 62 | 63 | Modules.server.generateAccounts = generateAccounts; 64 | -------------------------------------------------------------------------------- /code/.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.2 2 | accounts-password@1.1.4 3 | aldeed:collection2@2.5.0 4 | aldeed:simple-schema@1.4.0 5 | audit-argument-checks@1.0.4 6 | autoupdate@1.2.4 7 | babel-compiler@5.8.24_1 8 | babel-runtime@0.1.4 9 | base64@1.0.4 10 | binary-heap@1.0.4 11 | blaze@2.1.3 12 | blaze-tools@1.0.4 13 | boilerplate-generator@1.0.4 14 | browser-policy@1.0.5 15 | browser-policy-common@1.0.4 16 | browser-policy-content@1.0.6 17 | browser-policy-framing@1.0.6 18 | caching-compiler@1.0.0 19 | caching-html-compiler@1.0.2 20 | callback-hook@1.0.4 21 | check@1.1.0 22 | chuangbo:cookie@1.1.0 23 | coffeescript@1.0.11 24 | cosmos:browserify@0.9.2 25 | ddp@1.2.2 26 | ddp-client@1.2.1 27 | ddp-common@1.2.2 28 | ddp-rate-limiter@1.0.0 29 | ddp-server@1.2.2 30 | deps@1.0.9 31 | diff-sequence@1.0.1 32 | digilord:faker@1.0.7 33 | ecmascript@0.1.6 34 | ecmascript-runtime@0.2.6 35 | ejson@1.0.7 36 | email@1.0.8 37 | fortawesome:fontawesome@4.5.0 38 | fourseven:scss@3.4.1 39 | geojson-utils@1.0.4 40 | hot-code-push@1.0.0 41 | html-tools@1.0.5 42 | htmljs@1.0.5 43 | http@1.1.1 44 | id-map@1.0.4 45 | jquery@1.11.4 46 | jsx@0.2.3 47 | kadira:flow-router@2.10.0 48 | kadira:react-layout@1.5.2 49 | livedata@1.0.15 50 | localstorage@1.0.5 51 | logging@1.0.8 52 | mdg:validation-error@0.1.0 53 | meteor@1.1.10 54 | meteor-base@1.0.1 55 | meteorhacks:async@1.0.0 56 | meteorhacks:fast-render@2.11.0 57 | meteorhacks:inject-data@1.4.1 58 | meteorhacks:npm@1.5.0 59 | meteorhacks:picker@1.0.3 60 | meteorhacks:ssr@2.2.0 61 | minifiers@1.1.7 62 | minimongo@1.0.10 63 | mongo@1.1.3 64 | mongo-id@1.0.1 65 | npm-bcrypt@0.7.8_2 66 | npm-container@1.2.0 67 | npm-mongo@1.4.39_1 68 | observe-sequence@1.0.7 69 | ordered-dict@1.0.4 70 | promise@0.5.1 71 | random@1.0.5 72 | rate-limit@1.0.0 73 | react@0.14.1_1 74 | react-meteor-data@0.2.3 75 | react-runtime@0.14.1_1 76 | react-runtime-dev@0.14.1 77 | react-runtime-prod@0.14.1 78 | reactive-dict@1.1.3 79 | reactive-var@1.0.6 80 | reload@1.1.4 81 | retry@1.0.4 82 | routepolicy@1.0.6 83 | service-configuration@1.0.5 84 | session@1.1.1 85 | sha@1.0.4 86 | spacebars@1.0.7 87 | spacebars-compiler@1.0.7 88 | srp@1.0.4 89 | standard-minifiers@1.0.2 90 | stevezhu:lodash@3.10.1 91 | templating@1.1.5 92 | templating-tools@1.0.0 93 | themeteorchef:bert@2.1.0 94 | themeteorchef:jquery-validation@1.14.0 95 | tracker@1.0.9 96 | twbs:bootstrap@3.3.6 97 | ui@1.0.8 98 | underscore@1.0.4 99 | url@1.0.5 100 | webapp@1.2.3 101 | webapp-hashing@1.0.5 102 | -------------------------------------------------------------------------------- /getting-started-with-react.md: -------------------------------------------------------------------------------- 1 | ### Getting Started 2 | For this recipe, we'll need to install a couple packages to help us out with getting React up and running. 3 | 4 |

Terminal

5 | 6 | ```bash 7 | meteor add react 8 | ``` 9 | This is the special sauce. When we install this, we'll get access to React, along with everything we need to make React work with our Meteor application. If you're curious, you can [see what this includes here](https://react-in-meteor.readthedocs.org/en/latest/#whats-in-the-box). 10 | 11 |

Terminal

12 | 13 | ```bash 14 | meteor add kadira:react-layout 15 | ``` 16 | We'll use this package to make it easy to render React components in our application. This will be used in conjunction with [Flow Router](https://github.com/kadirahq/flow-router) (the router included with [Base](https://themeteorchef.com/base), the starter kit used for this recipe). 17 | 18 |
19 |

Additional Packages

20 |

This recipe relies on several other packages that come as part of Base, the boilerplate kit used here on The Meteor Chef. The packages listed above are merely recipe-specific additions to the packages that are included by default in the kit. Make sure to reference the Packages Included list for Base to ensure you have fulfilled all of the dependencies.

21 |
22 | 23 | ### The goal 24 | Our goal with this recipe is to get a basic understanding of React, how it works, and some of the considerations we need to make when moving an application from Blaze (Meteor's default user interface library). To do this, we'll focus on a refactor of [Base](https://themeteorchef.com/base), the starter kit used for recipes here at The Meteor Chef. This will give us a small scope to work within, enough to aid in our comprehension of React but not so much that it's overwhelming. How considerate! 25 | 26 | The number one thing to keep in mind about React is that it is _only responsible for the view layer_. This means that anything not related to our templates or template logic does not need to be refactored. Our focus is on taking all of the Blaze-specific code in Base and converting it to React. That's it. Take a deep breath. React has been getting a lot of hype which inevitably leads to fear of being left out. Don't worry, as [it was suggested here](https://themeteorchef.com/blog/react-vs-blaze), React is simply another way to build our interfaces, not law (despite what Mark Zuckerberg and Co. may desire). 27 | 28 | #### JSX 29 | 30 | To get the job done, we'll be relying on a new syntax (and file format) introduced by React called `JSX`. Using this format, we can embed the HTML in our templates directly in our JavaScript. Wait..why?! This is a convention of React. It sounds scary at first, but the purpose is to keep all of the markup and JavaScript related to our components in one place. If you'd like to learn more about JSX before diving in, it's recommended that you [check out the React vs. Blaze guide here](https://themeteorchef.com/blog/react-vs-blaze/) where we take a closer look at the syntactic differences between the two libraries. 31 | 32 | Ready to de-Blaze Base and board Spaceship React? Suit up! 33 | 34 | ### Converting existing templates to React components 35 | Our first task is to simply refactor each of the major (we're going to leave out the `notFound` template in the article) Blaze-based templates in Base into React components. We'll go file-by-file, combining the existing HTML and JavaScript for each template into singular components. As we'll see, by the time we finish we'll end up repeating a lot so if you feel confident you understand the higher-level points, don't be afraid to skip ahead. Ready to rock? Let's start with the `login` template. 36 | 37 | #### Login 38 | What better place to start than the entry point to our application? To get started, we actually need to start by renaming our `/client/templates` directory to `/client/components`. Why? Well, we want to ensure that we're not confusing ourselves later. When we're working with React, the code we'll write will be referred to as a "component," so it makes sense to rename now. 39 | 40 | Next, inside of the `/client/components/public` folder, we'll want to create a new file called `login.jsx` and add the skeleton for our new component. 41 | 42 |

/client/components/public/login.jsx

43 | 44 | ```javascript 45 | Login = React.createClass({ 46 | render() { 47 | return ( 48 | // Our template markup will be moved here. 49 | ); 50 | } 51 | }); 52 | ``` 53 | A few things to point out. First, notice that when we create a React component in our application, we want to assign it to a _global_ variable. Why? This is so that we can see the component throughout our application. Because we're working with a limited number of components in this app, using a global isn't the _worst_ thing we can do. If you're working on a bigger app, though, it may be worth namespacing your components (e.g. `Components.Login`). Up to you. 54 | 55 | Next, notice that all component definitions start with a call to `React.createClass()` accepting an object with methods (and properties) defined on it. By default, the `render()` method we've defined here is the only method that our component _must_ have. Everything else is optional depending on the functionality of our component. 56 | 57 | Once we have this in place, we can open up `/client/components/login.html` (remember, we renamed this directory from `/templates` so try not to get confused) and copy the contents—excluding the `