Commit c42d4eeaceb50a4195d854a48fe6122f086724e5

Authored by themeteorchef
1 parent 23c8a4c3c3
Exists in master

handful of changes

- Remove dependency on faker NPM package.
- Consolidate insert and update methods into single upsert method.
- Add /edit and /new pages for editing and creating documents.
- Move all exports to default exports (linter thing).
- Add a module for editing and creating documents.
- Update method tests for documents collection to use new upsert method.
- Oh, I'm sure there's something else...
1 accounts-base@1.2.14 1 accounts-base@1.2.14
2 accounts-password@1.3.1 2 accounts-password@1.3.1
3 alanning:roles@1.2.15 3 alanning:roles@1.2.15
4 aldeed:collection2@2.10.0 4 aldeed:collection2@2.10.0
5 aldeed:collection2-core@1.2.0 5 aldeed:collection2-core@1.2.0
6 aldeed:schema-deny@1.1.0 6 aldeed:schema-deny@1.1.0
7 aldeed:schema-index@1.1.0 7 aldeed:schema-index@1.1.1
8 aldeed:simple-schema@1.5.3 8 aldeed:simple-schema@1.5.3
9 allow-deny@1.0.5 9 allow-deny@1.0.5
10 audit-argument-checks@1.0.7 10 audit-argument-checks@1.0.7
11 autoupdate@1.3.12 11 autoupdate@1.3.12
12 babel-compiler@6.13.0 12 babel-compiler@6.13.0
13 babel-runtime@0.1.12 13 babel-runtime@0.1.13
14 base64@1.0.10 14 base64@1.0.10
15 binary-heap@1.0.10 15 binary-heap@1.0.10
16 blaze@2.1.9 16 blaze@2.1.9
17 blaze-tools@1.0.10 17 blaze-tools@1.0.10
18 boilerplate-generator@1.0.11 18 boilerplate-generator@1.0.11
19 browser-policy@1.0.9 19 browser-policy@1.0.9
20 browser-policy-common@1.0.11 20 browser-policy-common@1.0.11
21 browser-policy-content@1.0.12 21 browser-policy-content@1.0.12
22 browser-policy-framing@1.0.12 22 browser-policy-framing@1.0.12
23 caching-compiler@1.1.8 23 caching-compiler@1.1.8
24 caching-html-compiler@1.0.7 24 caching-html-compiler@1.0.7
25 callback-hook@1.0.10 25 callback-hook@1.0.10
26 check@1.2.4 26 check@1.2.4
27 coffeescript@1.11.1_2 27 coffeescript@1.11.1_2
28 dburles:factory@1.1.0 28 dburles:factory@1.1.0
29 ddp@1.2.5 29 ddp@1.2.5
30 ddp-client@1.3.2 30 ddp-client@1.3.2
31 ddp-common@1.2.7 31 ddp-common@1.2.7
32 ddp-rate-limiter@1.0.6 32 ddp-rate-limiter@1.0.6
33 ddp-server@1.3.11 33 ddp-server@1.3.11
34 deps@1.0.12 34 deps@1.0.12
35 diff-sequence@1.0.7 35 diff-sequence@1.0.7
36 ecmascript@0.5.9 36 ecmascript@0.5.9
37 ecmascript-runtime@0.3.15 37 ecmascript-runtime@0.3.15
38 ejson@1.0.13 38 ejson@1.0.13
39 email@1.1.18 39 email@1.1.18
40 es5-shim@4.6.15 40 es5-shim@4.6.15
41 fastclick@1.0.13 41 fastclick@1.0.13
42 fortawesome:fontawesome@4.6.3 42 fortawesome:fontawesome@4.6.3
43 fourseven:scss@3.10.0 43 fourseven:scss@3.10.1
44 geojson-utils@1.0.10 44 geojson-utils@1.0.10
45 hot-code-push@1.0.4 45 hot-code-push@1.0.4
46 html-tools@1.0.11 46 html-tools@1.0.11
47 htmljs@1.0.11 47 htmljs@1.0.11
48 http@1.2.10 48 http@1.2.10
49 id-map@1.0.9 49 id-map@1.0.9
50 jquery@1.11.10 50 jquery@1.11.10
51 launch-screen@1.1.0 51 launch-screen@1.1.0
52 livedata@1.0.18 52 livedata@1.0.18
53 localstorage@1.0.12 53 localstorage@1.0.12
54 logging@1.1.16 54 logging@1.1.16
55 mdg:validated-method@1.1.0 55 mdg:validated-method@1.1.0
56 mdg:validation-error@0.5.1 56 mdg:validation-error@0.5.1
57 meteor@1.6.0 57 meteor@1.6.0
58 meteor-base@1.0.4 58 meteor-base@1.0.4
59 minifier-css@1.2.15 59 minifier-css@1.2.15
60 minifier-js@1.2.15 60 minifier-js@1.2.15
61 minimongo@1.0.18 61 minimongo@1.0.18
62 mobile-experience@1.0.4 62 mobile-experience@1.0.4
63 mobile-status-bar@1.0.13 63 mobile-status-bar@1.0.13
64 modules@0.7.7 64 modules@0.7.7
65 modules-runtime@0.7.7 65 modules-runtime@0.7.7
66 mongo@1.1.14 66 mongo@1.1.14
67 mongo-id@1.0.6 67 mongo-id@1.0.6
68 npm-bcrypt@0.9.2 68 npm-bcrypt@0.9.2
69 npm-mongo@2.2.11_2 69 npm-mongo@2.2.11_2
70 observe-sequence@1.0.14 70 observe-sequence@1.0.14
71 ordered-dict@1.0.9 71 ordered-dict@1.0.9
72 practicalmeteor:chai@2.1.0_1 72 practicalmeteor:chai@2.1.0_1
73 practicalmeteor:loglevel@1.2.0_2 73 practicalmeteor:loglevel@1.2.0_2
74 practicalmeteor:mocha@2.4.5_6 74 practicalmeteor:mocha@2.4.5_6
75 practicalmeteor:mocha-core@1.0.1 75 practicalmeteor:mocha-core@1.0.1
76 practicalmeteor:sinon@1.14.1_2 76 practicalmeteor:sinon@1.14.1_2
77 promise@0.8.8 77 promise@0.8.8
78 raix:eventemitter@0.1.3 78 raix:eventemitter@0.1.3
79 random@1.0.10 79 random@1.0.10
80 rate-limit@1.0.6 80 rate-limit@1.0.6
81 reactive-dict@1.1.8 81 reactive-dict@1.1.8
82 reactive-var@1.0.11 82 reactive-var@1.0.11
83 reload@1.1.11 83 reload@1.1.11
84 retry@1.0.9 84 retry@1.0.9
85 routepolicy@1.0.12 85 routepolicy@1.0.12
86 service-configuration@1.0.11 86 service-configuration@1.0.11
87 session@1.1.7 87 session@1.1.7
88 sha@1.0.9 88 sha@1.0.9
89 shell-server@0.2.1 89 shell-server@0.2.1
90 spacebars@1.0.13 90 spacebars@1.0.13
91 spacebars-compiler@1.0.13 91 spacebars-compiler@1.0.13
92 srp@1.0.10 92 srp@1.0.10
93 standard-minifier-css@1.3.2 93 standard-minifier-css@1.3.2
94 standard-minifier-js@1.2.1 94 standard-minifier-js@1.2.1
95 static-html@1.1.13 95 static-html@1.1.13
96 templating@1.2.15 96 templating@1.2.15
97 templating-compiler@1.2.15 97 templating-compiler@1.2.15
98 templating-runtime@1.2.15 98 templating-runtime@1.2.15
99 templating-tools@1.0.5 99 templating-tools@1.0.5
100 themeteorchef:bert@2.1.1 100 themeteorchef:bert@2.1.1
101 tmeasday:test-reporter-helpers@0.2.1 101 tmeasday:test-reporter-helpers@0.2.1
102 tracker@1.1.1 102 tracker@1.1.1
103 ui@1.0.12 103 ui@1.0.12
104 underscore@1.0.10 104 underscore@1.0.10
105 url@1.0.11 105 url@1.0.11
106 webapp@1.3.12 106 webapp@1.3.12
107 webapp-hashing@1.0.9 107 webapp-hashing@1.0.9
108 xolvio:backdoor@0.2.1 108 xolvio:backdoor@0.2.1
109 xolvio:cleaner@0.3.1 109 xolvio:cleaner@0.3.1
110 110
imports/api/documents/documents.js
1 if (Meteor.isClient) import faker from 'faker';
2 import { Mongo } from 'meteor/mongo'; 1 import { Mongo } from 'meteor/mongo';
3 import { SimpleSchema } from 'meteor/aldeed:simple-schema'; 2 import { SimpleSchema } from 'meteor/aldeed:simple-schema';
4 import { Factory } from 'meteor/dburles:factory'; 3 import { Factory } from 'meteor/dburles:factory';
5 4
6 export const Documents = new Mongo.Collection('Documents'); 5 const Documents = new Mongo.Collection('Documents');
6 export default Documents;
7 7
8 Documents.allow({ 8 Documents.allow({
9 insert: () => false, 9 insert: () => false,
10 update: () => false, 10 update: () => false,
11 remove: () => false, 11 remove: () => false,
12 }); 12 });
13 13
14 Documents.deny({ 14 Documents.deny({
15 insert: () => true, 15 insert: () => true,
16 update: () => true, 16 update: () => true,
17 remove: () => true, 17 remove: () => true,
18 }); 18 });
19 19
20 Documents.schema = new SimpleSchema({ 20 Documents.schema = new SimpleSchema({
21 title: { 21 title: {
22 type: String, 22 type: String,
23 label: 'The title of the document.', 23 label: 'The title of the document.',
24 }, 24 },
25 body: {
26 type: String,
27 label: 'The body of the document.',
28 },
25 }); 29 });
26 30
27 Documents.attachSchema(Documents.schema); 31 Documents.attachSchema(Documents.schema);
28 32
29 Factory.define('document', Documents, { 33 Factory.define('document', Documents, {
30 title: () => faker.hacker.phrase(), 34 title: () => 'Factory Title',
35 body: () => 'Factory Body',
31 }); 36 });
imports/api/documents/documents.tests.js
1 /* eslint-env mocha */ 1 /* eslint-env mocha */
2 /* eslint-disable func-names, prefer-arrow-callback */ 2 /* eslint-disable func-names, prefer-arrow-callback */
3 3
4 import { assert } from 'meteor/practicalmeteor:chai'; 4 import { assert } from 'meteor/practicalmeteor:chai';
5 import { Documents } from './documents.js'; 5 import Documents from './documents.js';
6 6
7 describe('Documents collection', function () { 7 describe('Documents collection', function () {
8 it('registers the collection with Mongo properly', function () { 8 it('registers the collection with Mongo properly', function () {
9 assert.equal(typeof Documents, 'object'); 9 assert.equal(typeof Documents, 'object');
10 }); 10 });
11 }); 11 });
12 12
imports/api/documents/methods.js
1 import { SimpleSchema } from 'meteor/aldeed:simple-schema'; 1 import { SimpleSchema } from 'meteor/aldeed:simple-schema';
2 import { ValidatedMethod } from 'meteor/mdg:validated-method'; 2 import { ValidatedMethod } from 'meteor/mdg:validated-method';
3 import { Documents } from './documents'; 3 import Documents from './documents';
4 import { rateLimit } from '../../modules/rate-limit.js'; 4 import { rateLimit } from '../../modules/rate-limit.js';
5 5
6 export const insertDocument = new ValidatedMethod({ 6 export const upsertDocument = new ValidatedMethod({
7 name: 'documents.insert', 7 name: 'documents.upsert',
8 validate: new SimpleSchema({ 8 validate: new SimpleSchema({
9 title: { type: String }, 9 _id: { type: String, optional: true },
10 title: { type: String, optional: true },
11 body: { type: String, optional: true },
10 }).validator(), 12 }).validator(),
11 run(document) { 13 run(document) {
12 Documents.insert(document); 14 return Documents.upsert({ _id: document._id }, { $set: document });
13 },
14 });
15
16 export const updateDocument = new ValidatedMethod({
17 name: 'documents.update',
18 validate: new SimpleSchema({
19 _id: { type: String },
20 'update.title': { type: String, optional: true },
21 }).validator(),
22 run({ _id, update }) {
23 Documents.update(_id, { $set: update });
24 }, 15 },
25 }); 16 });
26 17
27 export const removeDocument = new ValidatedMethod({ 18 export const removeDocument = new ValidatedMethod({
28 name: 'documents.remove', 19 name: 'documents.remove',
29 validate: new SimpleSchema({ 20 validate: new SimpleSchema({
30 _id: { type: String }, 21 _id: { type: String },
31 }).validator(), 22 }).validator(),
32 run({ _id }) { 23 run({ _id }) {
33 Documents.remove(_id); 24 Documents.remove(_id);
34 }, 25 },
35 }); 26 });
36 27
37 rateLimit({ 28 rateLimit({
38 methods: [ 29 methods: [
39 insertDocument, 30 upsertDocument,
40 updateDocument,
41 removeDocument, 31 removeDocument,
42 ], 32 ],
43 limit: 5, 33 limit: 5,
44 timeRange: 1000, 34 timeRange: 1000,
imports/api/documents/methods.tests.js
1 /* eslint-env mocha */ 1 /* eslint-env mocha */
2 /* eslint-disable func-names, prefer-arrow-callback */ 2 /* eslint-disable func-names, prefer-arrow-callback */
3 3
4 import { Meteor } from 'meteor/meteor'; 4 import { Meteor } from 'meteor/meteor';
5 import { assert } from 'meteor/practicalmeteor:chai'; 5 import { assert } from 'meteor/practicalmeteor:chai';
6 import { resetDatabase } from 'meteor/xolvio:cleaner'; 6 import { resetDatabase } from 'meteor/xolvio:cleaner';
7 import { Factory } from 'meteor/dburles:factory'; 7 import { Factory } from 'meteor/dburles:factory';
8 import { Documents } from './documents.js'; 8 import Documents from './documents.js';
9 import { insertDocument, updateDocument, removeDocument } from './methods.js'; 9 import { upsertDocument, removeDocument } from './methods.js';
10 10
11 describe('Documents methods', function () { 11 describe('Documents methods', function () {
12 beforeEach(function () { 12 beforeEach(function () {
13 if (Meteor.isServer) { 13 if (Meteor.isServer) {
14 resetDatabase(); 14 resetDatabase();
15 } 15 }
16 }); 16 });
17 17
18 it('inserts a document into the Documents collection', function () { 18 it('inserts a document into the Documents collection', function () {
19 insertDocument.call({ title: 'You can\'t arrest me, I\'m the Cake Boss!' }); 19 upsertDocument.call({
20 title: 'You can\'t arrest me, I\'m the Cake Boss!',
21 body: 'They went nuts!',
22 });
23
20 const getDocument = Documents.findOne({ title: 'You can\'t arrest me, I\'m the Cake Boss!' }); 24 const getDocument = Documents.findOne({ title: 'You can\'t arrest me, I\'m the Cake Boss!' });
21 assert.equal(getDocument.title, 'You can\'t arrest me, I\'m the Cake Boss!'); 25 assert.equal(getDocument.body, 'They went nuts!');
22 }); 26 });
23 27
24 it('updates a document in the Documents collection', function () { 28 it('updates a document in the Documents collection', function () {
25 const { _id } = Factory.create('document'); 29 const { _id } = Factory.create('document');
26 30
27 updateDocument.call({ 31 upsertDocument.call({
28 _id, 32 _id,
29 update: { 33 title: 'You can\'t arrest me, I\'m the Cake Boss!',
30 title: 'You can\'t arrest me, I\'m the Cake Boss!', 34 body: 'They went nuts!',
31 },
32 }); 35 });
33 36
34 const getDocument = Documents.findOne(_id); 37 const getDocument = Documents.findOne(_id);
35 assert.equal(getDocument.title, 'You can\'t arrest me, I\'m the Cake Boss!'); 38 assert.equal(getDocument.title, 'You can\'t arrest me, I\'m the Cake Boss!');
36 }); 39 });
37 40
38 it('removes a document from the Documents collection', function () { 41 it('removes a document from the Documents collection', function () {
39 const { _id } = Factory.create('document'); 42 const { _id } = Factory.create('document');
40 removeDocument.call({ _id }); 43 removeDocument.call({ _id });
41 const getDocument = Documents.findOne(_id); 44 const getDocument = Documents.findOne(_id);
42 assert.equal(getDocument, undefined); 45 assert.equal(getDocument, undefined);
43 }); 46 });
44 }); 47 });
imports/api/documents/server/publications.js
1 import { Meteor } from 'meteor/meteor'; 1 import { Meteor } from 'meteor/meteor';
2 import { Documents } from '../documents'; 2 import { check } from 'meteor/check';
3 import Documents from '../documents';
3 4
4 Meteor.publish('documents', () => Documents.find()); 5 Meteor.publish('documents.list', () => Documents.find());
6
7 Meteor.publish('documents.view', (_id) => {
8 check(_id, String);
9 return Documents.find(_id);
10 });
11
12 Meteor.publish('documents.edit', (_id) => {
13 check(_id, String);
14 return Documents.find(_id);
15 });
5 16
imports/modules/document-editor.js
File was created 1 import $ from 'jquery';
2 import 'jquery-validation';
3 import { browserHistory } from 'react-router';
4 import { Bert } from 'meteor/themeteorchef:bert';
5 import { upsertDocument } from '../api/documents/methods.js';
6
7 let component;
8
9 const handleUpsert = () => {
10 const { doc } = component.props;
11 const confirmation = doc && doc._id ? 'Document updated!' : 'Document added!';
12 const upsert = {
13 title: document.querySelector('[name="title"]').value.trim(),
14 body: document.querySelector('[name="body"]').value.trim(),
15 };
16
17 if (doc && doc._id) upsert._id = doc._id;
18
19 upsertDocument.call(upsert, (error, { insertedId }) => {
20 if (error) {
21 Bert.alert(error.reason, 'danger');
22 } else {
23 component.form.reset();
24 Bert.alert(confirmation, 'success');
25 browserHistory.push(`/documents/${insertedId || doc._id}`);
26 }
27 });
28 };
29
30 const validate = () => {
31 $(component.form).validate({
32 rules: {
33 title: {
34 required: true,
35 },
36 body: {
37 required: true,
38 },
39 },
40 messages: {
41 title: {
42 required: 'Need a title in here, Seuss.',
43 },
44 body: {
45 required: 'This thneeds a body, please.',
46 },
47 },
48 submitHandler() { handleUpsert(); },
49 });
50 };
51
52 export default function handleLogin(options) {
53 component = options.component;
54 validate();
55 }
56
imports/modules/login.js
1 import $ from 'jquery'; 1 import $ from 'jquery';
2 import 'jquery-validation'; 2 import 'jquery-validation';
3 import { browserHistory } from 'react-router'; 3 import { browserHistory } from 'react-router';
4 import { Meteor } from 'meteor/meteor'; 4 import { Meteor } from 'meteor/meteor';
5 import { Bert } from 'meteor/themeteorchef:bert'; 5 import { Bert } from 'meteor/themeteorchef:bert';
6 6
7 let component; 7 let component;
8 8
9 const login = () => { 9 const login = () => {
10 const email = document.querySelector('[name="emailAddress"]').value; 10 const email = document.querySelector('[name="emailAddress"]').value;
11 const password = document.querySelector('[name="password"]').value; 11 const password = document.querySelector('[name="password"]').value;
12 12
13 Meteor.loginWithPassword(email, password, (error) => { 13 Meteor.loginWithPassword(email, password, (error) => {
14 if (error) { 14 if (error) {
15 Bert.alert(error.reason, 'warning'); 15 Bert.alert(error.reason, 'warning');
16 } else { 16 } else {
17 Bert.alert('Logged in!', 'success'); 17 Bert.alert('Logged in!', 'success');
18 18
19 const { location } = component.props; 19 const { location } = component.props;
20 if (location.state && location.state.nextPathname) { 20 if (location.state && location.state.nextPathname) {
21 browserHistory.push(location.state.nextPathname); 21 browserHistory.push(location.state.nextPathname);
22 } else { 22 } else {
23 browserHistory.push('/'); 23 browserHistory.push('/');
24 } 24 }
25 } 25 }
26 }); 26 });
27 }; 27 };
28 28
29 const validate = () => { 29 const validate = () => {
30 $(component.refs.login).validate({ 30 $('.login').validate({
31 rules: { 31 rules: {
32 emailAddress: { 32 emailAddress: {
33 required: true, 33 required: true,
34 email: true, 34 email: true,
35 }, 35 },
36 password: { 36 password: {
37 required: true, 37 required: true,
38 }, 38 },
39 }, 39 },
40 messages: { 40 messages: {
41 emailAddress: { 41 emailAddress: {
42 required: 'Need an email address here.', 42 required: 'Need an email address here.',
43 email: 'Is this email address legit?', 43 email: 'Is this email address legit?',
44 }, 44 },
45 password: { 45 password: {
46 required: 'Need a password here.', 46 required: 'Need a password here.',
47 }, 47 },
48 }, 48 },
49 submitHandler() { login(); }, 49 submitHandler() { login(); },
50 }); 50 });
51 }; 51 };
52 52
53 export default function handleLogin(options) { 53 export default function handleLogin(options) {
54 component = options.component; 54 component = options.component;
55 validate(); 55 validate();
56 } 56 }
57 57
imports/startup/client/routes.js
1 /* eslint-disable max-len */
2
1 import React from 'react'; 3 import React from 'react';
2 import { render } from 'react-dom'; 4 import { render } from 'react-dom';
3 import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 5 import { Router, Route, IndexRoute, browserHistory } from 'react-router';
4 import { Meteor } from 'meteor/meteor'; 6 import { Meteor } from 'meteor/meteor';
5 import { App } from '../../ui/layouts/App.js'; 7 import App from '../../ui/layouts/App.js';
6 import { Documents } from '../../ui/pages/Documents.js'; 8 import Documents from '../../ui/pages/Documents.js';
7 import { Index } from '../../ui/pages/Index.js'; 9 import NewDocument from '../../ui/pages/NewDocument.js';
8 import { Login } from '../../ui/pages/Login.js'; 10 import EditDocument from '../../ui/containers/EditDocument.js';
9 import { NotFound } from '../../ui/pages/NotFound.js'; 11 import ViewDocument from '../../ui/containers/ViewDocument.js';
10 import { RecoverPassword } from '../../ui/pages/RecoverPassword.js'; 12 import Index from '../../ui/pages/Index.js';
11 import { ResetPassword } from '../../ui/pages/ResetPassword.js'; 13 import Login from '../../ui/pages/Login.js';
12 import { Signup } from '../../ui/pages/Signup.js'; 14 import NotFound from '../../ui/pages/NotFound.js';
15 import RecoverPassword from '../../ui/pages/RecoverPassword.js';
16 import ResetPassword from '../../ui/pages/ResetPassword.js';
17 import Signup from '../../ui/pages/Signup.js';
13 18
14 const requireAuth = (nextState, replace) => { 19 const requireAuth = (nextState, replace) => {
15 if (!Meteor.loggingIn() && !Meteor.userId()) { 20 if (!Meteor.loggingIn() && !Meteor.userId()) {
16 replace({ 21 replace({
17 pathname: '/login', 22 pathname: '/login',
18 state: { nextPathname: nextState.location.pathname }, 23 state: { nextPathname: nextState.location.pathname },
19 }); 24 });
20 } 25 }
21 }; 26 };
22 27
23 Meteor.startup(() => { 28 Meteor.startup(() => {
24 render( 29 render(
25 <Router history={ browserHistory }> 30 <Router history={ browserHistory }>
26 <Route path="/" component={ App }> 31 <Route path="/" component={ App }>
27 <IndexRoute name="index" component={ Index } onEnter={ requireAuth } /> 32 <IndexRoute name="index" component={ Index } onEnter={ requireAuth } />
28 <Route name="documents" path="/documents" component={ Documents } onEnter={ requireAuth } /> 33 <Route name="documents" path="/documents" component={ Documents } onEnter={ requireAuth } />
34 <Route name="newDocument" path="/documents/new" component={ NewDocument } onEnter={ requireAuth } />
35 <Route name="editDocument" path="/documents/:_id/edit" component={ EditDocument } onEnter={ requireAuth } />
36 <Route name="editDocument" path="/documents/:_id" component={ ViewDocument } onEnter={ requireAuth } />
29 <Route name="login" path="/login" component={ Login } /> 37 <Route name="login" path="/login" component={ Login } />
30 <Route name="recover-password" path="/recover-password" component={ RecoverPassword } /> 38 <Route name="recover-password" path="/recover-password" component={ RecoverPassword } />
31 <Route name="reset-password" path="/reset-password/:token" component={ ResetPassword } /> 39 <Route name="reset-password" path="/reset-password/:token" component={ ResetPassword } />
32 <Route name="signup" path="/signup" component={ Signup } /> 40 <Route name="signup" path="/signup" component={ Signup } />
33 <Route path="*" component={ NotFound } /> 41 <Route path="*" component={ NotFound } />
34 </Route> 42 </Route>
35 </Router>, 43 </Router>,
36 document.getElementById('react-root') 44 document.getElementById('react-root')
37 ); 45 );
38 }); 46 });
39 47
imports/ui/components/AppNavigation.js
1 import React from 'react'; 1 import React from 'react';
2 import { Navbar } from 'react-bootstrap'; 2 import { Navbar } from 'react-bootstrap';
3 import { Link } from 'react-router'; 3 import { Link } from 'react-router';
4 import { PublicNavigation } from './PublicNavigation.js'; 4 import PublicNavigation from './PublicNavigation.js';
5 import { AuthenticatedNavigation } from './AuthenticatedNavigation.js'; 5 import AuthenticatedNavigation from './AuthenticatedNavigation.js';
6 6
7 const renderNavigation = hasUser => (hasUser ? <AuthenticatedNavigation /> : <PublicNavigation />); 7 const renderNavigation = hasUser => (hasUser ? <AuthenticatedNavigation /> : <PublicNavigation />);
8 8
9 const AppNavigation = ({ hasUser }) => ( 9 const AppNavigation = ({ hasUser }) => (
10 <Navbar> 10 <Navbar>
11 <Navbar.Header> 11 <Navbar.Header>
12 <Navbar.Brand> 12 <Navbar.Brand>
13 <Link to="/">Application Name</Link> 13 <Link to="/">Application Name</Link>
14 </Navbar.Brand> 14 </Navbar.Brand>
15 <Navbar.Toggle /> 15 <Navbar.Toggle />
16 </Navbar.Header> 16 </Navbar.Header>
17 <Navbar.Collapse> 17 <Navbar.Collapse>
18 { renderNavigation(hasUser) } 18 { renderNavigation(hasUser) }
19 </Navbar.Collapse> 19 </Navbar.Collapse>
20 </Navbar> 20 </Navbar>
21 ); 21 );
22 22
23 AppNavigation.propTypes = { 23 AppNavigation.propTypes = {
24 hasUser: React.PropTypes.object, 24 hasUser: React.PropTypes.object,
25 }; 25 };
26 26
27 export default AppNavigation; 27 export default AppNavigation;
28 28
imports/ui/components/DocumentEditor.js
File was created 1 /* eslint-disable max-len, no-return-assign */
2
3 import React from 'react';
4 import { FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap';
5 import documentEditor from '../../modules/document-editor.js';
6
7 export default class DocumentEditor extends React.Component {
8 componentDidMount() {
9 documentEditor({ component: this });
10 }
11
12 render() {
13 const { doc } = this.props;
14 return (<form
15 ref={ form => this.form = form }
16 onSubmit={ event => event.preventDefault() }
17 >
18 <FormGroup>
19 <ControlLabel>Title</ControlLabel>
20 <FormControl
21 type="text"
22 name="title"
23 defaultValue={ doc && doc.title }
24 placeholder="Oh, The Places You'll Go!"
25 />
26 </FormGroup>
27 <FormGroup>
28 <ControlLabel>Body</ControlLabel>
29 <FormControl
30 componentClass="textarea"
31 name="body"
32 defaultValue={ doc && doc.body }
33 placeholder="Congratulations! Today is your day. You're off to Great Places! You're off and away!"
34 />
35 </FormGroup>
36 <Button type="submit" bsStyle="success">
37 { doc && doc._id ? 'Save Changes' : 'Add Document' }
38 </Button>
39 </form>);
40 }
41 }
42
43 DocumentEditor.propTypes = {
44 doc: React.PropTypes.object,
45 };
46
imports/ui/components/DocumentsList.js
1 import React from 'react'; 1 import React from 'react';
2 import { ListGroup, Alert } from 'react-bootstrap'; 2 import { ListGroup, ListGroupItem, Alert } from 'react-bootstrap';
3 import { Document } from './Document.js';
4 3
5 const DocumentsList = ({ documents }) => ( 4 const DocumentsList = ({ documents }) => (
6 documents.length > 0 ? <ListGroup className="documents-list"> 5 documents.length > 0 ? <ListGroup className="DocumentsList">
7 {documents.map(doc => ( 6 {documents.map(({ _id, title }) => (
8 <Document key={ doc._id } document={ doc } /> 7 <ListGroupItem key={ _id } href={`/documents/${_id}`}>{ title }</ListGroupItem>
9 ))} 8 ))}
10 </ListGroup> : 9 </ListGroup> :
11 <Alert bsStyle="warning">No documents yet.</Alert> 10 <Alert bsStyle="warning">No documents yet.</Alert>
12 ); 11 );
13 12
14 DocumentsList.propTypes = { 13 DocumentsList.propTypes = {
15 documents: React.PropTypes.array, 14 documents: React.PropTypes.array,
16 }; 15 };
17 16
18 export default DocumentsList; 17 export default DocumentsList;
19 18
imports/ui/containers/AppNavigation.js
1 import { composeWithTracker } from 'react-komposer'; 1 import { composeWithTracker } from 'react-komposer';
2 import { Meteor } from 'meteor/meteor'; 2 import { Meteor } from 'meteor/meteor';
3 import { AppNavigation } from '../components/AppNavigation.js'; 3 import AppNavigation from '../components/AppNavigation.js';
4 4
5 const composer = (props, onData) => onData(null, { hasUser: Meteor.user() }); 5 const composer = (props, onData) => onData(null, { hasUser: Meteor.user() });
6 6
7 export default composeWithTracker(composer, {}, {}, { pure: false })(AppNavigation); 7 export default composeWithTracker(composer, {}, {}, { pure: false })(AppNavigation);
8 8
imports/ui/containers/DocumentsList.js
1 import { composeWithTracker } from 'react-komposer'; 1 import { composeWithTracker } from 'react-komposer';
2 import { Meteor } from 'meteor/meteor'; 2 import { Meteor } from 'meteor/meteor';
3 import { Documents } from '../../api/documents/documents.js'; 3 import Documents from '../../api/documents/documents.js';
4 import { DocumentsList } from '../components/DocumentsList.js'; 4 import DocumentsList from '../components/DocumentsList.js';
5 import { Loading } from '../components/Loading.js'; 5 import Loading from '../components/Loading.js';
6 6
7 const composer = (params, onData) => { 7 const composer = (params, onData) => {
8 const subscription = Meteor.subscribe('documents'); 8 const subscription = Meteor.subscribe('documents.list');
9 if (subscription.ready()) { 9 if (subscription.ready()) {
10 const documents = Documents.find().fetch(); 10 const documents = Documents.find().fetch();
11 onData(null, { documents }); 11 onData(null, { documents });
12 } 12 }
13 }; 13 };
14 14
15 export default composeWithTracker(composer, Loading)(DocumentsList); 15 export default composeWithTracker(composer, Loading)(DocumentsList);
16 16
imports/ui/containers/EditDocument.js
File was created 1 import { Meteor } from 'meteor/meteor';
2 import { composeWithTracker } from 'react-komposer';
3 import Documents from '../../api/documents/documents.js';
4 import EditDocument from '../pages/EditDocument.js';
5 import { Loading } from '../components/Loading.js';
6
7 const composer = ({ params }, onData) => {
8 const subscription = Meteor.subscribe('documents.edit', params._id);
9
10 if (subscription.ready()) {
11 const doc = Documents.findOne();
12 onData(null, { doc });
13 }
14 };
15
16 export default composeWithTracker(composer, Loading)(EditDocument);
17
imports/ui/containers/ViewDocument.js
File was created 1 import { Meteor } from 'meteor/meteor';
2 import { composeWithTracker } from 'react-komposer';
3 import Documents from '../../api/documents/documents.js';
4 import ViewDocument from '../pages/ViewDocument.js';
5 import { Loading } from '../components/Loading.js';
6
7 const composer = ({ params }, onData) => {
8 const subscription = Meteor.subscribe('documents.view', params._id);
9
10 if (subscription.ready()) {
11 const doc = Documents.findOne();
12 onData(null, { doc });
13 }
14 };
15
16 export default composeWithTracker(composer, Loading)(ViewDocument);
17
imports/ui/pages/EditDocument.js
File was created 1 import React from 'react';
2 import DocumentEditor from '../components/DocumentEditor.js';
3
4 const EditDocument = ({ doc }) => (
5 <div className="EditDocument">
6 <h4 className="page-header">Editing "{ doc.title }"</h4>
7 <DocumentEditor doc={ doc } />
8 </div>
9 );
10
11 EditDocument.propTypes = {
12 doc: React.PropTypes.object,
13 };
14
15 export default EditDocument;
16
imports/ui/pages/NewDocument.js
File was created 1 import React from 'react';
2 import DocumentEditor from '../components/DocumentEditor.js';
3
4 const NewDocument = () => (
5 <div className="NewDocument">
6 <h4 className="page-header">New Document</h4>
7 <DocumentEditor />
8 </div>
9 );
10
11 export default NewDocument;
12
imports/ui/pages/RecoverPassword.js
1 import React from 'react'; 1 import React from 'react';
2 import { Row, Col, Alert, FormGroup, FormControl, Button } from 'react-bootstrap'; 2 import { Row, Col, Alert, FormGroup, FormControl, Button } from 'react-bootstrap';
3 import { handleRecoverPassword } from '../../modules/recover-password'; 3 import handleRecoverPassword from '../../modules/recover-password';
4 4
5 export default class RecoverPassword extends React.Component { 5 export default class RecoverPassword extends React.Component {
6 componentDidMount() { 6 componentDidMount() {
7 handleRecoverPassword({ component: this }); 7 handleRecoverPassword({ component: this });
8 } 8 }
9 9
10 handleSubmit() { 10 handleSubmit(event) {
11 this.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"> 18 <Alert bsStyle="info">
19 Enter your email address below to receive a link to reset your password. 19 Enter your email address below to receive a link to reset your password.
20 </Alert> 20 </Alert>
21 <form ref="recoverPassword" className="recover-password" onSubmit={ this.handleSubmit }> 21 <form ref="recoverPassword" className="recover-password" onSubmit={ this.handleSubmit }>
22 <FormGroup> 22 <FormGroup>
23 <FormControl 23 <FormControl
24 type="email" 24 type="email"
25 ref="emailAddress" 25 ref="emailAddress"
26 name="emailAddress" 26 name="emailAddress"
27 placeholder="Email Address" 27 placeholder="Email Address"
28 /> 28 />
29 </FormGroup> 29 </FormGroup>
30 <Button type="submit" bsStyle="success">Recover Password</Button> 30 <Button type="submit" bsStyle="success">Recover Password</Button>
31 </form> 31 </form>
32 </Col> 32 </Col>
33 </Row>); 33 </Row>);
34 } 34 }
35 } 35 }
36 36
imports/ui/pages/ResetPassword.js
1 import React from 'react'; 1 import React from 'react';
2 import { Row, Col, Alert, FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap'; 2 import { Row, Col, Alert, FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap';
3 import { handleResetPassword } from '../../modules/reset-password'; 3 import handleResetPassword from '../../modules/reset-password';
4 4
5 export default class ResetPassword extends React.Component { 5 export default class ResetPassword extends React.Component {
6 componentDidMount() { 6 componentDidMount() {
7 handleResetPassword({ component: this, token: this.props.params.token }); 7 handleResetPassword({ component: this, token: this.props.params.token });
8 } 8 }
9 9
10 handleSubmit() { 10 handleSubmit(event) {
11 this.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">Reset Password</h4> 17 <h4 className="page-header">Reset Password</h4>
18 <Alert bsStyle="info"> 18 <Alert bsStyle="info">
19 To reset your password, enter a new one below. You will be logged in 19 To reset your password, enter a new one below. You will be logged in
20 with your new password. 20 with your new password.
21 </Alert> 21 </Alert>
22 <form ref="resetPassword" className="reset-password" onSubmit={ this.handleSubmit }> 22 <form ref="resetPassword" className="reset-password" onSubmit={ this.handleSubmit }>
23 <FormGroup> 23 <FormGroup>
24 <ControlLabel>New Password</ControlLabel> 24 <ControlLabel>New Password</ControlLabel>
25 <FormControl 25 <FormControl
26 type="password" 26 type="password"
27 ref="newPassword" 27 ref="newPassword"
28 name="newPassword" 28 name="newPassword"
29 placeholder="New Password" 29 placeholder="New Password"
30 /> 30 />
31 </FormGroup> 31 </FormGroup>
32 <FormGroup> 32 <FormGroup>
33 <ControlLabel>Repeat New Password</ControlLabel> 33 <ControlLabel>Repeat New Password</ControlLabel>
34 <FormControl 34 <FormControl
35 type="password" 35 type="password"
36 ref="repeatNewPassword" 36 ref="repeatNewPassword"
37 name="repeatNewPassword" 37 name="repeatNewPassword"
38 placeholder="Repeat New Password" 38 placeholder="Repeat New Password"
39 /> 39 />
40 </FormGroup> 40 </FormGroup>
41 <Button type="submit" bsStyle="success">Reset Password &amp; Login</Button> 41 <Button type="submit" bsStyle="success">Reset Password &amp; Login</Button>
42 </form> 42 </form>
43 </Col> 43 </Col>
44 </Row>); 44 </Row>);
45 } 45 }
46 } 46 }
47 47
48 ResetPassword.propTypes = { 48 ResetPassword.propTypes = {
49 params: React.PropTypes.object, 49 params: React.PropTypes.object,
50 }; 50 };
51 51
imports/ui/pages/ViewDocument.js
File was created 1 import React from 'react';
2 import { ButtonToolbar, ButtonGroup, Button } from 'react-bootstrap';
3 import { browserHistory } from 'react-router';
4 import { Bert } from 'meteor/themeteorchef:bert';
5 import { removeDocument } from '../../api/documents/methods.js';
6
7 const handleRemove = (_id) => {
8 if (confirm('Are you sure? This is permanent!')) {
9 removeDocument.call({ _id }, (error) => {
10 if (error) {
11 Bert.alert(error.reason, 'danger');
12 } else {
13 Bert.alert('Document deleted!', 'success');
14 browserHistory.push('/documents');
15 }
16 });
17 }
18 };
19
20 const ViewDocument = ({ doc }) => (
21 <div className="ViewDocument">
22 <div className="page-header clearfix">
23 <h4 className="pull-left">{ doc.title }</h4>
24 <ButtonToolbar className="pull-right">
25 <ButtonGroup bsSize="small">
26 <Button href={`/documents/${doc._id}/edit`}>Edit</Button>
27 <Button onClick={ () => handleRemove(doc._id) } className="text-danger">Delete</Button>
28 </ButtonGroup>
29 </ButtonToolbar>
30 </div>
31 { doc.body }
32 </div>
33 );
34
35 ViewDocument.propTypes = {
36 doc: React.PropTypes.object.isRequired,
37 };
38
39 export default ViewDocument;
40
imports/ui/pages/documents.js
1 import React from 'react'; 1 import React from 'react';
2 import { Row, Col } from 'react-bootstrap'; 2 import { Row, Col, Button } from 'react-bootstrap';
3 import DocumentsList from '../containers/DocumentsList.js'; 3 import DocumentsList from '../containers/DocumentsList.js';
4 import { AddDocument } from '../components/AddDocument.js';
5 4
6 const Documents = () => ( 5 const Documents = () => (
7 <Row> 6 <Row>
8 <Col xs={ 12 }> 7 <Col xs={ 12 }>
9 <h4 className="page-header">Documents</h4> 8 <div className="page-header clearfix">
10 <AddDocument /> 9 <h4 className="pull-left">Documents</h4>
10 <Button
11 bsStyle="success"
12 className="pull-right"
13 href="/documents/new"
14 >New Document</Button>
15 </div>
11 <DocumentsList /> 16 <DocumentsList />
12 </Col> 17 </Col>
13 </Row> 18 </Row>
14 ); 19 );
15 20
16 export default Documents; 21 export default Documents;
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, FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap'; 3 import { Row, Col, FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap';
4 import { handleLogin } from '../../modules/login'; 4 import handleLogin from '../../modules/login';
5 5
6 export default class Login extends React.Component { 6 export default class Login extends React.Component {
7 componentDidMount() { 7 componentDidMount() {
8 handleLogin({ component: this }); 8 handleLogin({ component: this });
9 } 9 }
10 10
11 handleSubmit() { 11 handleSubmit(event) {
12 this.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 <FormGroup> 20 <FormGroup>
21 <ControlLabel>Email Address</ControlLabel> 21 <ControlLabel>Email Address</ControlLabel>
22 <FormControl 22 <FormControl
23 type="email" 23 type="email"
24 ref="emailAddress" 24 ref="emailAddress"
25 name="emailAddress" 25 name="emailAddress"
26 placeholder="Email Address" 26 placeholder="Email Address"
27 /> 27 />
28 </FormGroup> 28 </FormGroup>
29 <FormGroup> 29 <FormGroup>
30 <ControlLabel> 30 <ControlLabel>
31 <span className="pull-left">Password</span> 31 <span className="pull-left">Password</span>
32 <Link className="pull-right" to="/recover-password">Forgot Password?</Link> 32 <Link className="pull-right" to="/recover-password">Forgot Password?</Link>
33 </ControlLabel> 33 </ControlLabel>
34 <FormControl 34 <FormControl
35 type="password" 35 type="password"
36 ref="password" 36 ref="password"
37 name="password" 37 name="password"
38 placeholder="Password" 38 placeholder="Password"
39 /> 39 />
40 </FormGroup> 40 </FormGroup>
41 <Button type="submit" bsStyle="success">Login</Button> 41 <Button type="submit" bsStyle="success">Login</Button>
42 </form> 42 </form>
43 </Col> 43 </Col>
44 </Row>); 44 </Row>);
45 } 45 }
46 } 46 }
47 47
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, FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap'; 3 import { Row, Col, FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap';
4 import { handleSignup } from '../../modules/signup'; 4 import handleSignup from '../../modules/signup';
5 5
6 export default class Signup extends React.Component { 6 export default class Signup extends React.Component {
7 componentDidMount() { 7 componentDidMount() {
8 handleSignup({ component: this }); 8 handleSignup({ component: this });
9 } 9 }
10 10
11 handleSubmit() { 11 handleSubmit(event) {
12 this.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 <FormGroup> 22 <FormGroup>
23 <ControlLabel>First Name</ControlLabel> 23 <ControlLabel>First Name</ControlLabel>
24 <FormControl 24 <FormControl
25 type="text" 25 type="text"
26 ref="firstName" 26 ref="firstName"
27 name="firstName" 27 name="firstName"
28 placeholder="First Name" 28 placeholder="First Name"
29 /> 29 />
30 </FormGroup> 30 </FormGroup>
31 </Col> 31 </Col>
32 <Col xs={ 6 } sm={ 6 }> 32 <Col xs={ 6 } sm={ 6 }>
33 <FormGroup> 33 <FormGroup>
34 <ControlLabel>Last Name</ControlLabel> 34 <ControlLabel>Last Name</ControlLabel>
35 <FormControl 35 <FormControl
36 type="text" 36 type="text"
37 ref="lastName" 37 ref="lastName"
38 name="lastName" 38 name="lastName"
39 placeholder="Last Name" 39 placeholder="Last Name"
40 /> 40 />
41 </FormGroup> 41 </FormGroup>
42 </Col> 42 </Col>
43 </Row> 43 </Row>
44 <FormGroup> 44 <FormGroup>
45 <ControlLabel>Email Address</ControlLabel> 45 <ControlLabel>Email Address</ControlLabel>
46 <FormControl 46 <FormControl
47 type="text" 47 type="text"
48 ref="emailAddress" 48 ref="emailAddress"
49 name="emailAddress" 49 name="emailAddress"
50 placeholder="Email Address" 50 placeholder="Email Address"
51 /> 51 />
52 </FormGroup> 52 </FormGroup>
53 <FormGroup> 53 <FormGroup>
54 <ControlLabel>Password</ControlLabel> 54 <ControlLabel>Password</ControlLabel>
55 <FormControl 55 <FormControl
56 type="password" 56 type="password"
57 ref="password" 57 ref="password"
58 name="password" 58 name="password"
59 placeholder="Password" 59 placeholder="Password"
60 /> 60 />
61 </FormGroup> 61 </FormGroup>
62 <Button type="submit" bsStyle="success">Sign Up</Button> 62 <Button type="submit" bsStyle="success">Sign Up</Button>
63 </form> 63 </form>
64 <p>Already have an account? <Link to="/login">Log In</Link>.</p> 64 <p>Already have an account? <Link to="/login">Log In</Link>.</p>
65 </Col> 65 </Col>
66 </Row>); 66 </Row>);
67 } 67 }
68 } 68 }
69 69
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 "test": "meteor test --driver-package practicalmeteor:mocha --port 5000", 7 "test": "meteor test --driver-package practicalmeteor:mocha --port 5000",
8 "chimp-watch": "chimp --ddp=http://localhost:3000 --watch --mocha --path=tests", 8 "chimp-watch": "chimp --ddp=http://localhost:3000 --watch --mocha --path=tests",
9 "chimp-test": "chimp --ddp=http://localhost:3000 --mocha --path=tests", 9 "chimp-test": "chimp --ddp=http://localhost:3000 --mocha --path=tests",
10 "staging": "meteor deploy staging.meteor.com --settings settings-development.json", 10 "staging": "meteor deploy staging.meteor.com --settings settings-development.json",
11 "production": "meteor deploy production.meteor.com --settings settings-production.json" 11 "production": "meteor deploy production.meteor.com --settings settings-production.json"
12 }, 12 },
13 "devDependencies": { 13 "devDependencies": {
14 "chimp": "^0.41.2", 14 "chimp": "^0.41.2",
15 "eslint": "^3.8.1", 15 "eslint": "^3.8.1",
16 "eslint-config-airbnb": "^12.0.0", 16 "eslint-config-airbnb": "^12.0.0",
17 "eslint-plugin-import": "^1.16.0", 17 "eslint-plugin-import": "^1.16.0",
18 "eslint-plugin-jsx-a11y": "^2.2.3", 18 "eslint-plugin-jsx-a11y": "^2.2.3",
19 "eslint-plugin-meteor": "^4.0.1", 19 "eslint-plugin-meteor": "^4.0.1",
20 "eslint-plugin-react": "^6.4.1", 20 "eslint-plugin-react": "^6.4.1"
21 "faker": "^3.1.0"
22 }, 21 },
23 "eslintConfig": { 22 "eslintConfig": {
24 "parserOptions": { 23 "parserOptions": {
25 "ecmaFeatures": { 24 "ecmaFeatures": {
26 "jsx": true 25 "jsx": true
27 } 26 }
28 }, 27 },
29 "plugins": [ 28 "plugins": [
30 "meteor", 29 "meteor",
31 "react" 30 "react"
32 ], 31 ],
33 "extends": [ 32 "extends": [
34 "airbnb/base", 33 "airbnb/base",
35 "plugin:meteor/guide", 34 "plugin:meteor/guide",
36 "plugin:react/recommended" 35 "plugin:react/recommended"
37 ], 36 ],
38 "env": { 37 "env": {
39 "browser": true 38 "browser": true
40 }, 39 },
41 "globals": { 40 "globals": {
42 "server": false, 41 "server": false,
43 "browser": false, 42 "browser": false,
44 "expect": false 43 "expect": false
45 }, 44 },
46 "rules": { 45 "rules": {
47 "import/no-unresolved": 0, 46 "import/no-unresolved": 0,
48 "import/no-extraneous-dependencies": 0, 47 "import/no-extraneous-dependencies": 0,
49 "import/extensions": 0, 48 "import/extensions": 0,
50 "no-underscore-dangle": [ 49 "no-underscore-dangle": [
51 "error", 50 "error",
52 { 51 {
53 "allow": [ 52 "allow": [
54 "_id", 53 "_id",
55 "_ensureIndex", 54 "_ensureIndex",
56 "_verifyEmailToken", 55 "_verifyEmailToken",
57 "_resetPasswordToken", 56 "_resetPasswordToken",
58 "_name" 57 "_name"
59 ] 58 ]
60 } 59 }
61 ] 60 ],
61 "class-methods-use-this": 0
62 } 62 }
63 }, 63 },
64 "dependencies": { 64 "dependencies": {
65 "bcrypt": "^0.8.7", 65 "bcrypt": "^0.8.7",
66 "bootstrap": "^3.3.7", 66 "bootstrap": "^3.3.7",
67 "jquery": "^3.1.1", 67 "jquery": "^2.2.4",
68 "jquery-validation": "^1.15.1", 68 "jquery-validation": "^1.15.1",
69 "react": "^15.3.2", 69 "react": "^15.3.2",
70 "react-addons-pure-render-mixin": "^15.3.2", 70 "react-addons-pure-render-mixin": "^15.3.2",
71 "react-bootstrap": "^0.30.5", 71 "react-bootstrap": "^0.30.5",
72 "react-dom": "^15.3.2", 72 "react-dom": "^15.3.2",
73 "react-komposer": "^1.13.1", 73 "react-komposer": "^1.13.1",
74 "react-router": "^3.0.0", 74 "react-router": "^3.0.0",
75 "react-router-bootstrap": "^0.23.1" 75 "react-router-bootstrap": "^0.23.1"
76 } 76 }
77 } 77 }