├── .babelrc ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── app ├── actions │ └── currentUser.js ├── collections │ └── index.js ├── components │ ├── About.js │ ├── App.js │ ├── Landing.js │ ├── NotFound.js │ ├── SecretPage.js │ ├── index.js │ └── tests │ │ └── app_spec.js ├── containers │ ├── DevTools.js │ ├── Header.js │ ├── Login.js │ ├── Profile.js │ ├── Root.js │ ├── Settings.js │ └── index.js ├── layouts │ ├── CoreLayout.js │ ├── EmptyLayout.js │ └── index.js ├── main_client.js ├── main_server.js ├── method_example.js ├── redux │ ├── configureStore.js │ └── modules │ │ ├── auth.js │ │ └── reducer.js ├── routes │ └── index.js ├── server │ └── init-user.js └── styles │ ├── _base.scss │ ├── core.scss │ └── vendor │ └── _normalize.scss ├── assets └── logo.png ├── core-js-custom-build.js ├── debug.js ├── deploy.js ├── dev.js ├── dirs.js ├── karma.conf.js ├── met ├── meteor_core ├── .gitignore ├── .meteor │ ├── .finished-upgraders │ ├── .gitignore │ ├── .id │ ├── packages │ ├── platforms │ ├── release │ └── versions ├── client │ └── .gitignore ├── index.html └── server │ ├── .gitignore │ └── cors.js ├── package.json ├── predeploy.js ├── prod.js ├── projectName.js ├── runWebpackConfigs.js ├── settings ├── .gitignore ├── devel.json ├── prod.json └── stage.json ├── test └── karma.bundle.js └── webpack ├── .gitignore ├── client ├── client.bundle.js ├── client.bundle.min.js └── d2a2450bb4b434caf462a7dc6bd7749c.jpeg ├── devProps.js ├── loadClientBundle.html ├── server ├── server.bundle.js └── server.bundle.js.map ├── webpack.config.client.deploy.js ├── webpack.config.client.dev.js ├── webpack.config.client.js ├── webpack.config.client.prod.js ├── webpack.config.server.deploy.js ├── webpack.config.server.dev.js ├── webpack.config.server.js └── webpack.config.server.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "env": { 4 | // only enable it when process.env.NODE_ENV is 'development' or undefined 5 | "development": { 6 | "plugins": [ 7 | "react-transform" 8 | ], 9 | "extra": { 10 | "react-transform": { 11 | "transforms": [{ 12 | "transform": "react-transform-hmr", 13 | "imports": ["react"], 14 | // this is important for Webpack HMR: 15 | "locals": ["module"] 16 | }, { 17 | "transform": "react-transform-catch-errors", 18 | // the second import is the React component to render error 19 | // (it can be a local path too, like "./src/ErrorReporter") 20 | "imports": ["react", "redbox-react"] 21 | }] 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser" : "babel-eslint", 3 | "extends" : "airbnb", 4 | "env" : { 5 | "browser" : true 6 | }, 7 | "globals" : { 8 | "__DEV__" : false, 9 | "__PROD__" : false, 10 | "__DEBUG__" : false, 11 | "__DEBUG_NW__" : false 12 | }, 13 | "rules": { 14 | /*"comma-dangle" : [1, "never"], 15 | "func-names" : 0, 16 | "key-spacing" : 0, 17 | "space-before-function-paren" : [2, "always"], 18 | "no-else-return" : 0, 19 | "no-multi-spaces" : 0, 20 | "quotes" : [2, "single"], 21 | "jsx-quotes" : [2, "prefer-single"], 22 | "one-var" : 0,*/ 23 | "no-console": 0, 24 | "func-names": 0, 25 | "react/sort-comp": 0, 26 | "comma-dangle": 1, 27 | "semi" : [2, "never"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | npm-debug.log 4 | /webpack/lib 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dax Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Note: Still under active development 2 | # meteor-react-redux-starter-kit 3 | Get started with Meteor, React, Redux, Webpack, and React-Router! 4 | 5 | Embrace the react-redux ecosystem while using meteor's awesome realtime magic 6 | 7 | The meteor and webpack integration settings are basically forked from [jedwards1211/meteor-webpack-react](https://github.com/jedwards1211/meteor-webpack-react)'s `webpack-meteor-tools` branch, definitely check out the README there before continuing. 8 | 9 | Redux store settings and project structure are inspired from these great repos 10 | 11 | 1. [davezuko/react-redux-starter-kit](https://github.com/davezuko/react-redux-starter-kit) 12 | 1. [erikras/react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example) 13 | 1. [erikras/ducks-modular-redux](erikras/ducks-modular-redux): Redux folder structuring 14 | 15 | # Packages 16 | 17 | ## build 18 | - webpack 19 | 20 | ## react family 21 | - react + react-dom 22 | - react-router 23 | - react-bootstrap 24 | - material-ui 25 | 26 | ## redux family 27 | - redux 28 | - react-redux 29 | - redux-simple-router 30 | - redux-devtools 31 | - redux-thunk 32 | 33 | 34 | (below TODO, not yet added) 35 | - reselect 36 | - immutable.js 37 | 38 | 39 | # Why 40 | 41 | // TODO: keeping ui and logic decoupled makes predictable and scalable 42 | 43 | --- 44 | 45 | # Getting Started 46 | 47 | First clone this repo and install dependencies: 48 | ``` 49 | git clone https://github.com/YuHuaiChen/meteor-react-redux-starter-kit.git 50 | cd meteor-react-redux-starter-kit 51 | npm install 52 | ``` 53 | 54 | ## Run in development mode 55 | 56 | ``` 57 | npm start 58 | ``` 59 | 60 | Go to `http://localhost:3000`, and press `ctrl+h` to see the awesome Redux-DevTools 61 | 62 | ## Run in production mode 63 | 64 | ``` 65 | npm run prod 66 | ``` 67 | 68 | ## Meteor Settings 69 | 70 | 71 | ### API TODOs 72 | 73 | checkout [hackathon-starter#Obtaining API Keys](https://github.com/sahat/hackathon-starter#obtaining-api-keys) for lots of API guides 74 | 75 | 1. Google Login 76 | 1. Facebook Login 77 | 78 | // TODO 79 | 80 | 1. filepicker 81 | 1. emails 82 | 83 | # More Detailed Guides 84 | 85 | // TODO... 86 | 87 | # Deployment 88 | 89 | # Goal 90 | 91 | ## Depend on Meteor as less as possible 92 | 93 | The goal for this starter kit is to depend on Meteor as less as possible, 94 | and embrace the whole React ecosystem. 95 | 96 | ## Trade-off between complexity and flexibility 97 | 98 | ## Trade-off between unopinionated and completeness 99 | 100 | # RoadMap 101 | 102 | 1. upgrade to Babel 6 103 | 1. email 104 | 1. testing 105 | 1. guides for beginners like hackathon-starter 106 | -------------------------------------------------------------------------------- /app/actions/currentUser.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaxChen/meteor-react-redux-starter-kit/e34ed6cd31ad10a6e78945c7652f91d445496102/app/actions/currentUser.js -------------------------------------------------------------------------------- /app/collections/index.js: -------------------------------------------------------------------------------- 1 | /* global Mongo, Meteor */ 2 | export const Users = Meteor.users; 3 | export const Posts = new Mongo.Collection('posts'); 4 | -------------------------------------------------------------------------------- /app/components/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router' 3 | 4 | const About = () => { 5 | return ( 6 |
7 |

About

8 | Back to Home 9 |
10 | ) 11 | } 12 | 13 | export default About 14 | -------------------------------------------------------------------------------- /app/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../styles/core.scss'; 3 | 4 | const App = ({children}) => { 5 | return ( 6 |
7 | {children} 8 |
9 | ); 10 | }; 11 | 12 | App.propTypes = { 13 | children: React.PropTypes.element, 14 | }; 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /app/components/Landing.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | // import { Link } from 'react-router'; 3 | 4 | const Landing = () => { 5 | return ( 6 |
7 |

