diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..bb191b5 --- /dev/null +++ b/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "react", + "es2015", + "stage-3" + ], + "plugins": [ + "transform-runtime", + "transform-decorators-legacy", + "transform-class-properties", + "react-hot-loader/babel", + "react-html-attrs" + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0511914 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{js,jsx,html}] +charset = utf-8 +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +# Indent sass files with 4 spaces +[*.{sass}] +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..dadeed3 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,82 @@ +--- + extends: + - plugin:react/recommended + + env: + browser: true + node: true + es6: true + + parserOptions: + ecmaVersion: 6 + sourceType: "module" + ecmaFeatures: + jsx: true + + globals: + __DEV__: true + __SERVER__: true + + plugins: + - react + + parser: "babel-eslint" + rules: + react/jsx-uses-vars: 1 + react/prop-types: [1, { ignore: [children] }] + + semi: 0 + key-spacing: 1 + curly: 0 + consistent-return: 0 + space-infix-ops: 1 + camelcase: 0 + no-spaced-func: 1 + no-alert: 1 + eol-last: 1 + comma-spacing: 1 + eqeqeq: 1 + + # possible errors + comma-dangle: 0 + no-cond-assign: 2 + no-console: 0 + no-constant-condition: 2 + no-control-regex: 2 + no-debugger: 2 + no-dupe-args: 2 + no-dupe-keys: 2 + no-duplicate-case: 2 + no-empty-character-class: 2 + no-empty: 2 + no-ex-assign: 2 + no-extra-boolean-cast: 2 + no-extra-parens: 0 + no-extra-semi: 2 + no-func-assign: 2 + no-inner-declarations: 2 + no-invalid-regexp: 2 + no-irregular-whitespace: 2 + no-negated-in-lhs: 2 + no-obj-calls: 2 + no-regex-spaces: 2 + no-sparse-arrays: 2 + no-unexpected-multiline: 2 + no-unreachable: 2 + use-isnan: 2 + valid-jsdoc: 2 + valid-typeof: 2 + + no-redeclare: 2 + + init-declarations: 2 + no-catch-shadow: 2 + no-delete-var: 2 + no-label-var: 2 + no-shadow-restricted-names: 2 + no-shadow: 2 + no-undef-init: 2 + no-undef: 2 + no-undefined: 2 + no-unused-vars: 2 + no-use-before-define: 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17628a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +# Ignore build files +public + +# Ignore my .mertrc file +.mertrc diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..2b6f469 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,3 @@ +{ + "esversion": 6 +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..333db29 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# react-webpack-babel-simple-starter +Simple React Webpack Babel Starter Kit + +Tired of complicated starters with 200MB of dependencies which are hard to understand and modify? + +Try this is a simple [React](https://facebook.github.io/react/), [Webpack](http://webpack.github.io/) and [Babel](https://babeljs.io/) application with nothing else in it. + +This is a fork of [react-webpack-babel-simple-starter](https://github.com/alicoding/react-webpack-babel) + +### What's in it? + +* Simple src/app.jsx and src/app.scss (local module css). +* Webpack configuration for development (with hot reloading) and production (with minification). +* CSS module loading, so you can include your css by ```import styles from './path/to.css';```. +* Both js(x) and css hot loaded during development. + +### To run + +* You'll need to have [git](https://git-scm.com/) and [node](https://nodejs.org/en/) installed in your system. +* Fork and clone the project: + +``` +git clone https://github.com/alicoding/react-webpack-babel.git +``` + +* Then install the dependencies: + +``` +npm install +``` + +* Run development server: + +``` +npm start +``` + +Open the web browser to `http://localhost:8888/` + +### To build the production package + +``` +npm run build +``` + +### Nginx Config + +Here is an example Nginx config: +``` +server { + # ... root and other options + + gzip on; + gzip_http_version 1.1; + gzip_types text/plain text/css text/xml application/javascript image/svg+xml; + + location / { + try_files $uri $uri/ /index.html; + } + + location ~ \.html?$ { + expires 1d; + } + + location ~ \.(svg|ttf|js|css|svgz|eot|otf|woff|jpg|jpeg|gif|png|ico)$ { + access_log off; + log_not_found off; + expires max; + } +} +``` + +### Eslint +There is a .eslint.yaml config for eslint ready with React plugin. +To use it, you need to install additional dependencies though: + +``` +npm install --save-dev eslint eslint-plugin-react +``` + +To do the actual linting, run: + +``` +npm run lint +``` + +### Notes on importing css styles +* styles having /src/ in their absolute path are considered part of the application and exported as local css modules. +* other styles are considered global styles used by many components and are included in the css bundle directly. + +### Contribute +Please contribute to the project if you know how to make it better, including this README :) + +### Personal Setup +On the personal Setup branch I've added some tools and extra funcitonalities to better suit my needs. +- [babel-plugin-react-html-attrs](https://github.com/insin/babel-plugin-react-html-attrs) Transforms JSX `class` attributes into `className` and `for` attributes into `htmlFor`, allowing you to copy and paste HTML into your React components without having to manually edit these particular attributes each time. +- Personal `.editorconfig` setup +- Re-added `bootstrap` (it was stripped in the original project) +- [react-router](https://github.com/ReactTraining/react-router) basic setup diff --git a/index.html b/index.html new file mode 100644 index 0000000..b936a7c --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + React App + + + + +
+
Loading...
+
+ + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..7f7ee4d --- /dev/null +++ b/package.json @@ -0,0 +1,64 @@ +{ + "name": "react-webpack-babel", + "version": "0.0.3", + "description": "React Webpack Babel Starter Kit", + "main": "''", + "scripts": { + "build": "webpack --config webpack.production.config.js --progress --profile --colors", + "start": "webpack-dev-server --progress --profile", + "lint": "eslint --ext js --ext jsx src || exit 0", + "dev": "webpack-dashboard -- webpack-dev-server --progress --profile --colors" + }, + "repository": { + "type": "git", + "url": "https://github.com/LeonardoGentile/react-webpack-babel-simple-starter" + }, + "author": "Leonardo Gentile", + "license": "MIT", + "homepage": "https://github.com/LeonardoGentile/react-webpack-babel-simple-starter#readme", + "dependencies": { + "axios": "^0.15.3", + "classnames": "^2.2.5", + "js-cookie": "^2.1.3", + "mobx": "^3.1.0", + "mobx-react": "^4.1.0", + "react": "15.4.1", + "react-dom": "15.4.1", + "react-router5": "^4.0.1", + "router5": "^4.5.1" + }, + "devDependencies": { + "autoprefixer": "^6.7.2", + "babel-core": "6.17.0", + "babel-eslint": "^6.1.2", + "babel-loader": "6.2.7", + "babel-plugin-react-html-attrs": "^2.0.0", + "babel-plugin-transform-class-properties": "^6.16.0", + "babel-plugin-transform-decorators-legacy": "^1.3.4", + "babel-plugin-transform-runtime": "^6.15.0", + "babel-polyfill": "^6.16.0", + "babel-preset-es2015": "6.16.0", + "babel-preset-react": "6.16.0", + "babel-preset-stage-3": "^6.22.0", + "babel-runtime": "^6.11.6", + "css-loader": "0.26.0", + "eslint": "^3.16.0", + "eslint-plugin-react": "^6.10.0", + "extract-text-webpack-plugin": "^1.0.1", + "file-loader": "0.9.0", + "html-webpack-plugin": "^2.22.0", + "lost": "^8.0.0", + "mobx-react-devtools": "^4.2.10", + "node-sass": "^3.10.1", + "postcss-loader": "^1.3.0", + "react-hot-loader": "^3.0.0-beta.6", + "sass-loader": "^4.0.2", + "sass-resources-loader": "^1.2.0", + "style-loader": "0.13.1", + "url-loader": "0.5.7", + "webpack": "1.13.2", + "webpack-cleanup-plugin": "^0.4.1", + "webpack-dashboard": "^0.3.0", + "webpack-dev-server": "1.16.3" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..0e1dcad --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,19 @@ +const AUTOPREFIXER_BROWSERS = [ + 'Android 2.3', + 'Android >= 4', + 'Chrome >= 35', + 'Firefox >= 31', + 'Explorer >= 9', + 'iOS >= 7', + 'Opera >= 12', + 'Safari >= 7.1', +]; + +module.exports = { + plugins: [ + require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS }), + require('lost') + ] +} + + diff --git a/src/actions/auth.js b/src/actions/auth.js new file mode 100644 index 0000000..317a1d0 --- /dev/null +++ b/src/actions/auth.js @@ -0,0 +1,53 @@ +import axios from 'axios'; +import Cookies from 'js-cookie'; +import querystring from 'querystring'; +import userStore from '../stores/UserStore' + +// TODO: +function _handleErrors(response) { + if (response.data && response.data.success !== true) { + throw Error(response); + } + return response; +} + + +export function login(formData, callback=undefined){ + axios({ + method: 'post', + url: '/login', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + data: querystring.stringify(formData) + }) + .then(_handleErrors) + .then((response) => { + console.log(response); + userStore.setUser(response.data.data); + }) + .catch((error) => { + // optional callback + typeof callback == "function" && callback(error); + // TODO: _handleErrors(error, callback) + console.log(error); + }); +} + + + +export function logout(){ + axios.get("/logout") + .then((res) => { + + // TODO: + // - Remove cookies (also disclaimer) + // - Remove stored tabs + // - remove last finder link + // - remove watchlist + // - go to home page + + // or setUser(res.data.data) basically the same + userStore.resetUser(); + }) +} diff --git a/src/app.jsx b/src/app.jsx new file mode 100644 index 0000000..fe2f8a9 --- /dev/null +++ b/src/app.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Provider } from 'mobx-react'; +import { RouterProvider } from 'react-router5'; +import Layout from './components/Layout' + +// Local imports +import * as config from './config'; +import * as stores from './stores'; // mobx stores +import createRouter from './create-router5' // Router5 + +// ================= +// = Global Styles = +// ================= +import './styles/base/_reset.sass'; +import './styles/base/_commons.sass'; + +const router = createRouter(true); + +// RouterProvider will add your router instance in context. +const wrappedApp = ( + + + + + +); + +// Render the entire app when the router starts +router.start((err, state) => { + ReactDOM.render( + wrappedApp, + document.getElementById('app') + ); +}); + + + diff --git a/src/components/Layout/Footer/Footer.jsx b/src/components/Layout/Footer/Footer.jsx new file mode 100644 index 0000000..09b1e8c --- /dev/null +++ b/src/components/Layout/Footer/Footer.jsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import styles from './Footer.sass'; + +export default class Footer extends React.Component { + + static propTypes = { + name: React.PropTypes.string, + }; + + constructor(props) { + super(props); + } + + render() { + return ( + + ); + } +} diff --git a/src/components/Layout/Footer/Footer.sass b/src/components/Layout/Footer/Footer.sass new file mode 100644 index 0000000..e69de29 diff --git a/src/components/Layout/Header/Header.jsx b/src/components/Layout/Header/Header.jsx new file mode 100644 index 0000000..a86c9ad --- /dev/null +++ b/src/components/Layout/Header/Header.jsx @@ -0,0 +1,35 @@ +import React from 'react'; +import NavAccount from './NavAccount/NavAccount'; +import { BaseLink, withRoute } from 'react-router5'; + +import styles from './Header.sass'; +import logo_img from './img/home_logo.jpg' + +class Header extends React.Component { + + render() { + const { router } = this.props; + + return ( +
+
+ +
+ + Logo + +
+ +
+ +
+ +
+
+
+ ); + } +} + +// will pass the router trough context +export default withRoute(Header); diff --git a/src/components/Layout/Header/Header.sass b/src/components/Layout/Header/Header.sass new file mode 100644 index 0000000..a40b7ba --- /dev/null +++ b/src/components/Layout/Header/Header.sass @@ -0,0 +1,26 @@ + +$headerHeight: 126px + +.headerContainer + position: relative // so we can use absolute inside it + z-index: $zindex_headerContainer + height: $headerHeight + // **** + background: $orange + border-bottom: 2px solid $redSB-dark + border-top: 2px solid $redSB-dark + +.header + +container + +clearfix + position: relative + height: 100% + +.navAccountContainer + position: absolute + top: 0 + right: 0 + z-index: $zindex_navAccount + + + diff --git a/src/components/Layout/Header/NavAccount/NavAccount.jsx b/src/components/Layout/Header/NavAccount/NavAccount.jsx new file mode 100644 index 0000000..f24e716 --- /dev/null +++ b/src/components/Layout/Header/NavAccount/NavAccount.jsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { observer, inject } from 'mobx-react'; +import classNames from 'classnames'; +import { withRoute } from 'react-router5'; + +import styles from './NavAccount.sass'; + +@inject("userStore") +@observer +class NavAccount extends React.Component { + + constructor(props, context){ + super(props, context); + // this should work + // this.router = context.router; + this._showLogin = this._showLogin.bind(this); + } + + componentWillUnmount() { + console.log("Hello, I am the NavAccount, I will unmount") + } + + // =========== + // = Helpers = + // =========== + _showLogin(e) { + e.preventDefault(); + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + + this.props.router.navigate('login', (err, state)=> { + if (err) console.log(err); + else console.log("I navigate to: " + state.name); + }); + } + + _renderMenu() { + const userStore = this.props.userStore; + + const isLoggedIn = userStore.isLoggedIn; + const ulClass = classNames(styles.navAccount, 'horizontal'); + + if (isLoggedIn){ + return ( + + ); + } + else { + return ( + + ); + } + + } + + render() { + return (this._renderMenu()) + } + +} + +export default withRoute(NavAccount); diff --git a/src/components/Layout/Header/NavAccount/NavAccount.sass b/src/components/Layout/Header/NavAccount/NavAccount.sass new file mode 100644 index 0000000..99a7f8f --- /dev/null +++ b/src/components/Layout/Header/NavAccount/NavAccount.sass @@ -0,0 +1,42 @@ + +.navAccount + > li + height: 46px + text-align: center + +light-on-dark + + > a + display: inline-block + color: #FFF + font-size: 15px + line-height: 44px + padding: 0 18px + + &.sign-in + background: $redSB-dark2 + + &.sign-up + background: $redSB-light + + &.builder + background: $redSB-light2 + + &.finder + background: $redSB + + &.my-account + //background: $redSB-dark url('/public/images/__menu_my_account.png') no-repeat 120px 0 + padding-right: 40px + position: relative + + &:hover + background-position: 120px -47px + + span + border-bottom: none + + .menu-hover + display: block + + a + padding-right: 0 diff --git a/src/components/Layout/Header/img/home_logo.jpg b/src/components/Layout/Header/img/home_logo.jpg new file mode 100644 index 0000000..175ef0d Binary files /dev/null and b/src/components/Layout/Header/img/home_logo.jpg differ diff --git a/src/components/Layout/Layout.jsx b/src/components/Layout/Layout.jsx new file mode 100644 index 0000000..469416d --- /dev/null +++ b/src/components/Layout/Layout.jsx @@ -0,0 +1,42 @@ +import React from 'react'; + +import Header from './Header/Header'; +import Sidebar from './Sidebar/Sidebar'; +import Footer from './Footer/Footer'; +import Main from '../Main/Main'; + +import styles from './Layout.sass'; + +class Layout extends React.Component { + constructor() { + super(); + } + + render() { + return ( +
+
+ +
+
+
+ +
+
+
+ +
+ ); + } +} + +export default Layout; + diff --git a/src/components/Layout/Layout.sass b/src/components/Layout/Layout.sass new file mode 100644 index 0000000..5c68a03 --- /dev/null +++ b/src/components/Layout/Layout.sass @@ -0,0 +1,9 @@ + +.body-loader + display: none + +body + background : $grey94 + font-size: 12px + line-height: 20px + width: 100% diff --git a/src/components/Layout/Sidebar/Sidebar.jsx b/src/components/Layout/Sidebar/Sidebar.jsx new file mode 100644 index 0000000..97bc428 --- /dev/null +++ b/src/components/Layout/Sidebar/Sidebar.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +export default class Sidebar extends React.Component { + + constructor(props) { + super(props); + } + + render() { + return ( +
Sidebar
+ ); + } +} diff --git a/src/components/Layout/index.js b/src/components/Layout/index.js new file mode 100644 index 0000000..5bc5a2b --- /dev/null +++ b/src/components/Layout/index.js @@ -0,0 +1,3 @@ +import Layout from './Layout'; + +export default Layout; diff --git a/src/components/Main/Main.jsx b/src/components/Main/Main.jsx new file mode 100644 index 0000000..033e242 --- /dev/null +++ b/src/components/Main/Main.jsx @@ -0,0 +1,31 @@ +import React, { createElement } from 'react'; +import { routeNode } from 'react-router5'; + +import Home from '../../pages/Home'; +import Index from '../../pages/Index'; +import Login from '../../pages/Login'; + + +const components = { + 'home': Home, + 'index': Index, + 'login': Login, +}; + +class Main extends React.Component { + + render(){ + const { route } = this.props; + const segment = route.name.split('.')[0]; + + return createElement(components[segment], + { ...route.params } + // For full unmount/mount pass the key as below: + // { key: route.meta.id, ...route.params } + ); + } +} +// withRouter is a High Order Function +// It is a sort of decorator to augment the Component class with extra methods or properties coming from react-router +// For example we can now access "this.props.router" (we need listenersPlugin from router5) +export default routeNode('')(Main); // '' because it it the root node diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..502303e --- /dev/null +++ b/src/config.js @@ -0,0 +1,11 @@ +import axios from 'axios'; + +// GLOBAL AXIOS CONFIG +axios.defaults.baseURL = '/api'; +axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; +axios.defaults.xsrfCookieName = 'csrftoken'; +axios.defaults.xsrfHeaderName = 'X-CSRFToken'; + +module.exports = {} + + diff --git a/src/create-router5.js b/src/create-router5.js new file mode 100644 index 0000000..913059a --- /dev/null +++ b/src/create-router5.js @@ -0,0 +1,29 @@ +import createRouter from 'router5'; +import loggerPlugin from 'router5/plugins/logger'; +import listenersPlugin from 'router5/plugins/listeners'; +import browserPlugin from 'router5/plugins/browser'; +import routes from './routes'; + + +const routerOptions = { + defaultRoute: 'home', + strictQueryParams: true +}; + +function configureRouter(useListenersPlugin = false) { + const router = createRouter(routes, routerOptions) + // Plugins + .usePlugin(loggerPlugin) + .usePlugin(browserPlugin({ + useHash: true + })); + + if (useListenersPlugin) { + router.usePlugin(listenersPlugin()); + } + + return router; +} + +// I can import the default module with whatever name I want (createRouter) +export default configureRouter; diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx new file mode 100644 index 0000000..743c50f --- /dev/null +++ b/src/pages/Home.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import styles from './Home.sass'; +import { routeNode } from 'react-router5'; + +class Home extends React.Component { + render() { + return ( +
+

Home, I am a subroute page

+ +
+ ); + } +} + + +export default routeNode('home')(Home); diff --git a/src/pages/Home.sass b/src/pages/Home.sass new file mode 100644 index 0000000..0306c59 --- /dev/null +++ b/src/pages/Home.sass @@ -0,0 +1,10 @@ +.redBg + background-color: red + border-radius: 5px + color: white + padding: 5px + + +.container-border + border: 1px dashed grey + diff --git a/src/pages/Index.jsx b/src/pages/Index.jsx new file mode 100644 index 0000000..2fa7fa6 --- /dev/null +++ b/src/pages/Index.jsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { observer, inject} from 'mobx-react'; +import styles from './Index.sass'; +import { routeNode } from 'react-router5'; + +@inject("tabStore") +@observer +class Index extends React.Component { + + _setActiveTab() { + const router = this.props.router; + const route = this.props.route; + const tabStore = this.props.tabStore; + const { id } = route.params; + tabStore.setActiveTab(id); + } + + constructor(props) { + super(props); + } + + // Triggered when a component will be scheduled to re-render because data it observes (from mobx store) has changed. + // This makes it easy to trace renders back to the action that caused the rendering. + componentWillReact() { + console.log("I will re-render, since the todo has changed!"); + } + + + // componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. I + // If you need to load data from a remote endpoint, this is a good place to instantiate the network request. + // Setting state in this method will trigger a re-rendering. + componentDidMount() { + console.log("I did mount Index"); + this._setActiveTab(); + } + + componentWillUnmount(){ + console.log("I will unmount Index"); + } + + // This method is not called for the initial render. + componentDidUpdate(prevProps, prevState) { + this._setActiveTab(); + console.log("I will re-render for whatever reason"); + } + + render() { + const tabStore = this.props.tabStore; + return ( +
+

Index, I am The Index Page {tabStore.activeTab}

+

This is some computed values:

+ +
{JSON.stringify(tabStore.tabs)}
+
+ ); + } +} + +export default routeNode('index')(Index); diff --git a/src/pages/Index.sass b/src/pages/Index.sass new file mode 100644 index 0000000..f0d0927 --- /dev/null +++ b/src/pages/Index.sass @@ -0,0 +1,10 @@ +.redBg + background-color: red + border-radius: 5px + color: white + padding: 5px + + +.container-border + border: 1px dashed grey + diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx new file mode 100644 index 0000000..b96ab06 --- /dev/null +++ b/src/pages/Login.jsx @@ -0,0 +1,95 @@ +import React from 'react'; +import {login, logout} from '../actions/auth' +import { observer, inject } from 'mobx-react'; +import { routeNode } from 'react-router5'; + +// 'inject' can be used to pick up the stores passed to components with Provider. +// It is a HOC that takes a list of strings and makes those stores available as 'this.props.storeName' to the wrapped component. +@inject('userStore') +@observer +class Login extends React.Component { + + // To validate the injected store + static propTypes = { + userStore: React.PropTypes.object.isRequired + }; + + constructor(props){ + super(props); + this.formData = {email:"", password:""}; + // BINDING + this.onEmailChange = this.onEmailChange.bind(this); + this.onPasswordChange = this.onPasswordChange.bind(this); + this.onSubmit = this.onSubmit.bind(this); + this.onLogout = this.onLogout.bind(this); + } + + componentDidMount(){ + console.log("I did mount the login"); + } + + componentWillUnmount(){ + console.log("I will unmount the login"); + } + + onEmailChange(e) { + Object.assign(this.formData, {}, {email: e.target.value}); + console.log(this.formData); + } + + onPasswordChange(e) { + Object.assign(this.formData, {}, {password: e.target.value}); + console.log(this.formData); + } + + onSubmit(event) { + event.preventDefault(); + login(this.formData); + } + + onLogout(e) { + event.preventDefault(); + logout(); + } + + render() { + return ( +
+

