├── .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 | Base Version
8 | v3.3.0
9 |
10 |
11 | Meteor Version
12 | v1.2.1
13 |
14 |
15 |
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 |
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 |
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 | Name
10 | Email Address
11 |
12 |
13 |
14 | {this.props.people.map( ( person, index ) => {
15 | return ;
16 | })}
17 |
18 |
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 |
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 |
19 |
20 |
21 |
22 | Toggle navigation
23 |
24 |
25 |
26 |
27 |
Base in React
28 |
29 | {this.navigationItems()}
30 |
31 |
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 |
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 |
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 |
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 |
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 |
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 |
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 `` tags and paste them _within_ the `return()` statement of our component's `render()` method. Once we've pasted, we'll need to make a few changes to the markup to make sure it's React-friendly. We're going to output the refactored version of this below and step through it. If you get stuck, make sure to [reference the source for the template](https://github.com/themeteorchef/base/blob/master/client/templates/public/login.html).
58 |
59 |
60 |
61 | ```javascript
62 | Login = React.createClass({
63 | render() {
64 | return (
65 |
66 |
67 |
Login
68 |
81 |
Don't have an account? Sign Up .
82 |
83 |
84 | );
85 | }
86 | });
87 | ```
88 | Okay? It looks the same? Almost! This is one of the first "gotchas" of React. Notice that some of our element's attributes have funky names and that some of our tags are ending with a `/` that we didn't need before. What?! Let's step through it.
89 |
90 | First, the easiest thing to point out is that instead of `class`, we're using `className`. Why? This is a React thing. As one of the React core-team members [pointed out here](https://www.quora.com/Why-do-I-have-to-use-className-instead-of-class-in-ReactJs-components-done-in-JSX/answer/Ben-Alpert) it's done as an aid to how they process code behind the scenes as well as padding for the future. The thing to keep in mind is that if we _do not_ change this, React will throw an error in our console and our classes will not be applied correctly. Okay. Not too big a deal.
91 |
92 | Next, look at our `` tags and their `for` attribute. This needs to be changed to `htmlFor`. Again, the same logic applies here. While we'll only see a few during our refactor, you can find [a full list of the attributes that React supports here](https://facebook.github.io/react/docs/tags-and-attributes.html). Okay we have those in place...anything else?
93 |
94 | On our ` ` tags, notice that we've added the trailing `/`. In our templates this wasn't required (we don't need these in HTML5), but in React _they are_ required. The purpose here is error avoidance. Without these, React still considers the ` ` tag open and fails to parse our code correctly. [Zuckerberg](https://youtu.be/xtdY6oH4rSU?t=8s)!
95 |
96 | Okay okay. Two more things. First, notice that all of our `{{pathFor}}` helpers have been replaced by regular routes. What gives? Well, obviously we don't have access to our template helpers so we need to come up with a solution. Here, we know that the paths we're calling to—`/recover-password` and `/signup`—don't have any dynamic data like parameters, so we can just past the regular path.
97 |
98 | Don't worry, later on we'll refactor our template helpers used for things like `pathFor` to be React-friendly. For now, though, we can leave these as plain relative URLs and they'll work fine. After we cover the helper refactor, you're welcome to come back and replace them. Whatever floats your boat!
99 |
100 | Okay, we've saved the biggest heart attack for last: events. Notice that on our `