Commit f0c912bf1f2a4157f8583e1922edfd5247346701

Authored by tmcdeveloper
1 parent d1f05315dd
Exists in master

add method tests and airbnb linting via eslint

1 # Meteor packages used by this project, one per line. 1 # Meteor packages used by this project, one per line.
2 # Check this file (and the other files in this directory) into your repository. 2 # Check this file (and the other files in this directory) into your repository.
3 # 3 #
4 # 'meteor add' and 'meteor remove' will edit this file for you, 4 # 'meteor add' and 'meteor remove' will edit this file for you,
5 # but you can also edit it by hand. 5 # but you can also edit it by hand.
6 6
7 meteor-base # Packages every Meteor app needs to have 7 meteor-base # Packages every Meteor app needs to have
8 mobile-experience # Packages for a great mobile UX 8 mobile-experience # Packages for a great mobile UX
9 mongo # The database Meteor supports right now 9 mongo # The database Meteor supports right now
10 reactive-var # Reactive variable for tracker 10 reactive-var # Reactive variable for tracker
11 session 11 session
12 jquery # Helpful client-side library 12 jquery # Helpful client-side library
13 tracker # Meteor's client-side reactive programming library 13 tracker # Meteor's client-side reactive programming library
14 14
15 standard-minifier-css # CSS minifier run for production mode 15 standard-minifier-css # CSS minifier run for production mode
16 standard-minifier-js # JS minifier run for production mode 16 standard-minifier-js # JS minifier run for production mode
17 es5-shim # ECMAScript 5 compatibility for older browsers. 17 es5-shim # ECMAScript 5 compatibility for older browsers.
18 ecmascript # Enable ECMAScript2015+ syntax in app code 18 ecmascript # Enable ECMAScript2015+ syntax in app code
19 19
20 accounts-password 20 accounts-password
21 accounts-base 21 accounts-base
22 check 22 check
23 audit-argument-checks 23 audit-argument-checks
24 browser-policy 24 browser-policy
25 25
26 fourseven:scss 26 fourseven:scss
27 aldeed:collection2 27 aldeed:collection2
28 # momentjs:moment NPM this 28 # momentjs:moment NPM this
29 alanning:roles 29 alanning:roles
30 react-meteor-data 30 react-meteor-data
31 themeteorchef:jquery-validation 31 themeteorchef:jquery-validation
32 themeteorchef:bert 32 themeteorchef:bert
33 static-html 33 static-html
34 xolvio:cleaner 34 xolvio:cleaner
35 practicalmeteor:mocha 35 practicalmeteor:mocha
36 xolvio:backdoor 36 xolvio:backdoor
37 xolvio:email-stub 37 xolvio:email-stub
38 mdg:validated-method 38 mdg:validated-method
39 dburles:factory
40 aldeed:simple-schema
39 41
1 accounts-base@1.2.5 1 accounts-base@1.2.5
2 accounts-password@1.1.7 2 accounts-password@1.1.7
3 alanning:roles@1.2.15 3 alanning:roles@1.2.15
4 aldeed:collection2@2.9.1 4 aldeed:collection2@2.9.1
5 aldeed:collection2-core@1.1.1 5 aldeed:collection2-core@1.1.1
6 aldeed:schema-deny@1.0.1 6 aldeed:schema-deny@1.0.1
7 aldeed:schema-index@1.0.1 7 aldeed:schema-index@1.0.1
8 aldeed:simple-schema@1.5.3 8 aldeed:simple-schema@1.5.3
9 allow-deny@1.0.3 9 allow-deny@1.0.3
10 audit-argument-checks@1.0.6 10 audit-argument-checks@1.0.6
11 autoupdate@1.2.7 11 autoupdate@1.2.7
12 babel-compiler@6.6.1 12 babel-compiler@6.6.1
13 babel-runtime@0.1.7 13 babel-runtime@0.1.7
14 base64@1.0.7 14 base64@1.0.7
15 binary-heap@1.0.7 15 binary-heap@1.0.7
16 blaze@2.1.6 16 blaze@2.1.6
17 blaze-tools@1.0.7 17 blaze-tools@1.0.7
18 boilerplate-generator@1.0.7 18 boilerplate-generator@1.0.7
19 browser-policy@1.0.8 19 browser-policy@1.0.8
20 browser-policy-common@1.0.8 20 browser-policy-common@1.0.8
21 browser-policy-content@1.0.9 21 browser-policy-content@1.0.9
22 browser-policy-framing@1.0.9 22 browser-policy-framing@1.0.9
23 caching-compiler@1.0.3 23 caching-compiler@1.0.3
24 caching-html-compiler@1.0.5 24 caching-html-compiler@1.0.5
25 callback-hook@1.0.7 25 callback-hook@1.0.7
26 check@1.1.3 26 check@1.1.3
27 coffeescript@1.0.16 27 coffeescript@1.0.16
28 dburles:factory@0.4.2
28 ddp@1.2.4 29 ddp@1.2.4
29 ddp-client@1.2.4 30 ddp-client@1.2.4
30 ddp-common@1.2.4 31 ddp-common@1.2.4
31 ddp-rate-limiter@1.0.3 32 ddp-rate-limiter@1.0.3
32 ddp-server@1.2.5 33 ddp-server@1.2.5
33 deps@1.0.11 34 deps@1.0.11
34 diff-sequence@1.0.4 35 diff-sequence@1.0.4
35 ecmascript@0.4.2 36 ecmascript@0.4.2
36 ecmascript-runtime@0.2.9 37 ecmascript-runtime@0.2.9
37 ejson@1.0.10 38 ejson@1.0.10
38 email@1.0.11 39 email@1.0.11
39 es5-shim@4.5.9 40 es5-shim@4.5.9
40 fastclick@1.0.10 41 fastclick@1.0.10
41 fortawesome:fontawesome@4.4.0_1 42 fortawesome:fontawesome@4.5.0
42 fourseven:scss@3.4.1 43 fourseven:scss@3.4.3
43 geojson-utils@1.0.7 44 geojson-utils@1.0.7
44 hot-code-push@1.0.3 45 hot-code-push@1.0.3
45 html-tools@1.0.8 46 html-tools@1.0.8
46 htmljs@1.0.8 47 htmljs@1.0.8
47 http@1.1.4 48 http@1.1.4
48 id-map@1.0.6 49 id-map@1.0.6
49 jquery@1.11.7 50 jquery@1.11.7
50 launch-screen@1.0.10 51 launch-screen@1.0.10
51 livedata@1.0.17 52 livedata@1.0.17
52 localstorage@1.0.8 53 localstorage@1.0.8
53 logging@1.0.11 54 logging@1.0.11
54 mdg:validated-method@1.0.2 55 mdg:validated-method@1.1.0
55 mdg:validation-error@0.5.1 56 mdg:validation-error@0.5.1
56 meteor@1.1.13 57 meteor@1.1.13
57 meteor-base@1.0.3 58 meteor-base@1.0.3
58 minifier-css@1.1.10 59 minifier-css@1.1.10
59 minifier-js@1.1.10 60 minifier-js@1.1.10
60 minimongo@1.0.13 61 minimongo@1.0.13
61 mobile-experience@1.0.3 62 mobile-experience@1.0.3
62 mobile-status-bar@1.0.11 63 mobile-status-bar@1.0.11
63 modules@0.5.2 64 modules@0.5.2
64 modules-runtime@0.6.2 65 modules-runtime@0.6.2
65 mongo@1.1.6 66 mongo@1.1.6
66 mongo-id@1.0.3 67 mongo-id@1.0.3
67 npm-bcrypt@0.7.8_2 68 npm-bcrypt@0.7.8_2
68 npm-mongo@1.4.42 69 npm-mongo@1.4.42
69 observe-sequence@1.0.10 70 observe-sequence@1.0.10
70 ordered-dict@1.0.6 71 ordered-dict@1.0.6
71 practicalmeteor:chai@2.1.0_1 72 practicalmeteor:chai@2.1.0_1
72 practicalmeteor:loglevel@1.2.0_2 73 practicalmeteor:loglevel@1.2.0_2
73 practicalmeteor:mocha@2.1.0_8 74 practicalmeteor:mocha@2.4.5_1
74 practicalmeteor:mocha-core@0.1.4 75 practicalmeteor:mocha-core@0.1.4
75 practicalmeteor:sinon@1.14.1_2 76 practicalmeteor:sinon@1.14.1_2
76 promise@0.6.6 77 promise@0.6.6
77 raix:eventemitter@0.1.3 78 raix:eventemitter@0.1.3
78 random@1.0.8 79 random@1.0.8
79 rate-limit@1.0.3 80 rate-limit@1.0.3
80 react-meteor-data@0.2.7 81 react-meteor-data@0.2.9
81 reactive-dict@1.1.6 82 reactive-dict@1.1.6
82 reactive-var@1.0.8 83 reactive-var@1.0.8
83 reload@1.1.7 84 reload@1.1.7
84 retry@1.0.6 85 retry@1.0.6
85 routepolicy@1.0.9 86 routepolicy@1.0.9
86 service-configuration@1.0.8 87 service-configuration@1.0.8
87 session@1.1.4 88 session@1.1.4
88 sha@1.0.6 89 sha@1.0.6
89 spacebars@1.0.10 90 spacebars@1.0.10
90 spacebars-compiler@1.0.10 91 spacebars-compiler@1.0.10
91 srp@1.0.7 92 srp@1.0.7
92 standard-minifier-css@1.0.5 93 standard-minifier-css@1.0.5
93 standard-minifier-js@1.0.5 94 standard-minifier-js@1.0.5
94 static-html@1.0.6 95 static-html@1.0.6
95 templating@1.1.8 96 templating@1.1.8
96 templating-tools@1.0.3 97 templating-tools@1.0.3
97 themeteorchef:bert@2.1.0 98 themeteorchef:bert@2.1.0
98 themeteorchef:jquery-validation@1.14.0 99 themeteorchef:jquery-validation@1.14.0
99 tmeasday:check-npm-versions@0.2.0 100 tmeasday:check-npm-versions@0.2.0
100 tmeasday:test-reporter-helpers@0.2.1 101 tmeasday:test-reporter-helpers@0.2.1
101 tracker@1.0.12 102 tracker@1.0.12
102 ui@1.0.10 103 ui@1.0.10
103 underscore@1.0.7 104 underscore@1.0.7
104 url@1.0.8 105 url@1.0.8
105 webapp@1.2.7 106 webapp@1.2.7
106 webapp-hashing@1.0.8 107 webapp-hashing@1.0.8
107 xolvio:backdoor@0.1.2 108 xolvio:backdoor@0.2.0
108 xolvio:cleaner@0.2.0 109 xolvio:cleaner@0.3.0
109 xolvio:email-stub@0.2.0 110 xolvio:email-stub@0.2.0
110 111
imports/api/documents/documents.js
1 import { Mongo } from 'meteor/mongo'; 1 import { Mongo } from 'meteor/mongo';
2 import faker from 'faker';
2 3
3 export const Documents = new Mongo.Collection( 'Documents' ); 4 export const Documents = new Mongo.Collection('Documents');
5
6 Documents.schema = new SimpleSchema({
7 title: {
8 type: String,
9 label: 'The title of the document.',
10 },
11 });
12
13 Documents.attachSchema(Documents.schema);
14
15 Factory.define('document', Documents, {
16 title: () => faker.hacker.phrase(),
17 });
4 18
imports/api/documents/documents.tests.js
File was created 1 /* eslint-env mocha */
2 /* eslint-disable func-names, prefer-arrow-callback */
3
4 import { chai } from 'meteor/practicalmeteor:chai';
5 import { Documents } from './documents.js';
6
7 const { assert } = chai;
8
9 describe('Documents collection', function () {
10 it('registers the collection with Mongo properly', function () {
11 assert.equal(typeof Documents, 'object');
12 });
13 });
14
imports/api/documents/methods.js
1 import { Documents } from './documents'; 1 import { Documents } from './documents';
2 2
3 export const insertDocument = new ValidatedMethod({ 3 export const insertDocument = new ValidatedMethod({
4 name: 'documents.insert', 4 name: 'documents.insert',
5 validate: new SimpleSchema({ 5 validate: new SimpleSchema({
6 title: { type: String } 6 title: { type: String },
7 }).validator(), 7 }).validator(),
8 run( document ) { 8 run(document) {
9 Documents.insert( document ); 9 Documents.insert(document);
10 } 10 },
11 }); 11 });
12 12
13 export const updateDocument = new ValidatedMethod({ 13 export const updateDocument = new ValidatedMethod({
14 name: 'documents.update', 14 name: 'documents.update',
15 validate: new SimpleSchema({ 15 validate: new SimpleSchema({
16 _id: { type: String }, 16 _id: { type: String },
17 'update.title': { type: String, optional: true } 17 'update.title': { type: String, optional: true },
18 }).validator(), 18 }).validator(),
19 run( { _id, update } ) { 19 run({ _id, update }) {
20 Documents.update( _id, { $set: update } ); 20 Documents.update(_id, { $set: update });
21 } 21 },
22 }); 22 });
23 23
24 export const removeDocument = new ValidatedMethod({ 24 export const removeDocument = new ValidatedMethod({
25 name: 'documents.remove', 25 name: 'documents.remove',
26 validate: new SimpleSchema({ 26 validate: new SimpleSchema({
27 _id: { type: String } 27 _id: { type: String },
28 }).validator(), 28 }).validator(),
29 run( { _id } ) { 29 run({ _id }) {
30 Documents.remove( _id ); 30 Documents.remove(_id);
31 } 31 },
32 }); 32 });
33 33
imports/api/documents/methods.tests.js
File was created 1 /* eslint-env mocha */
2 /* eslint-disable func-names, prefer-arrow-callback */
3
4 import { assert } from 'meteor/practicalmeteor:chai';
5 import { resetDatabase } from 'meteor/xolvio:cleaner';
6 import { Documents } from './documents.js';
7 import { insertDocument, updateDocument, removeDocument } from './methods.js';
8
9 describe('Documents methods', function () {
10 beforeEach(function () {
11 if (Meteor.isServer) {
12 resetDatabase();
13 }
14 });
15
16 it('inserts a document into the Documents collection', function () {
17 insertDocument.call({ title: 'You can\'t arrest me, I\'m the Cake Boss!' });
18 const getDocument = Documents.findOne({ title: 'You can\'t arrest me, I\'m the Cake Boss!' });
19 assert.equal(getDocument.title, 'You can\'t arrest me, I\'m the Cake Boss!');
20 });
21
22 it('updates a document in the Documents collection', function () {
23 const { _id } = Factory.create('document');
24
25 updateDocument.call({
26 _id,
27 update: {
28 title: 'You can\'t arrest me, I\'m the Cake Boss!',
29 },
30 });
31
32 const getDocument = Documents.findOne(_id);
33 assert.equal(getDocument.title, 'You can\'t arrest me, I\'m the Cake Boss!');
34 });
35
36 it('removes a document from the Documents collection', function () {
37 const { _id } = Factory.create('document');
38 removeDocument.call({ _id });
39 const getDocument = Documents.findOne(_id);
40 assert.equal(getDocument, undefined);
41 });
42 });
43
imports/api/documents/server/publications.js
1 import { Documents } from '../documents'; 1 import { Documents } from '../documents';
2 2
3 Meteor.publish( 'documents', function() { 3 Meteor.publish('documents', () => Documents.find());
4 return Documents.find();
5 });
6 4
imports/modules/get-input-value.js
1 export const getInputValue = ( component, ref, nested ) => { 1 export const getInputValue = (component, ref, nested) => {
2 let element = component.refs[ ref ]; 2 const element = component.refs[ref];
3 return nested ? element.refs.input.value : element.value; 3 return nested ? element.refs.input.value : element.value;
4 } 4 };
5 5
imports/modules/login.js
1 import { browserHistory } from 'react-router'; 1 import { browserHistory } from 'react-router';
2 2
3 let component; 3 let component;
4 4
5 const _handleLogin = () => { 5 const _handleLogin = () => {
6 // <Input /> component value is accessed via nested refs. 6 // <Input /> component value is accessed via nested refs.
7 const email = component.refs.emailAddress.refs.input.value, 7 const email = component.refs.emailAddress.refs.input.value;
8 password = component.refs.password.value; 8 const password = component.refs.password.value;
9 9
10 Meteor.loginWithPassword( email, password, ( error ) => { 10 Meteor.loginWithPassword(email, password, (error) => {
11 if ( error ) { 11 if (error) {
12 Bert.alert( error.reason, 'warning' ); 12 Bert.alert(error.reason, 'warning');
13 } else { 13 } else {
14 browserHistory.push( '/' ); 14 browserHistory.push('/');
15 Bert.alert( 'Logged in!', 'success' ); 15 Bert.alert('Logged in!', 'success');
16 } 16 }
17 }); 17 });
18 }; 18 };
19 19
20 const _validate = () => { 20 const _validate = () => {
21 $( component.refs.login ).validate({ 21 $(component.refs.login).validate({
22 rules: { 22 rules: {
23 emailAddress: { 23 emailAddress: {
24 required: true, 24 required: true,
25 email: true 25 email: true,
26 }, 26 },
27 password: { 27 password: {
28 required: true 28 required: true,
29 } 29 },
30 }, 30 },
31 messages: { 31 messages: {
32 emailAddress: { 32 emailAddress: {
33 required: 'Need an email address here.', 33 required: 'Need an email address here.',
34 email: 'Is this email address legit?' 34 email: 'Is this email address legit?',
35 }, 35 },
36 password: { 36 password: {
37 required: 'Need a password here.' 37 required: 'Need a password here.',
38 } 38 },
39 }, 39 },
40 submitHandler() { _handleLogin(); } 40 submitHandler() { _handleLogin(); },
41 }); 41 });
42 }; 42 };
43 43
44 export const handleLogin = ( options ) => { 44 export const handleLogin = (options) => {
45 component = options.component; 45 component = options.component;
46 _validate(); 46 _validate();
47 }; 47 };
48 48
imports/modules/recover-password.js
1 import { getInputValue } from './get-input-value'; 1 import { getInputValue } from './get-input-value';
2 2
3 let component; 3 let component;
4 4
5 const _handleRecovery = () => { 5 const _handleRecovery = () => {
6 Accounts.forgotPassword({ 6 Accounts.forgotPassword({
7 email: getInputValue( component, 'emailAddress', true ) 7 email: getInputValue(component, 'emailAddress', true),
8 }, ( error ) => { 8 }, (error) => {
9 if ( error ) { 9 if (error) {
10 Bert.alert( error.reason, 'warning' ); 10 Bert.alert(error.reason, 'warning');
11 } else { 11 } else {
12 Bert.alert( 'Check your inbox for a reset link!', 'success' ); 12 Bert.alert('Check your inbox for a reset link!', 'success');
13 } 13 }
14 }); 14 });
15 }; 15 };
16 16
17 const _validate = () => { 17 const _validate = () => {
18 $( component.refs.recoverPassword ).validate({ 18 $(component.refs.recoverPassword).validate({
19 rules: { 19 rules: {
20 emailAddress: { 20 emailAddress: {
21 required: true, 21 required: true,
22 email: true 22 email: true,
23 } 23 },
24 }, 24 },
25 messages: { 25 messages: {
26 emailAddress: { 26 emailAddress: {
27 required: 'Need an email address here.', 27 required: 'Need an email address here.',
28 email: 'Is this email address legit?' 28 email: 'Is this email address legit?',
29 } 29 },
30 }, 30 },
31 submitHandler() { _handleRecovery(); } 31 submitHandler() { _handleRecovery(); },
32 }); 32 });
33 }; 33 };
34 34
35 export const handleRecoverPassword = ( options ) => { 35 export const handleRecoverPassword = (options) => {
36 component = options.component; 36 component = options.component;
37 _validate(); 37 _validate();
38 }; 38 };
39 39
imports/modules/reset-password.js
1 import { getInputValue } from './get-input-value'; 1 import { getInputValue } from './get-input-value';
2 import { browserHistory } from 'react-router'; 2 import { browserHistory } from 'react-router';
3 3
4 let component, 4 let component;
5 token; 5 let token;
6 6
7 const _handleReset = () => { 7 const _handleReset = () => {
8 const password = getInputValue( component, 'newPassword', true ); 8 const password = getInputValue(component, 'newPassword', true);
9 Accounts.resetPassword( token, password, ( error ) => { 9 Accounts.resetPassword(token, password, (error) => {
10 if ( error ) { 10 if (error) {
11 Bert.alert( error.reason, 'danger' ); 11 Bert.alert(error.reason, 'danger');
12 } else { 12 } else {
13 browserHistory.push( '/' ); 13 browserHistory.push('/');
14 Bert.alert( 'Password reset!', 'success' ); 14 Bert.alert('Password reset!', 'success');
15 } 15 }
16 }); 16 });
17 }; 17 };
18 18
19 const _validate = () => { 19 const _validate = () => {
20 $( component.refs.resetPassword ).validate({ 20 $(component.refs.resetPassword).validate({
21 rules: { 21 rules: {
22 newPassword: { 22 newPassword: {
23 required: true, 23 required: true,
24 minlength: 6 24 minlength: 6,
25 }, 25 },
26 repeatNewPassword: { 26 repeatNewPassword: {
27 required: true, 27 required: true,
28 minlength: 6, 28 minlength: 6,
29 equalTo: '[name="newPassword"]' 29 equalTo: '[name="newPassword"]',
30 } 30 },
31 }, 31 },
32 messages: { 32 messages: {
33 newPassword: { 33 newPassword: {
34 required: "Enter a new password, please.", 34 required: 'Enter a new password, please.',
35 minlength: "Use at least six characters, please." 35 minlength: 'Use at least six characters, please.',
36 }, 36 },
37 repeatNewPassword: { 37 repeatNewPassword: {
38 required: "Repeat your new password, please.", 38 required: 'Repeat your new password, please.',
39 equalTo: "Hmm, your passwords don't match. Try again?" 39 equalTo: 'Hmm, your passwords don\'t match. Try again?',
40 } 40 },
41 }, 41 },
42 submitHandler() { _handleReset(); } 42 submitHandler() { _handleReset(); },
43 }); 43 });
44 }; 44 };
45 45
46 export const handleResetPassword = ( options ) => { 46 export const handleResetPassword = (options) => {
47 component = options.component; 47 component = options.component;
48 token = options.token; 48 token = options.token;
49 _validate(); 49 _validate();
50 }; 50 };
51 51
imports/modules/signup.js
1 import { browserHistory } from 'react-router'; 1 import { browserHistory } from 'react-router';
2 import { getInputValue } from './get-input-value'; 2 import { getInputValue } from './get-input-value';
3 3
4 let component; 4 let component;
5 5
6 const _getUserData = () => { 6 const _getUserData = () => ({
7 return { 7 email: getInputValue(component, 'emailAddress', true),
8 email: getInputValue( component, 'emailAddress', true ), 8 password: getInputValue(component, 'password', true),
9 password: getInputValue( component, 'password', true ), 9 profile: {
10 profile: { 10 name: {
11 name: { 11 first: getInputValue(component, 'firstName', true),
12 first: getInputValue( component, 'firstName', true ), 12 last: getInputValue(component, 'lastName', true),
13 last: getInputValue( component, 'lastName', true ) 13 },
14 } 14 },
15 } 15 });
16 };
17 };
18 16
19 const _handleSignup = () => { 17 const _handleSignup = () => {
20 const user = _getUserData(); 18 const user = _getUserData();
21 19
22 Accounts.createUser( user, ( error ) => { 20 Accounts.createUser(user, (error) => {
23 if ( error ) { 21 if (error) {
24 Bert.alert( error.reason, 'danger' ); 22 Bert.alert(error.reason, 'danger');
25 } else { 23 } else {
26 browserHistory.push( '/' ); 24 browserHistory.push('/');
27 Bert.alert( 'Welcome!', 'success' ); 25 Bert.alert('Welcome!', 'success');
28 } 26 }
29 }); 27 });
30 }; 28 };
31 29
32 const _validate = () => { 30 const _validate = () => {
33 $( component.refs.signup ).validate({ 31 $(component.refs.signup).validate({
34 rules: { 32 rules: {
35 firstName: { 33 firstName: {
36 required: true 34 required: true,
37 }, 35 },
38 lastName: { 36 lastName: {
39 required: true 37 required: true,
40 }, 38 },
41 emailAddress: { 39 emailAddress: {
42 required: true, 40 required: true,
43 email: true 41 email: true,
44 }, 42 },
45 password: { 43 password: {
46 required: true, 44 required: true,
47 minlength: 6 45 minlength: 6,
48 } 46 },
49 }, 47 },
50 messages: { 48 messages: {
51 firstName: { 49 firstName: {
52 required: 'First name?' 50 required: 'First name?',
53 }, 51 },
54 lastName: { 52 lastName: {
55 required: 'Last name?' 53 required: 'Last name?',
56 }, 54 },
57 emailAddress: { 55 emailAddress: {
58 required: 'Need an email address here.', 56 required: 'Need an email address here.',
59 email: 'Is this email address legit?' 57 email: 'Is this email address legit?',
60 }, 58 },
61 password: { 59 password: {
62 required: 'Need a password here.', 60 required: 'Need a password here.',
63 minlength: 'Use at least six characters, please.' 61 minlength: 'Use at least six characters, please.',
64 } 62 },
65 }, 63 },
66 submitHandler() { _handleSignup(); } 64 submitHandler() { _handleSignup(); },
67 }); 65 });
68 }; 66 };
69 67
70 export const handleSignup = ( options ) => { 68 export const handleSignup = (options) => {
71 component = options.component; 69 component = options.component;
72 _validate(); 70 _validate();
73 }; 71 };
74 72
imports/startup/client/index.js
1 import './routes.jsx'; 1 import './routes.js';
2 2
3 Bert.defaults.style = 'growl-top-right'; 3 Bert.defaults.style = 'growl-top-right';
4 4
imports/startup/client/routes.js
File was created 1 import React from 'react';
2 import { render } from 'react-dom';
3 import { Router, Route, Redirect, IndexRoute, browserHistory } from 'react-router';
4
5 import { App } from '../../ui/layouts/app';
6 import { Documents } from '../../ui/pages/documents';
7 import { Index } from '../../ui/pages/index';
8 import { Login } from '../../ui/pages/login';
9 import { NotFound } from '../../ui/pages/not-found';
10 import { RecoverPassword } from '../../ui/pages/recover-password';
11 import { ResetPassword } from '../../ui/pages/reset-password';
12 import { Signup } from '../../ui/pages/signup';
13
14 const requireAuth = (nextState, replace) => {
15 if (!Meteor.loggingIn() && !Meteor.user()) {
16 replace({
17 pathname: '/login',
18 state: { nextPathName: nextState.location.pathname },
19 });
20 }
21 };
22
23 Meteor.startup(() => {
24 render(
25 <Router history={ browserHistory }>
26 <Route path="/" component={ App }>
27 <IndexRoute name="index" component={ Index } onEnter={ requireAuth } />
28 <Route name="documents" path="/documents" component={ Documents } onEnter={ requireAuth } />
29 <Route name="login" path="/login" component={ Login } />
30 <Route name="recover-password" path="/recover-password" component={ RecoverPassword } />
31 <Route name="reset-password" path="/reset-password/:token" component={ ResetPassword } />
32 <Route name="signup" path="/signup" component={ Signup } />
33 <Route path="*" component={ NotFound } />
34 </Route>
35 </Router>,
36 document.getElementById('react-root')
37 );
38 });
39
imports/startup/client/routes.jsx
1 import React from 'react'; File was deleted
2 import { render } from 'react-dom';
3 import { Router, Route, Redirect, IndexRoute, browserHistory } from 'react-router';
4
5 import { App } from '../../ui/layouts/app';
6 import { Documents } from '../../ui/pages/documents';
7 import { Index } from '../../ui/pages/index';
8 import { Login } from '../../ui/pages/login';
9 import { NotFound } from '../../ui/pages/not-found';
10 import { RecoverPassword } from '../../ui/pages/recover-password';
11 import { ResetPassword } from '../../ui/pages/reset-password';
12 import { Signup } from '../../ui/pages/signup';
13
14 const requireAuth = ( nextState, replace ) => {
15 if ( !Meteor.loggingIn() && !Meteor.user() ) {
16 replace({
17 pathname: '/login',
18 state: { nextPathName: nextState.location.pathname }
19 });
20 }
21 };
22
23 Meteor.startup( () => {
24 render(
25 <Router history={ browserHistory }>
26 <Route path="/" component={ App }>
27 <IndexRoute name="index" component={ Index } onEnter={ requireAuth } />
28 <Route name="documents" path="/documents" component={ Documents } onEnter={ requireAuth } />
29 <Route name="login" path="/login" component={ Login } />
30 <Route name="recover-password" path="/recover-password" component={ RecoverPassword } />
31 <Route name="reset-password" path="/reset-password/:token" component={ ResetPassword } />
32 <Route name="signup" path="/signup" component={ Signup } />
33 <Route path="*" component={ NotFound } />
34 </Route>
35 </Router>,
36 document.getElementById( 'react-root' )
37 );
38 });
39 1 import React from 'react';
imports/startup/server/accounts/email-templates.js
1 const name = 'Application Name', 1 const name = 'Application Name';
2 email = '<support@application.com>', 2 const email = '<support@application.com>';
3 from = `${ name } ${ email }`, 3 const from = `${name} ${email}`;
4 emailTemplates = Accounts.emailTemplates; 4 const emailTemplates = Accounts.emailTemplates;
5 5
6 emailTemplates.siteName = name; 6 emailTemplates.siteName = name;
7 emailTemplates.from = from; 7 emailTemplates.from = from;
8 8
9 emailTemplates.resetPassword = { 9 emailTemplates.resetPassword = {
10 subject() { 10 subject() {
11 return `[${ name }] Reset Your Password`; 11 return `[${name}] Reset Your Password`;
12 }, 12 },
13 text( user, url ) { 13 text(user, url) {
14 let userEmail = user.emails[ 0 ].address, 14 const userEmail = user.emails[0].address;
15 urlWithoutHash = url.replace( '#/', '' ); 15 const urlWithoutHash = url.replace('#/', '');
16 16
17 return `A password reset has been requested for the account related to this address (${ userEmail }). 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: ${ email }.`; 17 return `A password reset has been requested for the account related to this
18 } 18 address (${userEmail}). To reset the password, visit the following link:
19 \n\n${urlWithoutHash}\n\n If you did not request this reset, please ignore
20 this email. If you feel something is wrong, please contact our support team:
21 ${email}.`;
22 },
19 }; 23 };
20 24
imports/startup/server/fixtures.js
1 const users = [{ 1 const users = [{
2 email: 'admin@admin.com', 2 email: 'admin@admin.com',
3 password: 'password', 3 password: 'password',
4 profile: { 4 profile: {
5 name: { first: 'Carl', last: 'Winslow' } 5 name: { first: 'Carl', last: 'Winslow' },
6 } 6 },
7 }]; 7 }];
8 8
9 users.forEach( ( { email, password, profile } ) => { 9 users.forEach(({ email, password, profile }) => {
10 const userExists = Meteor.users.findOne( { 'emails.address': email } ); 10 const userExists = Meteor.users.findOne({ 'emails.address': email });
11 11
12 if ( !userExists ) { 12 if (!userExists) {
13 Accounts.createUser({ 13 Accounts.createUser({ email, password, profile });
14 email: email,
15 password: password,
16 profile: profile
17 });
18 } 14 }
19 }); 15 });
20 16
imports/ui/components/add-document.js
1 import React from 'react'; 1 import React from 'react';
2 import { Row, Col, ListGroup, ListGroupItem, Input, Alert } from 'react-bootstrap'; 2 import { Row, Col, ListGroup, ListGroupItem, Input, Alert } from 'react-bootstrap';
3 import { insertDocument } from '../../api/documents/methods.js'; 3 import { insertDocument } from '../../api/documents/methods.js';
4 4
5 const handleInsertDocument = ( event ) => { 5 const handleInsertDocument = (event) => {
6 const target = event.target, 6 const target = event.target;
7 title = target.value.trim(); 7 const title = target.value.trim();
8 8
9 if ( title !== '' && event.keyCode === 13 ) { 9 if (title !== '' && event.keyCode === 13) {
10 insertDocument.call({ 10 insertDocument.call({
11 title: title 11 title,
12 }, ( error, response ) => { 12 }, (error) => {
13 if ( error ) { 13 if (error) {
14 Bert.alert( error.reason, 'danger' ); 14 Bert.alert(error.reason, 'danger');
15 } else { 15 } else {
16 target.value = ''; 16 target.value = '';
17 Bert.alert( 'Document added!', 'success' ); 17 Bert.alert('Document added!', 'success');
18 } 18 }
19 }); 19 });
20 } 20 }
21 }; 21 };
22 22
23 export const AddDocument = () => ( 23 export const AddDocument = () => (
24 <Input 24 <Input
25 type="text" 25 type="text"
26 onKeyUp={ handleInsertDocument } 26 onKeyUp={ handleInsertDocument }
27 placeholder="Type a document title and press enter..." 27 placeholder="Type a document title and press enter..."
28 /> 28 />
29 ) 29 );
30 30
imports/ui/components/app-navigation.js
1 import React from 'react'; 1 import React from 'react';
2 import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap'; 2 import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap';
3 import { Link } from 'react-router'; 3 import { Link } from 'react-router';
4 import { PublicNavigation } from './public-navigation'; 4 import { PublicNavigation } from './public-navigation';
5 import { AuthenticatedNavigation } from './authenticated-navigation'; 5 import { AuthenticatedNavigation } from './authenticated-navigation';
6 6
7 export class AppNavigation extends React.Component { 7 export class AppNavigation extends React.Component {
8 renderNavigation( hasUser ) { 8 renderNavigation(hasUser) {
9 return hasUser ? <AuthenticatedNavigation /> : <PublicNavigation />; 9 return hasUser ? <AuthenticatedNavigation /> : <PublicNavigation />;
10 } 10 }
11 11
12 render() { 12 render() {
13 return <Navbar> 13 return <Navbar>
14 <Navbar.Header> 14 <Navbar.Header>
15 <Navbar.Brand> 15 <Navbar.Brand>
16 <Link to="/">Application Name</Link> 16 <Link to="/">Application Name</Link>
17 </Navbar.Brand> 17 </Navbar.Brand>
18 <Navbar.Toggle /> 18 <Navbar.Toggle />
19 </Navbar.Header> 19 </Navbar.Header>
20 <Navbar.Collapse> 20 <Navbar.Collapse>
21 { this.renderNavigation( this.props.hasUser ) } 21 { this.renderNavigation(this.props.hasUser) }
22 </Navbar.Collapse> 22 </Navbar.Collapse>
23 </Navbar>; 23 </Navbar>;
24 } 24 }
25 } 25 }
26 26
imports/ui/components/authenticated-navigation.js
1 import React from 'react'; 1 import React from 'react';
2 import { browserHistory } from 'react-router'; 2 import { browserHistory } from 'react-router';
3 import { IndexLinkContainer, LinkContainer } from 'react-router-bootstrap'; 3 import { IndexLinkContainer, LinkContainer } from 'react-router-bootstrap';
4 import { Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap'; 4 import { Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap';
5 5
6 const handleLogout = () => { 6 const handleLogout = () => Meteor.logout(() => browserHistory.push('/login'));
7 return Meteor.logout( () => browserHistory.push( '/login' ) );
8 };
9 7
10 const userName = () => { 8 const userName = () => {
11 const user = Meteor.user(); 9 const user = Meteor.user();
12 if ( user ) { 10 const name = user && user.profile ? user.profile.name : '';
13 const name = user && user.profile ? user.profile.name : ''; 11 return user ? `${name.first} ${name.last}` : '';
14 return `${ name.first } ${ name.last }`;
15 }
16 }; 12 };
17 13
18 export const AuthenticatedNavigation = () => ( 14 export const AuthenticatedNavigation = () => (
19 <div> 15 <div>
20 <Nav> 16 <Nav>
21 <IndexLinkContainer to="/"> 17 <IndexLinkContainer to="/">
22 <NavItem eventKey={ 1 } href="/">Index</NavItem> 18 <NavItem eventKey={ 1 } href="/">Index</NavItem>
23 </IndexLinkContainer> 19 </IndexLinkContainer>
24 <LinkContainer to="/documents"> 20 <LinkContainer to="/documents">
25 <NavItem eventKey={ 2 } href="/documents">Documents</NavItem> 21 <NavItem eventKey={ 2 } href="/documents">Documents</NavItem>
26 </LinkContainer> 22 </LinkContainer>
27 </Nav> 23 </Nav>
28 <Nav pullRight> 24 <Nav pullRight>
29 <NavDropdown eventKey={ 3 } title={ userName() } id="basic-nav-dropdown"> 25 <NavDropdown eventKey={ 3 } title={ userName() } id="basic-nav-dropdown">
30 <MenuItem eventKey={ 3.1 } onClick={ handleLogout }>Logout</MenuItem> 26 <MenuItem eventKey={ 3.1 } onClick={ handleLogout }>Logout</MenuItem>
31 </NavDropdown> 27 </NavDropdown>
32 </Nav> 28 </Nav>
33 </div> 29 </div>
34 ) 30 );
35 31
imports/ui/components/document.js
File was created 1 import React from 'react';
2 import { Row, Col, ListGroupItem, Input, Button } from 'react-bootstrap';
3 import { updateDocument, removeDocument } from '../../api/documents/methods.js';
4
5 const handleUpdateDocument = (documentId, event) => {
6 const title = event.target.value.trim();
7 if (title !== '' && event.keyCode === 13) {
8 updateDocument.call({
9 _id: documentId,
10 update: { title },
11 }, (error) => {
12 if (error) {
13 Bert.alert(error.reason, 'danger');
14 } else {
15 Bert.alert('Document updated!', 'success');
16 }
17 });
18 }
19 };
20
21 const handleRemoveDocument = (documentId, event) => {
22 event.preventDefault();
23 if (confirm('Are you sure? This is permanent.')) {
24 removeDocument.call({
25 _id: documentId,
26 }, (error) => {
27 if (error) {
28 Bert.alert(error.reason, 'danger');
29 } else {
30 Bert.alert('Document removed!', 'success');
31 }
32 });
33 }
34 };
35
36 export const Document = ({ document }) => (
37 <ListGroupItem key={ document._id }>
38 <Row>
39 <Col xs={ 8 } sm={ 10 }>
40 <Input
41 type="text"
42 standalone
43 defaultValue={ document.title }
44 onKeyUp={ handleUpdateDocument.bind(this, document._id) }
45 />
46 </Col>
47 <Col xs={ 4 } sm={ 2 }>
48 <Button
49 bsStyle="danger"
50 className="btn-block"
51 onClick={ handleRemoveDocument.bind(this, document._id) }>
52 Remove
53 </Button>
54 </Col>
55 </Row>
56 </ListGroupItem>
57 );
58
imports/ui/components/documents-list.js
1 import React from 'react'; 1 import React from 'react';
2 import { Row, Col, ListGroup, ListGroupItem, Input, Button, Alert } from 'react-bootstrap'; 2 import { Row, Col, ListGroup, Alert } from 'react-bootstrap';
3 import { updateDocument, removeDocument } from '../../api/documents/methods.js'; 3 import { Document } from './document.js';
4 4
5 const handleUpdateDocument = ( documentId, event ) => { 5 export const DocumentsList = ({ documents }) => (
6 const title = event.target.value.trim(); 6 documents.length > 0 ? <ListGroup className="documents-list">
7 if ( title !== '' && event.keyCode === 13 ) { 7 {documents.map((doc) => (
8 updateDocument.call({ 8 <Document key={ doc._id } document={ doc } />
9 _id: documentId, 9 ))}
10 update: { title: title } 10 </ListGroup> :
11 }, ( error, response ) => { 11 <Alert bsStyle="warning">No documents yet.</Alert>
12 if ( error ) { 12 );
13 Bert.alert( error.reason, 'danger' );
14 }
15 });
16 }
17 };
18
19 const handleRemoveDocument = ( documentId, event ) => {
20 event.preventDefault();
21 if ( confirm( 'Are you sure? This is permanent.' ) ) {
22 removeDocument.call({
23 _id: documentId
24 }, ( error, response ) => {
25 if ( error ) {
26 Bert.alert( error.reason, 'danger' );
27 } else {
28 Bert.alert( 'Document removed!', 'success' );
29 }
30 });
31 }
32 };
33
34 export const DocumentsList = ( { documents } ) => {
35 if ( documents.length > 0 ) {
36 return <ListGroup className="documents-list">
37 {documents.map( ( { _id, title } ) => {
38 return <ListGroupItem key={ _id }>
39 <Row>
40 <Col xs={ 8 } sm={ 10 }>
41 <Input
42 type="text"
43 standalone
44 defaultValue={ title }
45 onKeyUp={ handleUpdateDocument.bind( this, _id ) }
46 />
47 </Col>
48 <Col xs={ 4 } sm={ 2 }>
49 <Button
50 bsStyle="danger"
51 className="btn-block"
52 onClick={ handleRemoveDocument.bind( this, _id ) }>
53 Remove
54 </Button>
55 </Col>
56 </Row>
57 </ListGroupItem>;
58 })}
59 </ListGroup>;
60 } else {
61 return <Alert bsStyle="warning">No documents yet.</Alert>;
62 }
63 };
64 13
imports/ui/components/public-navigation.js
1 import React from 'react'; 1 import React from 'react';
2 import { LinkContainer } from 'react-router-bootstrap'; 2 import { LinkContainer } from 'react-router-bootstrap';
3 import { Nav, NavItem } from 'react-bootstrap'; 3 import { Nav, NavItem } from 'react-bootstrap';
4 4
5 export const PublicNavigation = () => ( 5 export const PublicNavigation = () => (
6 <Nav pullRight> 6 <Nav pullRight>
7 <LinkContainer to="signup"> 7 <LinkContainer to="signup">
8 <NavItem eventKey={ 1 } href="/signup">Sign Up</NavItem> 8 <NavItem eventKey={ 1 } href="/signup">Sign Up</NavItem>
9 </LinkContainer> 9 </LinkContainer>
10 <LinkContainer to="login"> 10 <LinkContainer to="login">
11 <NavItem eventKey={ 2 } href="/login">Log In</NavItem> 11 <NavItem eventKey={ 2 } href="/login">Log In</NavItem>
12 </LinkContainer> 12 </LinkContainer>
13 </Nav> 13 </Nav>
14 ) 14 );
15 15
imports/ui/containers/app-navigation.js
1 import { composeWithTracker } from 'react-komposer'; 1 import { composeWithTracker } from 'react-komposer';
2 import { AppNavigation } from '../components/app-navigation'; 2 import { AppNavigation } from '../components/app-navigation';
3 3
4 const composer = ( props, onData ) => { 4 const composer = (props, onData) => {
5 onData( null, { hasUser: Meteor.user() } ); 5 onData(null, { hasUser: Meteor.user() });
6 }; 6 };
7 7
8 export default composeWithTracker( composer, {}, {}, { pure: false } )( AppNavigation ); 8 export default composeWithTracker(composer, {}, {}, { pure: false })(AppNavigation);
9 9
imports/ui/containers/documents-list.js
1 import React from 'react'; 1 import React from 'react';
2 import { composeWithTracker } from 'react-komposer'; 2 import { composeWithTracker } from 'react-komposer';
3 import { Documents } from '../../api/documents/documents.js'; 3 import { Documents } from '../../api/documents/documents.js';
4 import { DocumentsList } from '../components/documents-list.js'; 4 import { DocumentsList } from '../components/documents-list.js';
5 5
6 const composer = ( params, onReady ) => { 6 const composer = (params, onReady) => {
7 const subscription = Meteor.subscribe( 'documents' ); 7 const subscription = Meteor.subscribe('documents');
8 if ( subscription.ready() ) { 8 if (subscription.ready()) {
9 let documents = Documents.find().fetch(); 9 const documents = Documents.find().fetch();
10 onReady( null, { documents } ); 10 onReady(null, { documents });
11 } 11 }
12 }; 12 };
13 13
14 export default composeWithTracker( composer )( DocumentsList ); 14 export default composeWithTracker(composer)(DocumentsList);
15 15
imports/ui/layouts/app.js
1 import React from 'react'; 1 import React from 'react';
2 import { Grid } from 'react-bootstrap'; 2 import { Grid } from 'react-bootstrap';
3 import AppNavigation from '../containers/app-navigation'; 3 import AppNavigation from '../containers/app-navigation';
4 4
5 export const App = React.createClass({ 5 export const App = React.createClass({
6 contextTypes: { 6 contextTypes: {
7 router() { 7 router() {
8 return React.PropTypes.func.isRequired; 8 return React.PropTypes.func.isRequired;
9 } 9 },
10 }, 10 },
11 render() { 11 render() {
12 const { isActive } = this.context.router; 12 const { isActive } = this.context.router;
13 return <div> 13 return <div>
14 <AppNavigation activeRoute={ isActive } /> 14 <AppNavigation activeRoute={ isActive } />
15 <Grid> 15 <Grid>
16 { this.props.children } 16 { this.props.children }
17 </Grid> 17 </Grid>
18 </div>; 18 </div>;
19 } 19 },
20 }); 20 });
21 21
imports/ui/pages/documents.js
1 import React from 'react'; 1 import React from 'react';
2 import { Row, Col, Input, Button } from 'react-bootstrap'; 2 import { Row, Col, Input, Button } from 'react-bootstrap';
3 import DocumentsList from '../containers/documents-list.js'; 3 import DocumentsList from '../containers/documents-list.js';
4 import { AddDocument } from '../components/add-document.js'; 4 import { AddDocument } from '../components/add-document.js';
5 5
6 export const Documents = () => ( 6 export const Documents = () => (
7 <Row> 7 <Row>
8 <Col xs={ 12 }> 8 <Col xs={ 12 }>
9 <h4 className="page-header">Documents</h4> 9 <h4 className="page-header">Documents</h4>
10 <AddDocument /> 10 <AddDocument />
11 <DocumentsList /> 11 <DocumentsList />
12 </Col> 12 </Col>
13 </Row> 13 </Row>
14 ) 14 );
15 15
imports/ui/pages/index.js
1 import React from 'react'; 1 import React from 'react';
2 import { Jumbotron, Button } from 'react-bootstrap'; 2 import { Jumbotron, Button } from 'react-bootstrap';
3 3
4 export const Index = () => ( 4 export const Index = () => (
5 <Jumbotron className="text-center"> 5 <Jumbotron className="text-center">
6 <h2>Base</h2> 6 <h2>Base</h2>
7 <p>A starting point for Meteor applications.</p> 7 <p>A starting point for Meteor applications.</p>
8 <p><a className="btn btn-success" href="https://themeteorchef.com/base" role="button">Read the Documentation</a></p> 8 <p><a className="btn btn-success" href="https://themeteorchef.com/base" role="button">Read the Documentation</a></p>
9 <p style={ { fontSize: '16px', color: '#aaa' } }>Currently at v4.0.0</p> 9 <p style={ { fontSize: '16px', color: '#aaa' } }>Currently at v4.0.0</p>
10 </Jumbotron> 10 </Jumbotron>
11 ) 11 );
12 12
imports/ui/pages/login.js
1 import React from 'react'; 1 import React from 'react';
2 import { Link } from 'react-router'; 2 import { Link } from 'react-router';
3 import { Row, Col, PageHeader, Input, Button } from 'react-bootstrap'; 3 import { Row, Col, PageHeader, Input, Button } from 'react-bootstrap';
4 import { handleLogin } from '../../modules/login'; 4 import { handleLogin } from '../../modules/login';
5 5
6 export class Login extends React.Component { 6 export class Login extends React.Component {
7 componentDidMount() { 7 componentDidMount() {
8 handleLogin( { component: this } ); 8 handleLogin({ component: this });
9 } 9 }
10 10
11 handleSubmit( event ) { 11 handleSubmit(event) {
12 event.preventDefault(); 12 event.preventDefault();
13 } 13 }
14 14
15 render() { 15 render() {
16 return <Row> 16 return <Row>
17 <Col xs={ 12 } sm={ 6 } md={ 4 }> 17 <Col xs={ 12 } sm={ 6 } md={ 4 }>
18 <h4 className="page-header">Login</h4> 18 <h4 className="page-header">Login</h4>
19 <form ref="login" className="login" onSubmit={ this.handleSubmit }> 19 <form ref="login" className="login" onSubmit={ this.handleSubmit }>
20 <Input 20 <Input
21 type="email" 21 type="email"
22 label="Email Address" 22 label="Email Address"
23 ref="emailAddress" 23 ref="emailAddress"
24 name="emailAddress" 24 name="emailAddress"
25 placeholder="Email Address" 25 placeholder="Email Address"
26 /> 26 />
27 <div className="form-group"> 27 <div className="form-group">
28 <label htmlFor="password"> 28 <label htmlFor="password">
29 <span className="pull-left">Password</span> 29 <span className="pull-left">Password</span>
30 <Link className="pull-right" to="/recover-password">Forgot Password?</Link> 30 <Link className="pull-right" to="/recover-password">Forgot Password?</Link>
31 </label> 31 </label>
32 <input type="password" className="form-control" ref="password" name="password" placeholder="Password" /> 32 <input
33 type="password"
34 className="form-control"
35 ref="password"
36 name="password"
37 placeholder="Password"
38 />
33 </div> 39 </div>
34 <Button type="submit" bsStyle="success">Login</Button> 40 <Button type="submit" bsStyle="success">Login</Button>
35 </form> 41 </form>
36 </Col> 42 </Col>
37 </Row>; 43 </Row>;
38 } 44 }
39 } 45 }
40 46
imports/ui/pages/not-found.js
1 import React from 'react'; 1 import React from 'react';
2 import { browserHistory } from 'react-router'; 2 import { browserHistory } from 'react-router';
3 import { Alert } from 'react-bootstrap'; 3 import { Alert } from 'react-bootstrap';
4 4
5 export const NotFound = () => ( 5 export const NotFound = () => (
6 <Alert bsStyle="danger"> 6 <Alert bsStyle="danger">
7 <p><strong>Error [404]</strong>: { window.location.pathname } does not exist.</p> 7 <p><strong>Error [404]</strong>: { window.location.pathname } does not exist.</p>
8 </Alert> 8 </Alert>
9 ) 9 );
10 10
imports/ui/pages/recover-password.js
1 import React from 'react'; 1 import React from 'react';
2 import { Row, Col, Alert, Input, Button } from 'react-bootstrap'; 2 import { Row, Col, Alert, Input, Button } from 'react-bootstrap';
3 import { handleRecoverPassword } from '../../modules/recover-password'; 3 import { handleRecoverPassword } from '../../modules/recover-password';
4 4
5 export class RecoverPassword extends React.Component { 5 export class RecoverPassword extends React.Component {
6 componentDidMount() { 6 componentDidMount() {
7 handleRecoverPassword( { component: this } ); 7 handleRecoverPassword({ component: this });
8 } 8 }
9 9
10 handleSubmit( event ) { 10 handleSubmit(event) {
11 event.preventDefault(); 11 event.preventDefault();
12 } 12 }
13 13
14 render() { 14 render() {
15 return <Row> 15 return <Row>
16 <Col xs={ 12 } sm={ 6 } md={ 4 }> 16 <Col xs={ 12 } sm={ 6 } md={ 4 }>
17 <h4 className="page-header">Recover Password</h4> 17 <h4 className="page-header">Recover Password</h4>
18 <Alert bsStyle="info">Enter your email address below to receive a link to reset your password.</Alert> 18 <Alert bsStyle="info">
19 Enter your email address below to receive a link to reset your password.
20 </Alert>
19 <form ref="recoverPassword" className="recover-password" onSubmit={ this.handleSubmit }> 21 <form ref="recoverPassword" className="recover-password" onSubmit={ this.handleSubmit }>
20 <Input 22 <Input
21 type="email" 23 type="email"
22 ref="emailAddress" 24 ref="emailAddress"
23 name="emailAddress" 25 name="emailAddress"
24 placeholder="Email Address" 26 placeholder="Email Address"
25 /> 27 />
26 <Button type="submit" bsStyle="success">Recover Password</Button> 28 <Button type="submit" bsStyle="success">Recover Password</Button>
27 </form> 29 </form>
28 </Col> 30 </Col>
29 </Row>; 31 </Row>;
30 } 32 }
31 } 33 }
32 34
imports/ui/pages/reset-password.js
1 import React from 'react'; 1 import React from 'react';
2 import { Row, Col, Alert, Input, Button } from 'react-bootstrap'; 2 import { Row, Col, Alert, Input, Button } from 'react-bootstrap';
3 import { handleResetPassword } from '../../modules/reset-password'; 3 import { handleResetPassword } from '../../modules/reset-password';
4 4
5 export class ResetPassword extends React.Component { 5 export class ResetPassword extends React.Component {
6 componentDidMount() { 6 componentDidMount() {
7 handleResetPassword({ 7 handleResetPassword({
8 component: this, 8 component: this,
9 token: this.props.params.token 9 token: this.props.params.token,
10 }); 10 });
11 } 11 }
12 12
13 handleSubmit( event ) { 13 handleSubmit(event) {
14 event.preventDefault(); 14 event.preventDefault();
15 } 15 }
16 16
17 render() { 17 render() {
18 return <Row> 18 return <Row>
19 <Col xs={ 12 } sm={ 6 } md={ 4 }> 19 <Col xs={ 12 } sm={ 6 } md={ 4 }>
20 <h4 className="page-header">Reset Password</h4> 20 <h4 className="page-header">Reset Password</h4>
21 <Alert bsStyle="info">To reset your password, enter a new one below. You will be logged in with your new password.</Alert> 21 <Alert bsStyle="info">
22 To reset your password, enter a new one below. You will be logged in
23 with your new password.
24 </Alert>
22 <form ref="resetPassword" className="reset-password" onSubmit={ this.handleSubmit }> 25 <form ref="resetPassword" className="reset-password" onSubmit={ this.handleSubmit }>
23 <Input 26 <Input
24 label="New Password" 27 label="New Password"
25 type="password" 28 type="password"
26 ref="newPassword" 29 ref="newPassword"
27 name="newPassword" 30 name="newPassword"
28 placeholder="New Password" 31 placeholder="New Password"
29 /> 32 />
30 <Input 33 <Input
31 label="Repeat New Password" 34 label="Repeat New Password"
32 type="password" 35 type="password"
33 ref="repeatNewPassword" 36 ref="repeatNewPassword"
34 name="repeatNewPassword" 37 name="repeatNewPassword"
35 placeholder="Repeat New Password" 38 placeholder="Repeat New Password"
36 /> 39 />
37 <Button type="submit" bsStyle="success">Reset Password &amp; Login</Button> 40 <Button type="submit" bsStyle="success">Reset Password &amp; Login</Button>
38 </form> 41 </form>
39 </Col> 42 </Col>
40 </Row>; 43 </Row>;
41 } 44 }
42 } 45 }
43 46
imports/ui/pages/signup.js
1 import React from 'react'; 1 import React from 'react';
2 import { Link } from 'react-router'; 2 import { Link } from 'react-router';
3 import { Row, Col, PageHeader, Input, Button } from 'react-bootstrap'; 3 import { Row, Col, PageHeader, Input, Button } from 'react-bootstrap';
4 import { handleSignup } from '../../modules/signup'; 4 import { handleSignup } from '../../modules/signup';
5 5
6 export class Signup extends React.Component { 6 export class Signup extends React.Component {
7 componentDidMount() { 7 componentDidMount() {
8 handleSignup( { component: this } ); 8 handleSignup({ component: this });
9 } 9 }
10 10
11 handleSubmit( event ) { 11 handleSubmit(event) {
12 event.preventDefault(); 12 event.preventDefault();
13 } 13 }
14 14
15 render() { 15 render() {
16 return <Row> 16 return <Row>
17 <Col xs={ 12 } sm={ 6 } md={ 4 }> 17 <Col xs={ 12 } sm={ 6 } md={ 4 }>
18 <h4 className="page-header">Sign Up</h4> 18 <h4 className="page-header">Sign Up</h4>
19 <form ref="signup" className="signup" onSubmit={ this.handleSubmit }> 19 <form ref="signup" className="signup" onSubmit={ this.handleSubmit }>
20 <Row> 20 <Row>
21 <Col xs={ 6 } sm={ 6 }> 21 <Col xs={ 6 } sm={ 6 }>
22 <Input 22 <Input
23 type="text" 23 type="text"
24 label="First Name" 24 label="First Name"
25 ref="firstName" 25 ref="firstName"
26 name="firstName" 26 name="firstName"
27 placeholder="First Name" 27 placeholder="First Name"
28 /> 28 />
29 </Col> 29 </Col>
30 <Col xs={ 6 } sm={ 6 }> 30 <Col xs={ 6 } sm={ 6 }>
31 <Input 31 <Input
32 type="text" 32 type="text"
33 label="Last Name" 33 label="Last Name"
34 ref="lastName" 34 ref="lastName"
35 name="lastName" 35 name="lastName"
36 placeholder="Last Name" 36 placeholder="Last Name"
37 /> 37 />
38 </Col> 38 </Col>
39 </Row> 39 </Row>
40 <Input 40 <Input
41 type="email" 41 type="email"
42 label="Email Address" 42 label="Email Address"
43 ref="emailAddress" 43 ref="emailAddress"
44 name="emailAddress" 44 name="emailAddress"
45 placeholder="Email Address" 45 placeholder="Email Address"
46 /> 46 />
47 <Input 47 <Input
48 type="password" 48 type="password"
49 label="Password" 49 label="Password"
50 ref="password" 50 ref="password"
51 name="password" 51 name="password"
52 placeholder="Password" 52 placeholder="Password"
53 /> 53 />
54 <Button type="submit" bsStyle="success">Sign Up</Button> 54 <Button type="submit" bsStyle="success">Sign Up</Button>
55 </form> 55 </form>
56 <p>Already have an account? <Link to="/login">Log In</Link>.</p> 56 <p>Already have an account? <Link to="/login">Log In</Link>.</p>
57 </Col> 57 </Col>
58 </Row>; 58 </Row>;
59 } 59 }
60 } 60 }
61 61
1 { 1 {
2 "name": "application-name", 2 "name": "application-name",
3 "version": "1.0.0", 3 "version": "1.0.0",
4 "description": "Application description.", 4 "description": "Application description.",
5 "scripts": { 5 "scripts": {
6 "start": "meteor --settings settings-development.json", 6 "start": "meteor --settings settings-development.json",
7 "chimp-watch": "chimp --ddp=http://localhost:3000 --watch --mocha --path=tests", 7 "chimp-watch": "chimp --ddp=http://localhost:3000 --watch --mocha --path=tests",
8 "chimp-test": "chimp --ddp=http://localhost:3000 --mocha --path=tests", 8 "chimp-test": "chimp --ddp=http://localhost:3000 --mocha --path=tests",
9 "staging": "meteor deploy staging.meteor.com --settings settings-development.json", 9 "staging": "meteor deploy staging.meteor.com --settings settings-development.json",
10 "production": "meteor deploy production.meteor.com --settings settings-production.json" 10 "production": "meteor deploy production.meteor.com --settings settings-production.json"
11 }, 11 },
12 "devDependencies": {}, 12 "devDependencies": {},
13 "dependencies": { 13 "eslintConfig": {
14 "parserOptions": {
15 "ecmaFeatures": {
16 "jsx": true
17 }
18 },
19 "plugins": [
20 "meteor",
21 "react"
22 ],
23 "extends": [
24 "airbnb/base",
25 "plugin:meteor/guide"
26 ],
27 "globals": {
28 "$": false,
29 "Accounts": false,
30 "Bert": false,
31 "browser": false,
32 "expect": false,
33 "Factory": false,
34 "Meteor": false,
35 "server": false,
36 "SimpleSchema": false,
37 "ValidatedMethod": false
38 },
39 "rules": {}
40 },
41 "devDependencies": {
14 "chimp": "^0.33.0", 42 "chimp": "^0.33.0",
15 "react": "^0.14.8", 43 "eslint": "^2.6.0",
16 "react-addons-pure-render-mixin": "^0.14.8", 44 "eslint-config-airbnb": "^6.2.0",
45 "eslint-plugin-meteor": "^3.4.0",
46 "eslint-plugin-react": "^4.2.3",
47 "faker": "^3.1.0"
48 },
49 "dependencies": {
50 "react": "^15.0.1",
51 "react-addons-pure-render-mixin": "^15.0.1",
17 "react-bootstrap": "^0.28.4", 52 "react-bootstrap": "^0.28.4",
18 "react-dom": "^0.14.7", 53 "react-dom": "^15.0.1",
19 "react-komposer": "^1.7.1", 54 "react-komposer": "^1.7.1",
20 "react-router": "^2.0.1", 55 "react-router": "^2.0.1",
21 "react-router-bootstrap": "^0.20.1" 56 "react-router-bootstrap": "^0.20.1"
22 } 57 }
23 } 58 }
24 59
1 describe( 'Log In', function() { 1 /* eslint-env mocha */
2 beforeEach( function() { 2 /* eslint-disable func-names, prefer-arrow-callback */
3 server.execute( function() { 3
4 var user = Meteor.users.findOne( { 'emails.address': 'carl.winslow@abc.com' } ); 4 describe('Log In', function () {
5 if ( user ) { 5 beforeEach(function () {
6 Meteor.users.remove( user._id ); 6 server.execute(function () {
7 const user = Meteor.users.findOne({ 'emails.address': 'carl.winslow@abc.com' });
8 if (user) {
9 Meteor.users.remove(user._id);
7 } 10 }
8 }); 11 });
9 }); 12 });
10 13
11 it( 'should allow us to login', function() { 14 it('should allow us to login', function () {
12 server.execute( function() { 15 server.execute(function () {
13 Accounts.createUser({ 16 Accounts.createUser({
14 email: 'carl.winslow@abc.com', 17 email: 'carl.winslow@abc.com',
15 password: 'bigguy1989', 18 password: 'bigguy1989',
16 profile: { 19 profile: {
17 name: { first: 'Carl', last: 'Winslow' } 20 name: { first: 'Carl', last: 'Winslow' },
18 } 21 },
19 }); 22 });
20 }); 23 });
21 24
22 browser.url( 'http://localhost:3000/login' ) 25 browser.url('http://localhost:3000/login')
23 .setValue( '[name="emailAddress"]', 'carl.winslow@abc.com' ) 26 .setValue('[name="emailAddress"]', 'carl.winslow@abc.com')
24 .setValue( '[name="password"]', 'bigguy1989' ) 27 .setValue('[name="password"]', 'bigguy1989')
25 .submitForm( 'form' ); 28 .submitForm('form');
26 29
27 browser.waitForExist( '.jumbotron' ); 30 browser.waitForExist('.jumbotron');
28 expect( browser.getUrl() ).to.equal( 'http://localhost:3000/' ); 31 expect(browser.getUrl()).to.equal('http://localhost:3000/');
29 }); 32 });
30 }); 33 });
31 34
tests/not-found.js
1 describe( '404 Error', function() { 1 /* eslint-env mocha */
2 it( 'should render a 404 for a non-existent route', function() { 2 /* eslint-disable func-names, prefer-arrow-callback */
3 browser.url( 'http://localhost:3000/dididothat' )
4 .waitForExist( '.alert-danger' );
5 3
6 expect( browser.getText( '.alert-danger p' ) ).to.equal( 'Error [404]: /dididothat does not exist.' ); 4 describe('404 Error', function () {
5 it('should render a 404 for a non-existent route', function () {
6 browser.url('http://localhost:3000/dididothat')
7 .waitForExist('.alert-danger');
8
9 expect(browser.getText('.alert-danger p')).to.equal('Error [404]: /dididothat does not exist.');
7 }); 10 });
tests/reset-password.js
1 describe( 'Reset Password', function() { File was deleted
2 beforeEach( function() {
3 server.execute( function() {
4 var user = Meteor.users.findOne( { 'emails.address': 'carl.winslow@abc.com' } );
5 if ( user ) { Meteor.users.remove( user._id ); }
6
7 Accounts.createUser({
8 email: 'carl.winslow@abc.com',
9 password: 'bigguy1989',
10 profile: {
11 name: { first: 'Carl', last: 'Winslow' }
12 }
13 });
14 });
15 });
16
17 it( 'should send a recovery email when we request a reset @watch', function() {
18 browser.url( 'http://localhost:3000/recover-password' )
19 .setValue( '[name="emailAddress"]', 'carl.winslow@abc.com' )
20 .submitForm( 'form' );
21
22 let emails = server.call( 'emailStub/getEmails' );
23 console.log( emails[0] ); // Why no value?
24
25 // browser.url( 'http://localhost:3000' );
26 // browser.execute( function() {
27 // Accounts.onResetPasswordLink( function( token, finished ) {
28 // expect( token ).to.equal( 'blah' );
29 // finished();
30 // done();
31 // });
32 // });
33 });
34 });
35 1 describe( 'Reset Password', function() {
1 describe( 'Sign Up', function() { 1 /* eslint-env mocha */
2 beforeEach( function() { 2 /* eslint-disable func-names, prefer-arrow-callback */
3 server.execute( function() { 3
4 var user = Meteor.users.findOne( { 'emails.address': 'carl.winslow@abc.com' } ); 4 describe('Sign Up', function () {
5 if ( user ) { 5 beforeEach(function () {
6 Meteor.users.remove( user._id ); 6 server.execute(function () {
7 const user = Meteor.users.findOne({ 'emails.address': 'carl.winslow@abc.com' });
8 if (user) {
9 Meteor.users.remove(user._id);
7 } 10 }
8 }); 11 });
9 }); 12 });
10 13
11 it( 'should create a new user and login with redirect to index', function() { 14 it('should create a new user and login with redirect to index', function () {
12 browser.url( 'http://localhost:3000/signup' ) 15 browser.url('http://localhost:3000/signup')
13 .setValue( '[name="firstName"]', 'Carl' ) 16 .setValue('[name="firstName"]', 'Carl')
14 .setValue( '[name="lastName"]', 'Winslow' ) 17 .setValue('[name="lastName"]', 'Winslow')
15 .setValue( '[name="emailAddress"]', 'carl.winslow@abc.com' ) 18 .setValue('[name="emailAddress"]', 'carl.winslow@abc.com')
16 .setValue( '[name="password"]', 'bigguy1989' ) 19 .setValue('[name="password"]', 'bigguy1989')
17 .submitForm( 'form' ); 20 .submitForm('form');
18 21
19 browser.waitForExist( '.jumbotron' ); 22 browser.waitForExist('.jumbotron');
20 expect( browser.getUrl() ).to.equal( 'http://localhost:3000/' ); 23 expect(browser.getUrl()).to.equal('http://localhost:3000/');
21 }); 24 });
22 }); 25 });
23 26