YOUR MAIN SLOGAN

8 |

Here's an one-line introduction to impress people!

9 |
10 | ) 11 | } 12 | export default Landing 13 | -------------------------------------------------------------------------------- /app/components/NotFound.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | export default class Landing extends Component { 5 | render() { 6 | return ( 7 |
8 |

OOPS! 404 Not Found

9 |

The requested URL was not found on this server.

10 | Back To Home Page 11 |
12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/components/SecretPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const SecretPage = () => { 4 | return ( 5 |
6 |

This is a secret page! Require login to view!

7 |
8 | ) 9 | } 10 | 11 | export default SecretPage 12 | -------------------------------------------------------------------------------- /app/components/index.js: -------------------------------------------------------------------------------- 1 | export App from './App' 2 | export About from './About' 3 | export Landing from './Landing' 4 | export NotFound from './NotFound' 5 | export SecretPage from './SecretPage' 6 | -------------------------------------------------------------------------------- /app/components/tests/app_spec.js: -------------------------------------------------------------------------------- 1 | // See this for some tips with Meteor: https://medium.com/all-about-meteorjs/unit-test-react-components-in-meteor-a19d96684d7d 2 | 3 | // If you import ../App you'll have to stub out Meteor, this is 4 | // why it's important to use controller-views that just setup 5 | // data and then the children can be very easily tested with 6 | // just props and state. We'll use a local component for an example 7 | 8 | import React from 'react/addons'; 9 | //import App from '../App'; 10 | //import $ from 'jquery'; // you could use jq to make life easier 11 | const TestUtils = React.addons.TestUtils; 12 | const Simulate = TestUtils.Simulate; 13 | 14 | // these should go into a spec helper module 15 | 16 | function renderComponent(comp, props) { 17 | return TestUtils.renderIntoDocument( 18 | React.createElement(comp, props) 19 | ); 20 | } 21 | 22 | function simulateClickOn(selector) { 23 | var button = this.$el.find(selector)[0]; 24 | React.addons.TestUtils.Simulate.click(button); 25 | } 26 | 27 | 28 | const Post = React.createClass({ 29 | getDefaultProps() { 30 | return {title: "Default Post Name"}; 31 | }, 32 | getInitialState() { 33 | return { isVisible: true }; 34 | }, 35 | handleHide() { 36 | this.setState({isVisible: false}); 37 | }, 38 | render() { 39 | let visibleClass = (this.state.isVisible) ? 'block' : 'hidden'; 40 | return ( 41 |
42 |

{this.props.title}

43 |
44 | How now brown cow 45 |
46 | 47 |
48 | ); 49 | } 50 | }); 51 | 52 | 53 | describe('Sample post component', () => { 54 | it('renders default post name without props', () => { 55 | let comp = renderComponent(Post, {}); 56 | expect(comp.props.title).toEqual('Default Post Name'); 57 | }); 58 | 59 | it('renders correct post name with a name prop', () => { 60 | let comp = renderComponent(Post, {title: "Webpack is awesome!"}); 61 | expect(comp.props.title).toEqual("Webpack is awesome!"); 62 | }); 63 | 64 | it("should have a default state of visible", () => { 65 | let comp = renderComponent(Post, {}); 66 | expect(comp.state.isVisible).toEqual(true); 67 | }); 68 | 69 | it("should hide when hide button is clicked", () => { 70 | let comp = renderComponent(Post, {}); 71 | comp.handleHide(); 72 | expect(comp.state.isVisible).toEqual(false); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /app/containers/DevTools.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | // Exported from redux-devtools 4 | import { createDevTools } from 'redux-devtools' 5 | 6 | // Monitors are separate packages, and you can make a custom one 7 | import LogMonitor from 'redux-devtools-log-monitor' 8 | import DockMonitor from 'redux-devtools-dock-monitor' 9 | 10 | // createDevTools takes a monitor and produces a DevTools component 11 | const DevTools = createDevTools( 12 | // Monitors are individually adjustable with props. 13 | // Consult their repositories to learn about those props. 14 | // Here, we put LogMonitor inside a DockMonitor. 15 | 18 | 19 | 20 | ) 21 | 22 | export default DevTools 23 | -------------------------------------------------------------------------------- /app/containers/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { Link } from 'react-router' 3 | import { connect } from 'react-redux' 4 | import { bindActionCreators } from 'redux' 5 | import { pushPath } from 'redux-simple-router' 6 | import { 7 | AppBar, 8 | RaisedButton, 9 | Avatar, 10 | FlatButton, 11 | Divider, 12 | FontIcon, 13 | } from 'material-ui/lib' 14 | import IconMenu from 'material-ui/lib/menus/icon-menu' 15 | import MenuItem from 'material-ui/lib/menus/menu-item' 16 | import { logout } from '../redux/modules/auth' 17 | 18 | @connect( 19 | state => ({ auth: state.auth }), 20 | dispatch => bindActionCreators({ 21 | logout, 22 | pushPath, 23 | }, dispatch)) 24 | export default class Header extends Component { 25 | static propTypes = { 26 | auth: PropTypes.object.isRequired, 27 | logout: PropTypes.func.isRequired, 28 | pushPath: PropTypes.func.isRequired, 29 | } 30 | 31 | renderLoginProfile() { 32 | // console.log('[Header]: loggedIn: ' + this.props.auth.user) 33 | const { props } = this 34 | if (props.auth.user) { 35 | return ( 36 | 40 | } /> 41 | 42 | }> 43 | } 46 | primaryText="Profile" 47 | leftIcon={ 48 | people 49 | } /> 50 | } 53 | primaryText="Settings" leftIcon={ 54 | settings 55 | } /> 56 | 57 | exit_to_app 59 | } /> 60 | 61 | ) 62 | } 63 | return ( 64 | } 66 | linkButton 67 | primary 68 | label="Sign up or Login" 69 | /> 70 | ) 71 | } 72 | 73 | render() { 74 | return ( 75 | BrandName 78 | } 79 | showMenuIconButton={false} 80 | iconElementRight={this.renderLoginProfile()} 81 | /> 82 | ) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/containers/Login.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { Link } from 'react-router' 3 | import { connect } from 'react-redux' 4 | import { bindActionCreators } from 'redux' 5 | import { pushPath, replacePath } from 'redux-simple-router' 6 | import { 7 | signUpWithPassword, 8 | loginWithPassword, 9 | loginWithGoogle, 10 | loginWithFacebook, 11 | } from '../redux/modules/auth' 12 | // react-bootstrap 13 | import { 14 | Grid, 15 | Row, 16 | Col, 17 | Image, 18 | } from 'react-bootstrap' 19 | import { 20 | TextField, 21 | Card, 22 | CardTitle, 23 | CardText, 24 | RaisedButton, 25 | Divider, 26 | } from 'material-ui/lib' 27 | import Colors from 'material-ui/lib/styles/colors' 28 | 29 | @connect( 30 | state => ({ auth: state.auth }), 31 | dispatch => bindActionCreators({ 32 | signUpWithPassword, 33 | loginWithPassword, 34 | loginWithGoogle, 35 | loginWithFacebook, 36 | pushPath, 37 | replacePath, 38 | }, dispatch)) 39 | export default class Login extends Component { 40 | static propTypes = { 41 | route: PropTypes.object.isRequired, 42 | location: PropTypes.object.isRequired, 43 | pushPath: PropTypes.func.isRequired, 44 | replacePath: PropTypes.func, 45 | signUpWithPassword: PropTypes.func.isRequired, 46 | loginWithGoogle: PropTypes.func.isRequired, 47 | loginWithFacebook: PropTypes.func.isRequired, 48 | loginWithPassword: PropTypes.func.isRequired, 49 | auth: PropTypes.object.isRequired, 50 | } 51 | static defaultProps = { 52 | 'route.isLogin': true, 53 | } 54 | 55 | componentWillMount() { 56 | const { auth, location } = this.props 57 | if (auth.user) { 58 | if (location.state && location.state.nextPathname) { 59 | this.props.replacePath(location.state.nextPathname) 60 | } else { 61 | this.props.replacePath('/') 62 | } 63 | } 64 | } 65 | componentDidMount() { 66 | const focusEl = this.props.route.isLogin ? this.refs.user : this.refs.username 67 | focusEl.focus() 68 | } 69 | componentWillReceiveProps(nextProps) { 70 | const { location } = this.props 71 | if (nextProps.auth.user) { 72 | if (location.state && location.state.nextPathname) { 73 | this.props.pushPath(location.state.nextPathname) 74 | } else { 75 | this.props.pushPath('/') 76 | } 77 | } 78 | } 79 | componentDidUpdate() { 80 | const focusEl = this.props.route.isLogin ? this.refs.user : this.refs.username 81 | focusEl.focus() 82 | } 83 | 84 | handleSubmit(e) { 85 | e.preventDefault() 86 | if (this.props.route.isLogin) { 87 | const user = this.refs.user.getValue() 88 | const password = this.refs.password.getValue() 89 | this.props.loginWithPassword(user, password) 90 | } else { 91 | const username = this.refs.username.getValue() 92 | const email = this.refs.email.getValue() 93 | const password = this.refs.password.getValue() 94 | this.props.signUpWithPassword({ username, email, password }) 95 | } 96 | } 97 | 98 | renderForm() { 99 | return this.props.route.isLogin ? 100 | ( 101 |
102 | 107 | 113 | 119 | 120 | ) : 121 | ( 122 |
123 | 129 | 134 | 140 | 146 | 147 | ) 148 | } 149 | 150 | renderDivider() { 151 | return ( 152 | 153 | 154 | 155 | 156 | 157 | ) 158 | } 159 | 160 | render() { 161 | const { isLogin } = this.props.route 162 | return ( 163 | 164 | 165 | 166 | 168 | 169 | 170 | 171 | 172 | 173 | {this.props.auth.needLoginMsg && 174 | 175 | 176 |
You need to login to continue...
177 | 178 |
179 | } 180 | 181 | 182 | 183 | 184 | 188 | 189 | { /* Social Login */ } 190 | 191 | 192 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 214 | 215 | 216 | 217 | 218 | 219 | { this.renderDivider() } 220 | 221 | { /* Password Login */ } 222 | 223 | 224 | 225 | { this.renderForm() } 226 | 227 | 228 | 229 | 230 | { this.renderDivider() } 231 | 232 | 233 | 234 | 235 | { isLogin ? 236 | Don't have an account yet? Create One! 237 | : 238 | Already have an account? Login! 239 | } 240 | 241 | 242 | 243 | 244 | 245 | 246 |
247 | ) 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /app/containers/Profile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | // import { Link } from 'react-router'; 4 | 5 | @connect() 6 | export default class Profile extends Component { 7 | static propTypes = { 8 | } 9 | 10 | componentWillMount() { 11 | // fetch data 12 | } 13 | 14 | render() { 15 | return ( 16 |
17 |

Profile Page

18 |
19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/containers/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux' 3 | import { Router } from 'react-router' 4 | import getRoutes from '../routes' 5 | import DevTools from './DevTools' 6 | 7 | export default class Root extends React.Component { 8 | static propTypes = { 9 | history: React.PropTypes.object.isRequired, 10 | store: React.PropTypes.object.isRequired, 11 | debug: React.PropTypes.bool, 12 | } 13 | static defaultProps = { 14 | debug: false, 15 | } 16 | 17 | render() { 18 | return ( 19 | 20 |
21 | 22 | { getRoutes(this.props.store) } 23 | 24 | {this.props.debug ? : null} 25 |
26 |
27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/containers/Settings.js: -------------------------------------------------------------------------------- 1 | import React, { Component/* , PropTypes*/ } from 'react' 2 | // import { Link } from 'react-router' 3 | import { connect } from 'react-redux' 4 | // import { bindActionCreators } from 'redux' 5 | // import { pushPath, replacePath } from 'redux-simple-router' 6 | import { 7 | } from '../redux/modules/auth' 8 | // react-bootstrap 9 | import { 10 | Grid, 11 | Row, 12 | Col, 13 | // Image, 14 | } from 'react-bootstrap' 15 | import { 16 | // TextField, 17 | // Card, 18 | // CardTitle, 19 | // CardText, 20 | // RaisedButton, 21 | // Divider, 22 | } from 'material-ui/lib' 23 | // import Colors from 'material-ui/lib/styles/colors' 24 | 25 | @connect() 26 | export default class Settings extends Component { 27 | // static propTypes = { 28 | // } 29 | 30 | render() { 31 | return ( 32 | 33 | 34 | 35 |

Settings...

36 | 37 |
38 |
39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/containers/index.js: -------------------------------------------------------------------------------- 1 | export DevTools from './DevTools' 2 | export Header from './Header' 3 | export Login from './Login' 4 | export Profile from './Profile' 5 | export Root from './Root' 6 | export Settings from './Settings' 7 | -------------------------------------------------------------------------------- /app/layouts/CoreLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Header from '../containers/Header' 3 | 4 | const CoreLayout = ({children}) => { 5 | return ( 6 |
7 |
8 | {children} 9 |
10 | ) 11 | } 12 | 13 | CoreLayout.propTypes = { 14 | children: React.PropTypes.element, 15 | auth: React.PropTypes.object, 16 | } 17 | 18 | export default CoreLayout 19 | -------------------------------------------------------------------------------- /app/layouts/EmptyLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const EmptyLayout = ({children}) => { 4 | return ( 5 |
6 | {children} 7 |
8 | ); 9 | }; 10 | 11 | EmptyLayout.propTypes = { 12 | children: React.PropTypes.element, 13 | }; 14 | 15 | export default EmptyLayout; 16 | -------------------------------------------------------------------------------- /app/layouts/index.js: -------------------------------------------------------------------------------- 1 | export CoreLayout from './CoreLayout' 2 | export EmptyLayout from './EmptyLayout' 3 | -------------------------------------------------------------------------------- /app/main_client.js: -------------------------------------------------------------------------------- 1 | // this is the entry point of client 2 | import React from 'react' 3 | import ReactDOM from 'react-dom' 4 | import createBrowserHistory from 'history/lib/createBrowserHistory' 5 | import { syncReduxAndRouter } from 'redux-simple-router' 6 | import Root from './containers/Root' 7 | import configureStore from './redux/configureStore' 8 | import { viewerChanged } from './redux/modules/auth' 9 | // import { user } from './meteor-redux' 10 | 11 | // TODO set these globals with webpack 12 | window.__DEBUG__ = true 13 | 14 | const injectTapEventPlugin = require('react-tap-event-plugin') 15 | // Needed for onTouchTap 16 | // Can go away when react 1.0 release 17 | // Check this repo: 18 | // https://github.com/zilverline/react-tap-event-plugin 19 | injectTapEventPlugin() 20 | 21 | // react render 22 | Meteor.startup(() => { 23 | const target = document.getElementById('root') 24 | const history = createBrowserHistory() 25 | const store = configureStore(window.__INITIAL_STATE__, __DEBUG__) 26 | 27 | syncReduxAndRouter(history, store) 28 | 29 | // auto update currentUser using skinnygeek1010:flux-helpers 30 | trackViewer((newDocs) => { 31 | store.dispatch(viewerChanged(newDocs)) 32 | }) 33 | // dispatch once 34 | // console.log('manual call viewerChanged') 35 | store.dispatch(viewerChanged(Meteor.user())) 36 | 37 | const node = ( 38 | 43 | ) 44 | 45 | ReactDOM.render(node, target) 46 | 47 | if (__DEBUG__) { 48 | window.store = store 49 | } 50 | }) 51 | -------------------------------------------------------------------------------- /app/main_server.js: -------------------------------------------------------------------------------- 1 | import './method_example' 2 | import initUser from './server/init-user' 3 | 4 | Meteor.startup(() => { 5 | initUser() 6 | 7 | if (Meteor.settings.google) { 8 | ServiceConfiguration.configurations.upsert({ 9 | service: 'google', 10 | }, { 11 | $set: Meteor.settings.google, 12 | }) 13 | } 14 | if (Meteor.settings.facebook) { 15 | ServiceConfiguration.configurations.upsert({ 16 | service: 'facebook', 17 | }, { 18 | $set: Meteor.settings.facebook, 19 | }) 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /app/method_example.js: -------------------------------------------------------------------------------- 1 | // if you import this on the client, it will be a stub 2 | // if you import this on the server, it will be the real method 3 | // use Meteor.call as normal to consume it. 4 | 5 | /* global Meteor */ 6 | 7 | Meteor.methods({ 8 | sayHello() { 9 | return 'Hello from Meteor method!'; 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /app/redux/configureStore.js: -------------------------------------------------------------------------------- 1 | import rootReducer from './modules/reducer'; 2 | import thunk from 'redux-thunk'; 3 | import DevTools from 'containers/DevTools'; 4 | import { applyMiddleware, compose, createStore } from 'redux'; 5 | 6 | export default function configureStore(initialState, debug = false) { 7 | let createStoreWithMiddleware; 8 | 9 | const middleware = applyMiddleware(thunk); 10 | 11 | if (debug) { 12 | createStoreWithMiddleware = compose( 13 | middleware, 14 | DevTools.instrument() 15 | ); 16 | } else { 17 | createStoreWithMiddleware = compose(middleware); 18 | } 19 | 20 | const store = createStoreWithMiddleware(createStore)( 21 | rootReducer, initialState 22 | ); 23 | if (module.hot) { 24 | module.hot.accept('./modules/reducer', () => { 25 | const nextRootReducer = require('./modules/reducer'); 26 | 27 | store.replaceReducer(nextRootReducer); 28 | }); 29 | } 30 | return store; 31 | } 32 | -------------------------------------------------------------------------------- /app/redux/modules/auth.js: -------------------------------------------------------------------------------- 1 | import { extend } from 'lodash' 2 | import { replacePath } from 'redux-simple-router' 3 | 4 | /** 5 | * uses "Ducks: Redux Reducer Bundles", check out: 6 | * https://github.com/erikras/ducks-modular-redux 7 | * on more how to structure your reducers 8 | */ 9 | const VIEWER_CHANGED = 'my-app/auth/VIEWER_CHANGED' 10 | const LOGIN_ERROR = 'my-app/auth/LOGIN_ERROR' 11 | const LOGOUT_ERROR = 'my-app/auth/LOGOUT_ERROR' 12 | const SIGN_UP_ERROR = 'my-app/auth/SIGN_UP_ERROR' 13 | const SHOW_NEED_LOGIN_MSG = 'my-app/auth/SHOW_NEED_LOGIN_MSG' 14 | 15 | // helper to *copy* old state and merge new data with it 16 | function merge(oldState, newState) { 17 | return extend({}, oldState, newState) 18 | } 19 | 20 | 21 | const initialState = { 22 | user: null, 23 | errMsg: null, 24 | needLoginMsg: false, 25 | } 26 | 27 | export default function reducer(state = initialState, action) { 28 | switch (action.type) { 29 | case VIEWER_CHANGED: 30 | return merge(state, { 31 | user: action.user, 32 | errMsg: null, 33 | needLoginMsg: false, 34 | }) 35 | case LOGIN_ERROR: 36 | case LOGOUT_ERROR: 37 | case SIGN_UP_ERROR: 38 | return merge(state, { errMsg: action.err }) 39 | case SHOW_NEED_LOGIN_MSG: 40 | return merge(state, { needLoginMsg: true }) 41 | default: 42 | return state 43 | } 44 | } 45 | 46 | export function viewerChanged(newDocs) { 47 | // console.log('viewerChanged') 48 | return { 49 | type: VIEWER_CHANGED, 50 | user: newDocs, 51 | } 52 | } 53 | 54 | export function loginError(err) { 55 | return { 56 | type: LOGIN_ERROR, 57 | err, 58 | } 59 | } 60 | export function loginWithPassword(user, password) { 61 | // console.log('loginWithPassword action called') 62 | return dispatch => { 63 | // console.log('now logging in...') 64 | Meteor.loginWithPassword(user, password, err => { 65 | if (err) { 66 | alert('Error while login with password: ' + err.message) 67 | dispatch(loginError(err.message)) 68 | // } else { 69 | // console.log('login success!!!') 70 | // dispatch(loggedIn()) 71 | } 72 | }) 73 | } 74 | } 75 | export function loginWithFacebook() { 76 | return dispatch => { 77 | Meteor.loginWithFacebook(err => { 78 | if (err) { 79 | alert('Error while login with facebook: ' + err.message) 80 | dispatch(loginError(err.message)) 81 | // } else { 82 | // dispatch(loggedIn()) 83 | } 84 | }) 85 | } 86 | } 87 | export function loginWithGoogle() { 88 | return dispatch => { 89 | Meteor.loginWithGoogle(err => { 90 | if (err) { 91 | alert('Error while login with google: ' + err.message) 92 | dispatch(loginError(err.message)) 93 | // } else { 94 | // dispatch(loggedIn()) 95 | } 96 | }) 97 | } 98 | } 99 | 100 | export function logoutError(err) { 101 | return { 102 | type: LOGOUT_ERROR, 103 | err, 104 | } 105 | } 106 | 107 | export function logout() { 108 | return dispatch => { 109 | Meteor.logout(err => { 110 | if (err) { 111 | alert('Error while logging out: ' + err.message) 112 | dispatch(logoutError(err)) 113 | } 114 | }) 115 | } 116 | } 117 | 118 | export function logoutAndRedirectHome() { 119 | return dispatch => { 120 | Meteor.logout(err => { 121 | if (err) { 122 | dispatch(logoutError(err)) 123 | } else { 124 | dispatch(replacePath('/')) 125 | } 126 | }) 127 | } 128 | } 129 | 130 | export function signUpError(err) { 131 | return { 132 | type: SIGN_UP_ERROR, 133 | err, 134 | } 135 | } 136 | export function signUpWithPassword({username, email, password}) { 137 | return dispatch => { 138 | Accounts.createUser({username, email, password}, err => { 139 | if (err) { 140 | alert('Error while creating Account: ' + err.message) 141 | dispatch(signUpError(err.message)) 142 | // } else { 143 | // dispatch() 144 | } 145 | }) 146 | } 147 | } 148 | 149 | export function showNeedLoginMsg() { 150 | return { 151 | type: SHOW_NEED_LOGIN_MSG, 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/redux/modules/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routeReducer } from 'redux-simple-router'; 3 | import auth from './auth'; 4 | 5 | export default combineReducers({ 6 | auth, 7 | routing: routeReducer, 8 | }); 9 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, IndexRoute } from 'react-router' 3 | import { CoreLayout, EmptyLayout } from '../layouts' 4 | import { 5 | Login, 6 | Profile, 7 | Settings, 8 | } from '../containers' 9 | import { 10 | About, 11 | App, 12 | Landing, 13 | NotFound, 14 | SecretPage, 15 | } from '../components' 16 | import { 17 | logoutAndRedirectHome, 18 | showNeedLoginMsg, 19 | } from '../redux/modules/auth' 20 | 21 | export default function getRoutes(store) { 22 | /** 23 | * This is how you access redux store in react-router onEnter/onLeave Hooks 24 | * see this for more detail: 25 | * http://stackoverflow.com/questions/33643290/how-do-i-get-a-hold-of-the-store-dispatch-in-react-router-onenter 26 | */ 27 | function logoutOnEnterHook(nextState, replaceState) { 28 | if (store.getState().auth.user) { 29 | // logout the user, and redirect to '/' while success 30 | store.dispatch(logoutAndRedirectHome()) 31 | } else { 32 | // the user hasn't login yet 33 | replaceState(null, '/') 34 | } 35 | } 36 | 37 | function requireAuth(nextState, replaceState) { 38 | if (!store.getState().auth.user) { 39 | store.dispatch(showNeedLoginMsg()) 40 | replaceState({ nextPathname: nextState.location.pathname }, '/login') 41 | } 42 | } 43 | 44 | return ( 45 | 46 | {/* Layout No.1 */} 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {/* Layout No.2 */} 56 | 57 | 58 | 59 | 60 | 61 | 62 | {/* Catch app path for Not Found */} 63 | 64 | 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /app/server/init-user.js: -------------------------------------------------------------------------------- 1 | export default function initUser() { 2 | // fixture 3 | if (Meteor.users.find().count() === 0) { 4 | const userObject = { 5 | username: 'dd', 6 | mail: 'dd@dd', 7 | password: '123123', 8 | profile: { 9 | name: 'ddd', 10 | }, 11 | } 12 | Accounts.createUser(userObject) 13 | } 14 | 15 | // Accounts.config({ 16 | // sendVerificationEmail: true, 17 | // }) 18 | 19 | // Validate username, sending a specific error message on failure. 20 | Accounts.validateNewUser(user => { 21 | if (user.username && user.username.length >= 3) { 22 | return true 23 | } 24 | if (user.services.facebook || user.services.google) { 25 | return true 26 | } 27 | throw new Meteor.Error(403, 'Username must have at least 3 characters') 28 | }) 29 | 30 | Accounts.onCreateUser((options, user) => { 31 | if (options.profile) { 32 | user.profile = options.profile 33 | } else { 34 | user.profile = { 35 | name: user.username, 36 | } 37 | } 38 | 39 | // facebook login: 40 | if (user.services) { 41 | if (user.services.facebook) { 42 | user.emails = [ 43 | { address: user.services.facebook.email, verified: true }, 44 | ] 45 | } else if (user.services.google) { 46 | user.emails = [ 47 | { address: user.services.google.email, verified: true }, 48 | ] 49 | } 50 | } 51 | 52 | return user 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /app/styles/_base.scss: -------------------------------------------------------------------------------- 1 | // Settings go here 2 | -------------------------------------------------------------------------------- /app/styles/core.scss: -------------------------------------------------------------------------------- 1 | @import 'base'; 2 | @import 'vendor/normalize'; 3 | -------------------------------------------------------------------------------- /app/styles/vendor/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } 428 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaxChen/meteor-react-redux-starter-kit/e34ed6cd31ad10a6e78945c7652f91d445496102/assets/logo.png -------------------------------------------------------------------------------- /core-js-custom-build.js: -------------------------------------------------------------------------------- 1 | require('shelljs/global'); 2 | var dirs = require('./dirs'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | dirs.lib = path.join(dirs.webpack, 'lib'); 7 | if (!fs.existsSync(dirs.lib)) mkdir(dirs.lib); 8 | 9 | 10 | var coreJsVersion = JSON.parse(fs.readFileSync('node_modules/core-js/package.json')).version; 11 | var targetFile = 'core-js-no-number-' + coreJsVersion + '.js'; 12 | 13 | if (!fs.existsSync(path.join(dirs.lib, targetFile))) { 14 | echo('Building core-js@' + coreJsVersion + ' without ES6 number constructor...'); 15 | cd('node_modules/core-js'); 16 | exec('npm install'); 17 | cd(__dirname); 18 | exec('npm run build-core-js'); 19 | cd('node_modules/core-js'); 20 | mv('core-js-no-number.js', path.join(dirs.lib, targetFile)); 21 | rm('-rf', 'node_modules'); 22 | cd(dirs.lib); 23 | ln('-sf', targetFile, 'core-js-no-number.js'); 24 | } 25 | else { 26 | echo('core-js@' + coreJsVersion + ' without ES6 number constructor is up to date'); 27 | } 28 | -------------------------------------------------------------------------------- /debug.js: -------------------------------------------------------------------------------- 1 | require('shelljs/global'); 2 | exec('node-inspector', {async: true}); 3 | env.NODE_OPTIONS = '--debug=5858'; 4 | require('./dev'); 5 | -------------------------------------------------------------------------------- /deploy.js: -------------------------------------------------------------------------------- 1 | require('shelljs/global'); 2 | var path = require('path'); 3 | 4 | if (!process.argv[2]) { 5 | echo('See ' + path.basename(__filename) + ' to customize your deploy command'); 6 | return; 7 | } 8 | 9 | var projectName = require('./projectName'); 10 | if (!projectName) { 11 | echo('Please enter your project name in projectName.js'); 12 | } 13 | 14 | var dirs = require('./dirs'); 15 | 16 | echo(); 17 | echo('Building Webpack bundles for deployment...'); 18 | echo(); 19 | require('./predeploy')(function(err) { 20 | if (err) exit(1); 21 | deploy(); 22 | }); 23 | 24 | function deploy() { 25 | switch (process.argv[2]) { 26 | 27 | case 'meteor.com': 28 | cd(dirs.meteor); 29 | exec('meteor deploy ' + projectName + '.meteor.com', {async: true}); 30 | break; 31 | 32 | case 'modulus': 33 | env.METEOR_SETTINGS = cat('settings/prod.json'); 34 | cd(dirs.meteor); 35 | exec('modulus deploy --project-name ' + projectName, {async: true}); 36 | break; 37 | 38 | case 'mup': 39 | echo("Make sure to mup init and mup setup before first deploy"); 40 | /* 41 | * you will also need to move settings/prod.json to settings/prod/settings.json 42 | * then mup init inside settings/prod/ so that mup uses the new settings.json 43 | * this will require a settings path change in ./dev script 44 | */ 45 | cd('settings/prod'); 46 | exec('mup deploy', {async: true}); 47 | break; 48 | 49 | case 'demeteorizer': 50 | rm('-rf', 'dist/bundle'); 51 | mkdir('-p', 'dist/bundle'); 52 | cd(dirs.meteor); 53 | exec("demeteorizer -o ../dist/bundle --json '" + cat('../settings/prod.json') + "'", {async: true}); 54 | // run your own command to deploy to your server 55 | break; 56 | 57 | default: 58 | echo('See ' + path.basename(__filename) + ' to customize your deploy command'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /dev.js: -------------------------------------------------------------------------------- 1 | require('shelljs/global'); 2 | var dirs = require('./dirs'); 3 | 4 | require('./core-js-custom-build'); 5 | 6 | require('./runWebpackConfigs')('dev', function(err) { 7 | if (err) throw err; 8 | cd(dirs.meteor); 9 | exec('meteor --settings ../settings/devel.json', {async: true}); 10 | }); 11 | -------------------------------------------------------------------------------- /dirs.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | webpack: path.join(__dirname, 'webpack'), 5 | meteor: path.join(__dirname, 'meteor_core'), 6 | }; 7 | 8 | module.exports.assets= path.join(module.exports.webpack, 'assets'); 9 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpackConfig = require('./webpack/webpack.config.client.js'); 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | //singleRun: true, 7 | reporters: [ 'dots' ], 8 | browsers: [ 'Chrome' ], 9 | files: [ './test/karma.bundle.js' ], 10 | frameworks: [ 'jasmine' ], 11 | plugins: [ 12 | 'karma-chrome-launcher', 13 | //'karma-firefox-launcher', 14 | 'karma-jasmine', 15 | //'karma-mocha', 16 | 'karma-sourcemap-loader', 17 | 'karma-webpack', 18 | ], 19 | // run the bundle through the webpack and sourcemap plugins 20 | preprocessors: { 21 | './test/karma.bundle.js': [ 'webpack', 'sourcemap' ] 22 | }, 23 | // use our own webpack config to mirror test setup 24 | webpack: { 25 | entry: [ 26 | './lib/core-js-no-number', 27 | 'regenerator/runtime', 28 | ], 29 | devtool: 'eval-source-map', 30 | resolve: webpackConfig.resolve, 31 | module: { loaders: webpackConfig.module.loaders }, 32 | }, 33 | webpackMiddleware: { 34 | noInfo: true, 35 | } 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /met: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd meteor_core && meteor $@ 3 | -------------------------------------------------------------------------------- /meteor_core/.gitignore: -------------------------------------------------------------------------------- 1 | .webpack-assets* -------------------------------------------------------------------------------- /meteor_core/.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_core/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /meteor_core/.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 | 1kkvpljtq0j5s32te4b 8 | -------------------------------------------------------------------------------- /meteor_core/.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 | accounts-password 8 | babel-runtime 9 | meteor-base 10 | mobile-experience 11 | mongo 12 | session 13 | tracker 14 | logging 15 | reload 16 | random 17 | ejson 18 | check 19 | mindfront:why-reminify 20 | static-html 21 | fortawesome:fontawesome 22 | accounts-google 23 | service-configuration 24 | accounts-facebook 25 | skinnygeek1010:flux-helpers 26 | -------------------------------------------------------------------------------- /meteor_core/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /meteor_core/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.2.1 2 | -------------------------------------------------------------------------------- /meteor_core/.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.2 2 | accounts-facebook@1.0.6 3 | accounts-google@1.0.6 4 | accounts-oauth@1.1.8 5 | accounts-password@1.1.4 6 | autoupdate@1.2.4 7 | babel-compiler@5.8.24_1 8 | babel-runtime@0.1.4 9 | base64@1.0.4 10 | binary-heap@1.0.4 11 | blaze@2.1.3 12 | blaze-tools@1.0.4 13 | boilerplate-generator@1.0.4 14 | caching-compiler@1.0.0 15 | caching-html-compiler@1.0.2 16 | callback-hook@1.0.4 17 | check@1.1.0 18 | ddp@1.2.2 19 | ddp-client@1.2.1 20 | ddp-common@1.2.2 21 | ddp-rate-limiter@1.0.0 22 | ddp-server@1.2.2 23 | deps@1.0.9 24 | diff-sequence@1.0.1 25 | ecmascript@0.1.6 26 | ecmascript-runtime@0.2.6 27 | ejson@1.0.7 28 | email@1.0.8 29 | facebook@1.2.2 30 | fastclick@1.0.7 31 | fortawesome:fontawesome@4.5.0 32 | geojson-utils@1.0.4 33 | google@1.1.7 34 | hot-code-push@1.0.0 35 | html-tools@1.0.5 36 | htmljs@1.0.5 37 | http@1.1.1 38 | id-map@1.0.4 39 | jquery@1.11.4 40 | launch-screen@1.0.4 41 | livedata@1.0.15 42 | localstorage@1.0.5 43 | logging@1.0.8 44 | meteor@1.1.10 45 | meteor-base@1.0.1 46 | mindfront:why-reminify@1.0.0 47 | minifiers@1.1.7 48 | minimongo@1.0.10 49 | mobile-experience@1.0.1 50 | mobile-status-bar@1.0.6 51 | mongo@1.1.3 52 | mongo-id@1.0.1 53 | npm-bcrypt@0.7.8_2 54 | npm-mongo@1.4.39_1 55 | oauth@1.1.6 56 | oauth2@1.1.5 57 | observe-sequence@1.0.7 58 | ordered-dict@1.0.4 59 | promise@0.5.1 60 | random@1.0.5 61 | rate-limit@1.0.0 62 | reactive-dict@1.1.3 63 | reactive-var@1.0.6 64 | reload@1.1.4 65 | retry@1.0.4 66 | routepolicy@1.0.6 67 | service-configuration@1.0.5 68 | session@1.1.1 69 | sha@1.0.4 70 | skinnygeek1010:flux-helpers@0.1.0 71 | spacebars@1.0.7 72 | spacebars-compiler@1.0.7 73 | srp@1.0.4 74 | static-html@1.0.3 75 | templating@1.1.5 76 | templating-tools@1.0.0 77 | tracker@1.0.9 78 | ui@1.0.8 79 | underscore@1.0.4 80 | url@1.0.5 81 | webapp@1.2.3 82 | webapp-hashing@1.0.5 83 | -------------------------------------------------------------------------------- /meteor_core/client/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /meteor_core/index.html: -------------------------------------------------------------------------------- 1 | 2 | Meteor-react-redux-starter-kit 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /meteor_core/server/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !cors.js 4 | -------------------------------------------------------------------------------- /meteor_core/server/cors.js: -------------------------------------------------------------------------------- 1 | WebApp.connectHandlers.use(function(req, res, next) { 2 | res.setHeader("Access-Control-Allow-Origin", "*"); 3 | return next(); 4 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meteor-react-redux-starter-kit", 3 | "version": "0.0.1", 4 | "description": "Get started using Meteor with React, Redux, Webpack and React-router", 5 | "repository": "YuHuaiChen/meteor-react-redux-starter-kit", 6 | "devDependencies": { 7 | "babel": "~5.8.21", 8 | "babel-core": "~5.8.22", 9 | "babel-eslint": "^4.0.5", 10 | "babel-loader": "^5.1.2", 11 | "babel-plugin-react-transform": "^1.0.5", 12 | "core-js": "^1.0.0", 13 | "css-loader": "^0.15.3", 14 | "eslint-config-airbnb": "0.0.7", 15 | "eslint-plugin-react": "^3.2.2", 16 | "grunt": "^0.4.5", 17 | "grunt-cli": "^0.1.13", 18 | "karma": "^0.13.9", 19 | "karma-chrome-launcher": "^0.2.0", 20 | "karma-jasmine": "^0.2.2", 21 | "karma-sourcemap-loader": "^0.3.5", 22 | "karma-webpack": "^1.7.0", 23 | "node-libs-browser": "^0.5.2", 24 | "postcss-loader": "^0.8.0", 25 | "react-tools": "^0.10.0", 26 | "react-transform": "0.0.3", 27 | "react-transform-catch-errors": "^1.0.0", 28 | "react-transform-hmr": "^1.0.1", 29 | "redbox-react": "^1.1.1", 30 | "redux-devtools": "^3.0.0", 31 | "redux-devtools-dock-monitor": "^1.0.1", 32 | "redux-devtools-log-monitor": "^1.0.1", 33 | "regenerator": "^0.8.36", 34 | "shelljs": "^0.5.3", 35 | "source-map-support": "^0.3.2", 36 | "style-loader": "^0.12.3", 37 | "webpack": "^1.12.0", 38 | "webpack-dev-server": "^1.12.0", 39 | "webpack-meteor-tools": "git://github.com/mindfront/webpack-meteor-tools.git#843f5a4b30c6bf268bb0fd1d2add7abcb60dabf9" 40 | }, 41 | "dependencies": { 42 | "classnames": "^2.1.3", 43 | "file-loader": "^0.8.5", 44 | "history": "^1.17.0", 45 | "lodash": "^3.10.0", 46 | "material-ui": "^0.14.0-rc2", 47 | "node-sass": "^3.4.2", 48 | "react": "^0.14.3", 49 | "react-bootstrap": "^0.28.1", 50 | "react-dom": "^0.14.3", 51 | "react-mixin": "^2.0.1", 52 | "react-redux": "^4.0.2", 53 | "react-router": "^1.0.2", 54 | "react-router-bootstrap": "^0.19.3", 55 | "react-tap-event-plugin": "^0.2.1", 56 | "redux": "^3.0.5", 57 | "redux-simple-router": "^1.0.1", 58 | "redux-thunk": "^1.0.2", 59 | "sass-loader": "^3.1.2", 60 | "url-loader": "^0.5.7" 61 | }, 62 | "scripts": { 63 | "start": "node dev.js", 64 | "prod": "node prod.js", 65 | "lint": "eslint app", 66 | "build-core-js": "grunt --gruntfile node_modules/core-js/Gruntfile.js build:es5,es6,es7,js,web --blacklist=es6.number.constructor --path=core-js-no-number" 67 | }, 68 | "author": "YuHuaiChen", 69 | "license": "MIT" 70 | } 71 | -------------------------------------------------------------------------------- /predeploy.js: -------------------------------------------------------------------------------- 1 | require('shelljs/global'); 2 | var runWebpackConfigs = require('./runWebpackConfigs'); 3 | 4 | require('./core-js-custom-build'); 5 | 6 | process.env.NODE_ENV = env.NODE_ENV = 'production'; 7 | 8 | module.exports = runWebpackConfigs.bind(undefined, 'deploy'); 9 | -------------------------------------------------------------------------------- /prod.js: -------------------------------------------------------------------------------- 1 | require('shelljs/global'); 2 | var dirs = require('./dirs'); 3 | 4 | require('./core-js-custom-build'); 5 | 6 | process.env.NODE_ENV = env.NODE_ENV = 'production'; 7 | require('./runWebpackConfigs')('prod', function(err) { 8 | if (err) throw err; 9 | cd(dirs.meteor); 10 | exec('meteor run --production --settings ../settings/prod.json', {async: true}); 11 | }); 12 | -------------------------------------------------------------------------------- /projectName.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | module.exports = path.basename(__dirname); // replace with your project name -------------------------------------------------------------------------------- /runWebpackConfigs.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var dirs = require('./dirs'); 4 | var runWebpackConfigs = require('webpack-meteor-tools/lib/runWebpackConfigs'); 5 | 6 | module.exports = function(mode, callback) { 7 | var modeRx = new RegExp('\\.' + mode + '\\.js$'); 8 | var configs = fs.readdirSync(dirs.webpack) 9 | .filter(modeRx.test.bind(modeRx)) 10 | .map(function(file) { 11 | return require(path.join(dirs.webpack, file)); 12 | }); 13 | return runWebpackConfigs(configs, callback); 14 | }; 15 | -------------------------------------------------------------------------------- /settings/.gitignore: -------------------------------------------------------------------------------- 1 | devel.json 2 | stage.json 3 | prod.json 4 | -------------------------------------------------------------------------------- /settings/devel.json: -------------------------------------------------------------------------------- 1 | { 2 | "public": { 3 | "env": "DEVEL", 4 | "foo": "bar" 5 | }, 6 | "google": { 7 | "clientId": "112427994311-7tp5l72hfctjrlajd5dd751nn1iqm0fr.apps.googleusercontent.com", 8 | "secret": "VXttMiFv8bmA0OVtGL8AMUgW" 9 | }, 10 | "facebook": { 11 | "appId": "899114126863033", 12 | "secret": "7f928ec94149e480206f1534c749dcfe" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /settings/prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "public": { 3 | "env": "PROD", 4 | "foo": "bar" 5 | }, 6 | "google": { 7 | "clientId": "112427994311-2a50jo0gfij46kbiumc6i690sjlvutqk.apps.googleusercontent.com", 8 | "secret": "ssM_OxeZzXsjGQbjX5GFPN_K" 9 | }, 10 | "facebook": { 11 | "appId": "899109836863462", 12 | "secret": "6cf661a11ea1297d18ea72be7c6c535e" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /settings/stage.json: -------------------------------------------------------------------------------- 1 | { 2 | "public": { 3 | "env": "STAGE", 4 | "foo": "bar" 5 | }, 6 | "google": { 7 | "clientId": "112427994311-pasmhqtscugd0kqst912q2qmviioogq0.apps.googleusercontent.com", 8 | "secret": "hk6vnGfp6f0eXaGZJkPE9cXT" 9 | }, 10 | "facebook": { 11 | "appId": "899115303529582", 12 | "secret": "96f432f1c1b3b153bf3c8518e4d8bfc3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/karma.bundle.js: -------------------------------------------------------------------------------- 1 | // require all foo_spec.js, bar_spec.jsx files in the app directory 2 | var context = require.context('../app', true, /.+_spec\.jsx?$/); 3 | context.keys().forEach(context); 4 | module.exports = context; 5 | -------------------------------------------------------------------------------- /webpack/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | assets 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /webpack/client/client.bundle.min.js: -------------------------------------------------------------------------------- 1 | /home/andy/meteor-webpack-react/webpack/server/client.bundle.js -------------------------------------------------------------------------------- /webpack/client/d2a2450bb4b434caf462a7dc6bd7749c.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaxChen/meteor-react-redux-starter-kit/e34ed6cd31ad10a6e78945c7652f91d445496102/webpack/client/d2a2450bb4b434caf462a7dc6bd7749c.jpeg -------------------------------------------------------------------------------- /webpack/devProps.js: -------------------------------------------------------------------------------- 1 | var host = '0.0.0.0'; 2 | var webpackPort = 9090; 3 | var meteorPort = 3000; 4 | 5 | module.exports = { 6 | host: host, 7 | webpackPort: webpackPort, 8 | meteorPort: meteorPort, 9 | baseUrl: 'http://' + host + ':' + webpackPort, 10 | contentBase: 'http://' + host + ':' + meteorPort, 11 | }; -------------------------------------------------------------------------------- /webpack/loadClientBundle.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /webpack/server/server.bundle.js: -------------------------------------------------------------------------------- 1 | var require=Npm.require;!function(t){function n(e){if(r[e])return r[e].exports;var o=r[e]={exports:{},id:e,loaded:!1};return t[e].call(o.exports,o,o.exports,n),o.loaded=!0,o.exports}var r={};return n.m=t,n.c=r,n.p="/server/",n(0)}([function(t,n,r){r(6),r(5),t.exports=r(3)},function(t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=Meteor.users;n.Users=r;var e=new Mongo.Collection("posts");n.Posts=e},function(t,n,r){"use strict";function e(){console.log("Creating fake posts"),[1,2,3,4].forEach(function(t){i.Posts.insert({name:"Test post # "+t,desc:"How now brown cow"})})}function o(){console.log("Creating fake users"),["Bob","Jane","Max"].forEach(function(t){Accounts.createUser({username:t,password:"password",profile:{}})})}Object.defineProperty(n,"__esModule",{value:!0}),n.createPosts=e,n.createUsers=o;var i=r(1)},function(t,n,r){"use strict";var e=r(1),o=r(2);r(4),e.Posts.find().fetch().length||(o.createPosts(),o.createUsers()),console.log("\n\nRunning on server only"),console.log("There are # posts:",e.Posts.find().fetch().length)},function(t,n){"use strict";Meteor.methods({sayHello:function(){return"Hello from Meteor method!"}})},function(t,n){!function(n){"use strict";function r(t,n,r,e){var i=Object.create((n||o).prototype);return i._invoke=s(t,r||null,new p(e||[])),i}function e(t,n,r){try{return{type:"normal",arg:t.call(n,r)}}catch(e){return{type:"throw",arg:e}}}function o(){}function i(){}function u(){}function c(t){["next","throw","return"].forEach(function(n){t[n]=function(t){return this._invoke(n,t)}})}function a(t){this.arg=t}function f(t){function n(n,r){var e=t[n](r),u=e.value;return u instanceof a?Promise.resolve(u.arg).then(o,i):Promise.resolve(u).then(function(t){return e.value=t,e})}function r(t,r){var o=e?e.then(function(){return n(t,r)}):new Promise(function(e){e(n(t,r))});return e=o["catch"](function(t){}),o}"object"==typeof process&&process.domain&&(n=process.domain.bind(n));var e,o=n.bind(t,"next"),i=n.bind(t,"throw");n.bind(t,"return");this._invoke=r}function s(t,n,r){var o=b;return function(i,u){if(o===_)throw new Error("Generator is already running");if(o===M){if("throw"===i)throw u;return g()}for(;;){var c=r.delegate;if(c){if("return"===i||"throw"===i&&c.iterator[i]===y){r.delegate=null;var a=c.iterator["return"];if(a){var f=e(a,c.iterator,u);if("throw"===f.type){i="throw",u=f.arg;continue}}if("return"===i)continue}var f=e(c.iterator[i],c.iterator,u);if("throw"===f.type){r.delegate=null,i="throw",u=f.arg;continue}i="next",u=y;var s=f.arg;if(!s.done)return o=w,s;r[c.resultName]=s.value,r.next=c.nextLoc,r.delegate=null}if("next"===i)o===w?r.sent=u:r.sent=y;else if("throw"===i){if(o===b)throw o=M,u;r.dispatchException(u)&&(i="next",u=y)}else"return"===i&&r.abrupt("return",u);o=_;var f=e(t,n,r);if("normal"===f.type){o=r.done?M:w;var s={value:f.arg,done:r.done};if(f.arg!==O)return s;r.delegate&&"next"===i&&(u=y)}else"throw"===f.type&&(o=M,i="throw",u=f.arg)}}}function l(t){var n={tryLoc:t[0]};1 in t&&(n.catchLoc=t[1]),2 in t&&(n.finallyLoc=t[2],n.afterLoc=t[3]),this.tryEntries.push(n)}function h(t){var n=t.completion||{};n.type="normal",delete n.arg,t.completion=n}function p(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(l,this),this.reset(!0)}function v(t){if(t){var n=t[m];if(n)return n.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var r=-1,e=function o(){for(;++r=0;--e){var o=this.tryEntries[e],i=o.completion;if("root"===o.tryLoc)return n("end");if(o.tryLoc<=this.prev){var u=d.call(o,"catchLoc"),c=d.call(o,"finallyLoc");if(u&&c){if(this.prev=0;--r){var e=this.tryEntries[r];if(e.tryLoc<=this.prev&&d.call(e,"finallyLoc")&&this.prev=0;--n){var r=this.tryEntries[n];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),h(r),O}},"catch":function(t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.tryLoc===t){var e=r.completion;if("throw"===e.type){var o=e.arg;h(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,n,r){return this.delegate={iterator:v(t),resultName:n,nextLoc:r},O}}}("object"==typeof global?global:"object"==typeof window?window:"object"==typeof self?self:this)},function(t,n,r){var e;!function(o,i,u){"use strict";!function(t){function n(e){if(r[e])return r[e].exports;var o=r[e]={exports:{},id:e,loaded:!1};return t[e].call(o.exports,o,o.exports,n),o.loaded=!0,o.exports}var r={};return n.m=t,n.c=r,n.p="",n(0)}([function(t,n,r){r(1),r(33),r(39),r(41),r(43),r(45),r(47),r(49),r(50),r(51),r(52),r(53),r(54),r(55),r(56),r(57),r(58),r(59),r(60),r(61),r(62),r(64),r(65),r(66),r(67),r(68),r(69),r(70),r(72),r(73),r(74),r(76),r(77),r(78),r(80),r(81),r(82),r(83),r(84),r(85),r(86),r(87),r(88),r(89),r(90),r(91),r(92),r(94),r(96),r(100),r(101),r(103),r(104),r(108),r(113),r(114),r(117),r(119),r(121),r(123),r(124),r(125),r(127),r(128),r(130),r(131),r(132),r(133),r(139),r(142),r(143),r(145),r(146),r(147),r(148),r(149),r(150),r(151),r(152),r(153),r(154),r(155),r(156),r(158),r(159),r(160),r(161),r(162),r(163),r(165),r(166),r(167),r(168),r(170),r(171),r(173),r(174),r(176),r(177),r(178),r(179),t.exports=r(182)},function(t,n,r){var e,o=r(2),i=r(3),c=r(5),a=r(6),f=r(8),s=r(10),l=r(11),h=r(12),p=r(17),v=r(18),g=r(16)("__proto__"),y=r(9),d=r(29),m=r(20),x=r(22),S=r(30),b=r(25),w=r(31),_=r(24),M=r(21),O=r(4),E=Object.prototype,P=[],j=P.slice,F=P.join,A=o.setDesc,N=o.getDesc,k=o.setDescs,D=r(32)(!1),L={};i||(e=!O(function(){return 7!=A(f("div"),"a",{get:function(){return 7}}).a}),o.setDesc=function(t,n,r){if(e)try{return A(t,n,r)}catch(o){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(d(t)[n]=r.value),t},o.getDesc=function(t,n){if(e)try{return N(t,n)}catch(r){}return s(t,n)?c(!E.propertyIsEnumerable.call(t,n),t[n]):void 0},o.setDescs=k=function(t,n){d(t);for(var r,e=o.getKeys(n),i=e.length,u=0;i>u;)o.setDesc(t,r=e[u++],n[r]);return t}),h(h.S+h.F*!i,"Object",{getOwnPropertyDescriptor:o.getDesc,defineProperty:o.setDesc,defineProperties:k});var R="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(","),T=R.concat("length","prototype"),I=R.length,C=function(){var t,n=f("iframe"),r=I,e=">";for(n.style.display="none",a.appendChild(n),n.src="javascript:",t=n.contentWindow.document,t.open(),t.write("