├── .gitignore ├── .meteor ├── .finished-upgraders ├── .gitignore ├── .id ├── packages ├── platforms ├── release └── versions ├── Makefile ├── README.md ├── app.json ├── components ├── accountsUIWrapper.jsx ├── home.jsx ├── layout.jsx ├── lib │ ├── alert.jsx │ ├── dropdown.jsx │ └── inlineEdit.jsx └── post.jsx ├── example.settings.json ├── lib ├── app.browserify.js ├── app.browserify.options.json ├── collections.js └── mixins │ └── accountActionsMixin.jsx ├── packages.json ├── packages └── npm-container │ ├── .npm │ └── package │ │ ├── .gitignore │ │ ├── README │ │ └── npm-shrinkwrap.json │ ├── index.js │ └── package.js ├── public └── favicon.png ├── server ├── email.js ├── newUser.js ├── publications.js ├── seed.js └── serviceConfig.js └── styles ├── _dropdown.scss └── index.scss /.gitignore: -------------------------------------------------------------------------------- 1 | # System files 2 | .DS_Store 3 | 4 | lib/app.browserify.js.cached 5 | lib/app.browserify.js.map 6 | 7 | # Sensitive files 8 | settings.json 9 | -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | -------------------------------------------------------------------------------- /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 1yxk8jr1psbmxj11wlrx5 8 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-base # Packages every Meteor app needs to have 8 | mobile-experience # Packages for a great mobile UX 9 | mongo # The database Meteor supports right now 10 | blaze-html-templates # Compile .html files into Meteor Blaze views 11 | session # Client-side reactive dictionary for your app 12 | jquery # Helpful client-side library 13 | tracker # Meteor's client-side reactive programming library 14 | 15 | standard-minifiers # JS/CSS minifiers run for production mode 16 | es5-shim # ECMAScript 5 compatibility for older browsers. 17 | ecmascript # Enable ECMAScript2015+ syntax in app code 18 | 19 | insecure # Allow all DB writes from clients (for prototyping) 20 | 21 | react 22 | fourseven:scss 23 | mrt:normalize.css 24 | kadira:flow-router 25 | kadira:react-layout 26 | stevezhu:lodash 27 | meteorhacks:npm 28 | cosmos:browserify 29 | 30 | 31 | momentjs:moment 32 | accounts-ui-unstyled 33 | alanning:roles 34 | kadira:dochead 35 | 36 | 37 | npm-container 38 | service-configuration 39 | email 40 | dyaa:bootstrap-sass-only 41 | q42:react-markdown 42 | check 43 | audit-argument-checks 44 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.2.1 2 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.2 2 | accounts-ui-unstyled@1.1.8 3 | alanning:roles@1.2.14 4 | audit-argument-checks@1.0.4 5 | autoupdate@1.2.4 6 | babel-compiler@5.8.24_1 7 | babel-runtime@0.1.4 8 | base64@1.0.4 9 | binary-heap@1.0.4 10 | blaze@2.1.3 11 | blaze-html-templates@1.0.1 12 | blaze-tools@1.0.4 13 | boilerplate-generator@1.0.4 14 | caching-compiler@1.0.0 15 | caching-html-compiler@1.0.2 16 | callback-hook@1.0.4 17 | check@1.1.0 18 | coffeescript@1.0.11 19 | cosmos:browserify@0.9.2 20 | ddp@1.2.2 21 | ddp-client@1.2.1 22 | ddp-common@1.2.2 23 | ddp-rate-limiter@1.0.0 24 | ddp-server@1.2.2 25 | deps@1.0.9 26 | diff-sequence@1.0.1 27 | dyaa:bootstrap-sass-only@3.3.5_3 28 | ecmascript@0.1.6 29 | ecmascript-runtime@0.2.6 30 | ejson@1.0.7 31 | email@1.0.8 32 | es5-shim@4.1.14 33 | fastclick@1.0.7 34 | fourseven:scss@3.4.1 35 | geojson-utils@1.0.4 36 | hot-code-push@1.0.0 37 | html-tools@1.0.5 38 | htmljs@1.0.5 39 | http@1.1.1 40 | id-map@1.0.4 41 | insecure@1.0.4 42 | jquery@1.11.4 43 | jsx@0.2.3 44 | kadira:dochead@1.4.0 45 | kadira:flow-router@2.10.0 46 | kadira:react-layout@1.5.3 47 | launch-screen@1.0.4 48 | less@2.5.1 49 | livedata@1.0.15 50 | localstorage@1.0.5 51 | logging@1.0.8 52 | meteor@1.1.10 53 | meteor-base@1.0.1 54 | meteorhacks:async@1.0.0 55 | meteorhacks:npm@1.5.0 56 | minifiers@1.1.7 57 | minimongo@1.0.10 58 | mobile-experience@1.0.1 59 | mobile-status-bar@1.0.6 60 | momentjs:moment@2.10.6 61 | mongo@1.1.3 62 | mongo-id@1.0.1 63 | mrt:normalize.css@2.0.1 64 | npm-container@1.2.0 65 | npm-mongo@1.4.39_1 66 | observe-sequence@1.0.7 67 | ordered-dict@1.0.4 68 | perak:markdown@1.0.5 69 | promise@0.5.1 70 | q42:react-markdown@0.0.3 71 | random@1.0.5 72 | rate-limit@1.0.0 73 | react@0.14.3 74 | react-meteor-data@0.2.4 75 | react-runtime@0.14.3 76 | react-runtime-dev@0.14.3 77 | react-runtime-prod@0.14.3 78 | reactive-dict@1.1.3 79 | reactive-var@1.0.6 80 | reload@1.1.4 81 | retry@1.0.4 82 | routepolicy@1.0.6 83 | service-configuration@1.0.5 84 | session@1.1.1 85 | spacebars@1.0.7 86 | spacebars-compiler@1.0.7 87 | standard-minifiers@1.0.2 88 | stevezhu:lodash@3.10.1 89 | templating@1.1.5 90 | templating-tools@1.0.0 91 | tracker@1.0.9 92 | ui@1.0.8 93 | underscore@1.0.4 94 | url@1.0.5 95 | webapp@1.2.3 96 | webapp-hashing@1.0.5 97 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | serve: 2 | meteor --settings settings.json 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Meteor/React boilerplate 2 | 3 | **React is rapidly changing, so this boilerplate might not be relevant anymore...** 4 | 5 | MeteorJS and ReactJS go together like peas and carrots, peanut butter and jelly, or peanut butter and chocolate. Unfortunately, getting an app up and running on this combination not straightforward. I've collected some of the repetitive boilerplate set up into this repo, and try to hint how I like to organize projects. 6 | 7 | Issues and PR's welcome! In the meantime, the nirvana of Meteor/React awaits... 8 | 9 | ## Set up 10 | 11 | If you haven't already set up Meteor, [do that first](https://www.meteor.com/install). `git clone` this repo or [download a ZIP](https://github.com/andrewliebchen/meteor-react-boilerplate/archive/master.zip) to your local machine. 12 | 13 | You'll need to rename `example.settings.json` to just `settings.json`. You can take this opportunity to set up accounts (see the next section 👇), or just leave it as is. 14 | 15 | That's it! The first time you start the development server, Meteor will install all packages and dependencies. 🌟 16 | 17 | ## Development 18 | 19 | To start a local development server, run: 20 | 21 | ``` 22 | make serve 23 | ``` 24 | 25 | This is a simple alias for `meteor --settings settings.json`, which you're more than welcome to run...but the `make` command is much shorter 💁 26 | 27 | ### User accounts 28 | 29 | The [Service Configuration](https://atmospherejs.com/meteor/service-configuration) package allows you to configure accounts without using Meteor's Blaze-based UI. Add Facebook, Github, Google, or Twitter public and secret keys to `settings.json` and the boilerplate will take care of the rest... 30 | 31 | Account services set up links: 32 | * [Facebook Developers](https://developers.facebook.com/apps/) 33 | * [Github Applications](https://github.com/settings/applications) 34 | * [Google Developers Console](https://console.developers.google.com/) 35 | * [Twitter Apps](https://apps.twitter.com/) 36 | 37 | Alternatively, if you don't want to use the Service Configuration package, you can use the `` component. This is a simple React wrapper around the Blaze accounts UI component. 38 | 39 | ### User roles 40 | 41 | If you're building an application that requires user roles, I've included [Roles](https://atmospherejs.com/alanning/roles), a great package for creating, validating, and managing user roles. Once you've created at least one user via your UI or in `meteor shell`, you can promote that user to an admin by opening `meteor shell`, finding the ID of the user you want to promote, then run: 42 | 43 | ``` 44 | Roles.addUsersToRoles(USERID, ['admin']) 45 | ``` 46 | 47 | For more about Roles, check out the documentation on Atmosphere or [Github](https://github.com/alanning/meteor-roles/). 48 | 49 | ### Sending email from your app 50 | 51 | To send an email from your application, set up a free account with [Sendgrid](https://sendgrid.com/), then add your credentials to the email section of your `settings.json`. A general method to send email is available: 52 | 53 | ``` 54 | Meteor.call('sendEmail', { 55 | to: 'user@example.com', 56 | from: 'you@example.com', 57 | subject: 'Lift off with Meteor', 58 | text: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.' 59 | }); 60 | ``` 61 | 62 | ## Organization 63 | 64 | Directory structure should be recognizable if you're familiar with Meteor. 65 | 66 | #### `/components` 67 | 68 | As you're building your app, most of your time will be spent in `/components`. This directory will house all of your React components. Since Meteor is an isomorphic JS framework, we can include client- and server-side in the same file. 69 | 70 | Generally you can have your React component, Flow Router routes, and server methods in the same file. This is pretty sweet because it represents a true _separation of concerns_ because each _file_ contains everything necessary for that component to function! 71 | 72 | For example: 73 | 74 | ```js 75 | Widget = React.createClass({ 76 | handleWidgetClick() { 77 | Meteor.call('updateWidget'); 78 | }, 79 | 80 | render() { 81 | return ( 82 |
83 | ); 84 | } 85 | }); 86 | 87 | if(Meteor.isClient) { 88 | FlowRouter.route('/widgets/:_id', { 89 | action(id) { 90 | ReactLayout.render(Widget); 91 | }); 92 | }); 93 | } 94 | 95 | if(Meteor.isServer) { 96 | Meteor.methods({ 97 | updateWidget() { 98 | return Widgets.update(...); 99 | } 100 | }); 101 | } 102 | ``` 103 | 104 | Maybe this is a bad idea, I don't know. Make your own boilerplate. 105 | 106 | #### `/lib` 107 | 108 | `/lib` contains a directory for React mixins. This ensures that mixins will be available before React components are loaded... 109 | 110 | #### `/styles` 111 | 112 | The boilerplate uses Sass, but feel free to remove the `fourseven:scss` package if you want to use Less or CSS only. 113 | 114 | ## Dependencies & packages 115 | 116 | Here's a few more packages included (that haven't been mentioned already)... 117 | 118 | Package | Description 119 | ------- | ----------- 120 | fourseven:scss | Sass and SCSS support for Meteor.js (with autoprefixer and sourcemaps). 121 | kadira:dochead | Isomorphic way to manipulate document.head for Meteor apps 122 | kadira:flow-router | Carefully Designed Client Side Router for Meteor 123 | momentjs:moment | Parse, validate, manipulate, and display dates - official Meteor packaging 124 | meteorhacks:npm | Use npm modules with your Meteor App 125 | stevezhu:lodash | A utility library delivering consistency, customization, performance, & extras. 126 | dyaa:bootstrap-sass-only | Bootstrap with Sass-files only. React should handle many of the interface actions instead of Bootstrap's JS. 127 | email | Send email messages 128 | q42:react-markdown | React component to parse markdown in Meteor 129 | 130 | ## Deployment 131 | 132 | ### meteor.com 133 | 134 | Until MDG discontinues it, you can always deploy for free by runing `meteor deploy`. More about [Meteor deployment](http://guide.meteor.com/deployment.html). 135 | 136 | ### Heroku 137 | 138 | Set up a (free) [Heroku account](https://id.heroku.com/) and deploy: 139 | 140 | [![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) 141 | 142 | ## Acknowledgements 143 | 144 | * [Ryan Glover](https://twitter.com/rglover) AKA [The Meteor Chef](https://themeteorchef.com/) always provides solutions and inspiration. How do you do it? Seriously, this guy puts a [massive amount of time](https://docs.google.com/spreadsheets/d/1aSwgJRngLpx-anWAzuG7s_hqcjMwlnLdLNJyYhzH4eY/edit#gid=0) into each recipe. 145 | * [Sacha Greif](http://sachagreif.com/) and his [Telescope](https://github.com/TelescopeJS/Telescope). If you ever need to know how to do something in Meteor, a solution is probably in Telescope's source. 146 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Meteor/React Boilerplate", 3 | "description": "Get your Meteor/React project started in two shakes of a lamb's tail", 4 | "repository": "https://github.com/andrewliebchen/meteor-react-boilerplate", 5 | "keywords": ["meteor", "reactjs", "boilerplate"], 6 | "env": { 7 | "BUILDPACK_URL": "https://github.com/jordansissel/heroku-buildpack-meteor.git", 8 | "ROOT_URL": { 9 | "description": "Unless you have custom domain, it will be your Heroku app name + .herokuapp.com" 10 | } 11 | }, 12 | "addons": [ 13 | "mongolab" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /components/accountsUIWrapper.jsx: -------------------------------------------------------------------------------- 1 | AccountsUIWrapper = React.createClass({ 2 | componentDidMount() { 3 | this.view = Blaze.render(Blaze.Template.loginButtons, 4 | ReactDOM.findDOMNode(this.refs.container)); 5 | }, 6 | 7 | componentWillUnmount() { 8 | Blaze.remove(this.view); 9 | }, 10 | 11 | render() { 12 | return ; 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /components/home.jsx: -------------------------------------------------------------------------------- 1 | Home = React.createClass({ 2 | mixins: [ReactMeteorData], 3 | 4 | getMeteorData() { 5 | return { 6 | posts: Posts.find().fetch() 7 | }; 8 | }, 9 | 10 | render() { 11 | return ( 12 |
13 |

Hello, World!

14 |
15 | {this.data.posts.map((post, i) => { 16 | return ; 17 | })} 18 |
19 |
20 | ); 21 | } 22 | }); 23 | 24 | if(Meteor.isClient) { 25 | FlowRouter.route('/', { 26 | subscriptions() { 27 | this.register('posts', Meteor.subscribe('posts')); 28 | }, 29 | 30 | action() { 31 | FlowRouter.subsReady('posts', () => { 32 | DocHead.setTitle('Meteor React Boilerplate'); 33 | ReactLayout.render(Layout, { 34 | content: 35 | }); 36 | }); 37 | } 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /components/layout.jsx: -------------------------------------------------------------------------------- 1 | Layout = React.createClass({ 2 | mixins: [AccountActionsMixin], 3 | 4 | propTypes: { 5 | content: React.PropTypes.element.isRequired 6 | }, 7 | 8 | componentWillMount() { 9 | DocHead.addMeta({ 10 | rel: "icon", 11 | type: "image/png", 12 | href: "/favicon.png " 13 | }); 14 | DocHead.addMeta({ 15 | name: "viewport", 16 | content: "width=device-width, initial-scale=1" 17 | }); 18 | }, 19 | 20 | render() { 21 | return ( 22 |
23 |
24 |
25 | Home 26 | 31 |
32 |
33 |
34 | 35 | {this.props.content} 36 |
37 |
38 | ); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /components/lib/alert.jsx: -------------------------------------------------------------------------------- 1 | Alert = React.createClass({ 2 | mixins: [ReactMeteorData], 3 | 4 | getMeteorData() { 5 | return { 6 | alert: Session.get('alert') 7 | }; 8 | }, 9 | 10 | componentDidUpdate() { 11 | if(this.data.alert) { 12 | setTimeout(() => { 13 | Session.set('alert', null); 14 | }, 5000); 15 | } 16 | }, 17 | 18 | render() { 19 | let {alert} = this.data; 20 | 21 | if(alert) { 22 | return ( 23 |
24 | {alert.message} 25 |
26 | ); 27 | } else { 28 | return false; 29 | } 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /components/lib/dropdown.jsx: -------------------------------------------------------------------------------- 1 | const CSSTransitionGroup = React.addons.CSSTransitionGroup; 2 | 3 | DropdownItem = React.createClass({ 4 | propTypes: { 5 | handleClick: React.PropTypes.func 6 | }, 7 | 8 | render() { 9 | return ( 10 |
  • 11 | {this.props.children} 12 |
  • 13 | ); 14 | } 15 | }); 16 | 17 | Dropdown = React.createClass({ 18 | propTypes: { 19 | toggle: React.PropTypes.string, 20 | className: React.PropTypes.string 21 | }, 22 | 23 | getInitialState() { 24 | return { 25 | menu: false, 26 | clickOnDropdown: false 27 | }; 28 | }, 29 | 30 | handleMouseDown() { 31 | this.setState({clickOnDropdown: true}); 32 | }, 33 | 34 | handleMouseUp() { 35 | this.setState({clickOnDropdown: false}); 36 | }, 37 | 38 | pageClick() { 39 | this.setState({ 40 | menu: false, 41 | clickOnDropdown: false 42 | }); 43 | }, 44 | 45 | handleDropdownToggle() { 46 | this.setState({menu: !this.state.menu}); 47 | }, 48 | 49 | componentDidMount() { 50 | window.addEventListener('mousedown', this.pageClick, false); 51 | }, 52 | 53 | render() { 54 | let dropdownClassName = classnames({ 55 | 'dropdown': true, 56 | 'open': this.state.menu 57 | }); 58 | return ( 59 |
    60 | 65 | 69 | {this.state.menu ? 70 |
      75 | {this.props.children} 76 |
    77 | : null} 78 |
    79 |
    80 | ); 81 | } 82 | }); 83 | -------------------------------------------------------------------------------- /components/lib/inlineEdit.jsx: -------------------------------------------------------------------------------- 1 | InlineEdit = React.createClass({ 2 | propTypes: { 3 | defaultValue: React.PropTypes.string, 4 | method: React.PropTypes.string, 5 | id: React.PropTypes.string, 6 | type: React.PropTypes.oneOf(['input', 'textarea']), 7 | placeholder: React.PropTypes.string, 8 | successMessage: React.PropTypes.string 9 | }, 10 | 11 | getDefaultProps() { 12 | return { 13 | type: 'input', 14 | placeholder: 'Click to add' 15 | }; 16 | }, 17 | 18 | getInitialState() { 19 | return { 20 | editing: false, 21 | text: this.props.defaultValue 22 | }; 23 | }, 24 | 25 | handleEditToggle() { 26 | this.setState({editing: !this.state.editing}); 27 | }, 28 | 29 | handleOnChange(event) { 30 | this.setState({text: event.target.value}); 31 | }, 32 | 33 | handleSave(event) { 34 | if(event.which === 13) { 35 | if(!event.shiftKey) { 36 | Meteor.call(this.props.method, { 37 | id: this.props.id, 38 | value: this.state.text 39 | }, (error, success) => { 40 | if(success){ 41 | Session.set('alert', { 42 | status: 'success', 43 | message: this.props.successMessage 44 | }); 45 | this.setState({ 46 | editing: false 47 | }); 48 | } 49 | }); 50 | } 51 | } 52 | }, 53 | 54 | render() { 55 | let {defaultValue, type} = this.props; 56 | if(this.state.editing) { 57 | return ( 58 |
    59 | {type === 'input' ? 60 | 69 | : null} 70 | {type === 'textarea' ? 71 | 72 |