{JSON.stringify(this.props.userStore.user)}

+
+

Login

+
+
+
+
+
+ +
+ +
+ + +
+ ); + } +} + +export default routeNode('login')(Login); diff --git a/src/routes.js b/src/routes.js new file mode 100644 index 0000000..7766483 --- /dev/null +++ b/src/routes.js @@ -0,0 +1,5 @@ +export default [ + { name: 'home', path: '/' }, + { name: 'login', path: '/login'}, + { name: 'index', path: '/index/:id'}, +]; diff --git a/src/services/api.js b/src/services/api.js new file mode 100644 index 0000000..70b786d --- /dev/null +++ b/src/services/api.js @@ -0,0 +1 @@ +// TODO diff --git a/src/stores/TabStore.js b/src/stores/TabStore.js new file mode 100644 index 0000000..f261963 --- /dev/null +++ b/src/stores/TabStore.js @@ -0,0 +1,48 @@ +import { observable, computed, autorun, action } from 'mobx'; + +// Mobx Observable Store +class TabStore { + + constructor() { + // Autorun runs every time the store changes + // In reality what I want is to run a react .render() whenever something changes + autorun(() => console.log("Active Tab: " + this.activeTab)); + } + + @observable activeTab = null; + @observable tabs = []; + + @computed get tabNumbers() { + return this.tabs.length; + } + + + // =========== + // = Methods = + // =========== + _addTab = (id) => { + + const found = this.tabs.find(function(item, index, array) { + return item.id === id + }); + if (!found) { + this.tabs.push({ + id: id, + }); + } + else { + console.log("Element already in array"); + } + }; + + @action setActiveTab = (id) => { + this.activeTab = id; + this._addTab(id); + }; + + +} + + +const tabStore = window.__tabStore__ = new TabStore(); +export default tabStore; diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js new file mode 100644 index 0000000..b149612 --- /dev/null +++ b/src/stores/UserStore.js @@ -0,0 +1,57 @@ +import { observable, action, computed } from 'mobx'; + +// TODO: +function _handleErrors(response) { + if (response.data && response.data.success !== true) { + throw Error(response); + } + return response; +} + + +class UserStore { + + constructor() { + this.user = this.defaultUser; + this.watchlist = []; + } + + + defaultUser = { + "level" : "V", + "status" : "U" + }; + + // =============== + // = OBSERVABLES = + // =============== + @observable user; + @observable watchlist; + + // ============ + // = COMPUTED = + // ============ + @computed get isLoggedIn() { + return this.user.level !== 'V'; + } + + // =========== + // = Actions = + // =========== + @action setUser = (user) => { + this.user = user; + }; + + @action resetUser = () => { + this.user = this.defaultUser; + this.watchlist = []; + }; + +} + + +const userStore = new UserStore(); + +export default userStore; + +export { UserStore }; diff --git a/src/stores/index.js b/src/stores/index.js new file mode 100644 index 0000000..43fe932 --- /dev/null +++ b/src/stores/index.js @@ -0,0 +1,8 @@ +import tabStore from './TabStore'; +import userStore from './UserStore'; + +export { + tabStore, + userStore, +}; + diff --git a/src/styles/abstracts/_mixins.sass b/src/styles/abstracts/_mixins.sass new file mode 100644 index 0000000..c179c02 --- /dev/null +++ b/src/styles/abstracts/_mixins.sass @@ -0,0 +1,31 @@ +=container + margin: auto + width: $width + +=clearfix + &:after + content: "" + display: table + clear: both + +// ================== +// = FONT-SMOOTHING = +// ================== +// Activate/Deactivate font-smoothing +// The default is subpixel level, by passing "on" we deactivate it, activating the antialiased (less defined) level. +// This is needed for light text on dark bg `bug` on chrome: http://usabilitypost.com/2012/11/05/stop-fixing-font-smoothing/ +=font-smoothing-antialiased($value: on) + @if $value == on + -webkit-font-smoothing: antialiased // DO NOT USE THIS SITE-WIDE + -moz-osx-font-smoothing: grayscale + @else + -webkit-font-smoothing: subpixel-antialiased // On most non-retina displays this will give the sharpest text (default) + -moz-osx-font-smoothing: auto + + +// It removes the subpixel smoothing (default) for light text on dark bg +=light-on-dark + +font-smoothing-antialiased(on) + +=dark-on-light + +font-smoothing-antialiased(off) diff --git a/src/styles/abstracts/_variables.sass b/src/styles/abstracts/_variables.sass new file mode 100644 index 0000000..918bb0e --- /dev/null +++ b/src/styles/abstracts/_variables.sass @@ -0,0 +1,27 @@ + +$width : 960px + +// Colors +$redSB : #87000D +$redSB-light : #A90010 +$redSB-light2 : #98000F +$redSB-dark : #640009 +$redSB-dark2: #75000A +$grey94 : #F1F1F1 +$orange: rgb(200, 42, 30) +// Fonts +$mainFont : "myriad-pro" +$fallBackFonts : "Myriad Pro", MyriadPro, Arial, sans-serif + +// =========== +// = Z-Index = +// =========== +$zindex_headerContainer: 11 + +$zindex_navAccount: 14 +$zindex_navAccount__Dropdown: 13 +$zindex_socialbuttons: 12 + +$zindex_menuNavigation: 13 +$zindex_menuNavigationTooltips: 10 +$zindex_menuNavigationDropdown: 10 diff --git a/src/styles/base/_commons.sass b/src/styles/base/_commons.sass new file mode 100644 index 0000000..0898aaa --- /dev/null +++ b/src/styles/base/_commons.sass @@ -0,0 +1,13 @@ +// Display the list form left to right + +ul + horizontal + +clearfix + li + float: left + + +.clearfix + +clearfix + + diff --git a/src/styles/base/_reset.sass b/src/styles/base/_reset.sass new file mode 100644 index 0000000..5f8c6f4 --- /dev/null +++ b/src/styles/base/_reset.sass @@ -0,0 +1,61 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video + margin: 0 + padding: 0 + border: 0 + font-size: 100% + font: inherit + vertical-align: baseline + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section + display: block + +body + line-height: 1 + +ol, ul + list-style: none + +blockquote, q + quotes: none + +blockquote:before, blockquote:after, +q:before, q:after + content: '' + content: none + +table + border-collapse: collapse + border-spacing: 0 + +a + text-decoration: none + outline: 0 + + +/* apply a natural box layout model to all elements */ +*, *:before, *:after + -moz-box-sizing: border-box + -webkit-box-sizing: border-box + box-sizing: border-box + + +html + font-family: sans-serif \ No newline at end of file diff --git a/src/template.html b/src/template.html new file mode 100644 index 0000000..d743bc1 --- /dev/null +++ b/src/template.html @@ -0,0 +1,15 @@ + + + + + React App + + + +
+
Loading...
+
+ + + + diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..2782ed5 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,64 @@ +"use strict"; +var webpack = require('webpack'); +var path = require('path'); +var loadersConf = require('./webpack.loaders'); +var DashboardPlugin = require('webpack-dashboard/plugin'); + +const HOST = process.env.HOST || "127.0.0.1"; +const PORT = process.env.PORT || "8888"; + +module.exports = { + // If you pass an array: All modules are loaded upon startup. The last one is exported. + entry: [ + 'babel-polyfill', // emulate a full ES2015 (Promise, WeakMap, ectr) + 'react-hot-loader/patch', // needed for hot loader v3 + './src/app.jsx' // your app's entry point (exported module) + ], + output: { + path: path.join(__dirname, 'public'), + filename: 'bundle.js', + publicPath: 'http://localhost:8888/public/', + }, + module: { + loaders: loadersConf + }, + resolve: { + extensions: ['', '.js', '.jsx'], + }, + devtool: '#inline-source-map', // 'eval-cheap-source-map', + devServer: { + contentBase: "./", + publicPath: "http://localhost:8888/public/", + // do not print bundle build stats + noInfo: true, + // enable HMR + hot: true, + // embed the webpack-dev-server runtime into the bundle + inline: true, + // serve index.html in place of 404 responses to allow HTML5 history + historyApiFallback: true, + port: PORT, + host: HOST, + colors: true, + // proxy to my local dev server running at 8000 port for anything starting with 'api' (then stripped) + proxy: { + '/api': { + target: 'http://localhost:8000', + pathRewrite: {'^/api' : ''}, + secure: false, + changeOrigin: true, + logLevel: 'debug', + } + } + }, + // These files will imported in every sass file (imported) + sassResources: [ + './src/styles/abstracts/_variables.sass', + './src/styles/abstracts/_mixins.sass', + ], + plugins: [ + new webpack.NoErrorsPlugin(), + new webpack.HotModuleReplacementPlugin(), + new DashboardPlugin(), // cool console output + ] +}; diff --git a/webpack.loaders.js b/webpack.loaders.js new file mode 100644 index 0000000..61fd9d8 --- /dev/null +++ b/webpack.loaders.js @@ -0,0 +1,97 @@ +var path = require('path'); + +// Modularized sass files +const sassLoaders = [ + 'css-loader?sourceMap&camelCase=dashes&modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]', + 'postcss-loader?sourceMap=inline', + 'sass-loader?sourceMap&outputStyle=expanded&indentedSyntax=sass&includePaths[]=' + path.resolve(__dirname, './src/styles'), + 'sass-resources' // NB: the config for sass-resources is in the the webpack config file +]; + +// Not modularize sass files (globals) +const sassLoadersGloabals = [ + 'css-loader?sourceMap&camelCase=dashes&importLoaders=1', + 'postcss-loader?sourceMap=inline', + 'sass-loader?sourceMap&outputStyle=expanded&indentedSyntax=sass&includePaths[]=' + path.resolve(__dirname, './src/styles'), + 'sass-resources' // NB: the config for sass-resources is in the the webpack config file +]; + + +module.exports = [ + // ========= + // = Babel = + // ========= + // Load these exts with babel (so we can use 'import' instead of 'require' and es6 syntax) + { + test: /\.jsx?$/, + include: __dirname + '/src/', + loader: "babel" + }, + // ========= + // = Fonts = + // ========= + { + test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, + exclude: /(node_modules)/, + loader: "file" + }, + { + test: /\.(woff|woff2)$/, + exclude: /(node_modules)/, + loader: "url?prefix=font/&limit=5000" + }, + { + test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, + exclude: /(node_modules)/, + loader: "url?limit=10000&mimetype=application/octet-stream" + }, + // ========== + // = Images = + // ========== + { + test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, + exclude: /(node_modules)/, + loader: "url?limit=10000&mimetype=image/svg+xml" + }, + { + test: /\.gif/, + exclude: /(node_modules)/, + loader: "url-loader?limit=10000&mimetype=image/gif" + }, + { + test: /\.jpg/, + exclude: /(node_modules)/, + loader: "url-loader?limit=10000&mimetype=image/jpg" + }, + { + test: /\.png/, + exclude: /(node_modules)/, + loader: "url-loader?limit=10000&mimetype=image/png&name=[path][name].[ext]" + }, + // ========== + // = Styles = + // ========== + // NB: + // - css-loader takes a CSS file and reads off all its dependencies + // - style-loader will embed those styles directly into the markup(not when using dev-server) + + // Global CSS (from node_modules) + { + test: /\.css$/, + include: __dirname + '/node_modules', + loader: 'style-loader!css-loader' + }, + // Global ('locals') sass imports. Do not modularize these imports (leave them as global css styles) + { + test: /\.(sass|scss)$/, + include: __dirname + '/src/styles/base', + loader: 'style-loader!' + sassLoadersGloabals.join('!') + }, + // Local SASS modules + { + test: /\.(sass|scss)$/, + exclude: __dirname + '/src/styles/base', + loader: 'style-loader!' + sassLoaders.join('!') + }, +]; + diff --git a/webpack.production.config.js b/webpack.production.config.js new file mode 100644 index 0000000..dbfe111 --- /dev/null +++ b/webpack.production.config.js @@ -0,0 +1,69 @@ + +// TODO + +var webpack = require('webpack'); +var path = require('path'); +var loaders = require('./webpack.loaders'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var WebpackCleanupPlugin = require('webpack-cleanup-plugin'); + +// local css modules +loaders.push({ + test: /[\/\\]src[\/\\].*\.css/, + exclude: /(node_modules|bower_components|public)/, + loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]') +}); + +// local scss modules +loaders.push({ + test: /[\/\\]src[\/\\].*\.scss/, + exclude: /(node_modules|bower_components|public)/, + loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss!sass') +}); +// global css files +loaders.push({ + test: /[\/\\](node_modules|global)[\/\\].*\.css$/, + loader: ExtractTextPlugin.extract('style', 'css') +}); + +module.exports = { + entry: [ + './src/app.jsx' + ], + output: { + path: path.join(__dirname, 'public'), + filename: '[chunkhash].js' + }, + resolve: { + extensions: ['', '.js', '.jsx'] + }, + module: { + loaders + }, + plugins: [ + new WebpackCleanupPlugin(), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: '"production"' + } + }), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false, + screw_ie8: true, + drop_console: true, + drop_debugger: true + } + }), + new webpack.optimize.OccurenceOrderPlugin(), + new ExtractTextPlugin('[contenthash].css', { + allChunks: true + }), + new HtmlWebpackPlugin({ + template: './src/template.html', + title: 'Webpack App' + }), + new webpack.optimize.DedupePlugin() + ] +};