diff --git a/.gitignore b/.gitignore index 2c22538..56a58ec 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,9 @@ generated/node_modules generated/bower_components generated/public +generated/build node_modules bower_components npm-debug.log .DS_Store -coverage/ \ No newline at end of file +coverage/ diff --git a/generated/.babelrc b/generated/.babelrc index c13c5f6..1e4e461 100644 --- a/generated/.babelrc +++ b/generated/.babelrc @@ -1,3 +1,10 @@ { - "presets": ["es2015"] + "presets": ["es2015", "react", "stage-1", "stage-2"], + "env": { + "development": { + "presets": [ + "react-hmre" + ] + } + } } diff --git a/generated/.eslintrc.json b/generated/.eslintrc.json index 2158099..99aed32 100644 --- a/generated/.eslintrc.json +++ b/generated/.eslintrc.json @@ -2,6 +2,10 @@ { "root": true, "extends": "fullstack", // from the `eslint-config-fullstack` npm module, + "parser": "babel-eslint", + "parserOptions": { + "sourceType": "module" + }, "env": { "es6": true, "node": false, @@ -13,7 +17,8 @@ "rules": { "global-require": 0, // 0 = off, 1 = warn (yellow), 2 = error (red) "camelcase": 0, - "no-debugger": 1 + "no-debugger": 1, + "no-unused-expressions": 1 // add more rules here to override the fullstack config } } diff --git a/generated/browser/api/auth/index.js b/generated/browser/api/auth/index.js new file mode 100644 index 0000000..f236e5e --- /dev/null +++ b/generated/browser/api/auth/index.js @@ -0,0 +1,26 @@ +'use strict'; + +import axios from 'axios'; +import { parseJSON, parseData } from '../../utils'; + +const BASE_URL = process.env.URL || `http://localhost:${process.env.PORT || 1337}`; + +export const fetchSession = () => { + return axios.get(`${BASE_URL}/session`).then(parseData); +} + +export const tryLogin = (credentials) => { + return axios.post(`${BASE_URL}/login`, credentials); +} + +export const tryLogout = () => { + return axios.get(`${BASE_URL}/logout`); +} + +const auth = { + fetchSession, + tryLogin, + tryLogout +} + +export default auth; diff --git a/generated/browser/api/index.js b/generated/browser/api/index.js new file mode 100644 index 0000000..c6e4055 --- /dev/null +++ b/generated/browser/api/index.js @@ -0,0 +1 @@ +export auth from './auth'; diff --git a/generated/browser/api/secretStash/index.js b/generated/browser/api/secretStash/index.js new file mode 100644 index 0000000..9a6f200 --- /dev/null +++ b/generated/browser/api/secretStash/index.js @@ -0,0 +1,16 @@ +'use strict'; + +import axios from 'axios'; +import { parseData } from '../../utils'; + +const BASE_URL = process.env.URL || `http://localhost:${process.env.PORT || 1337}`; + +export const getStash = () => { + return axios.get(`${BASE_URL}/api/members/secret-stash`).then(parseData); +} + +const secretStash = { + getStash +} + +export default secretStash; diff --git a/generated/browser/app.js b/generated/browser/app.js new file mode 100644 index 0000000..30f3bd6 --- /dev/null +++ b/generated/browser/app.js @@ -0,0 +1,23 @@ +'use strict'; + +import React from 'react'; +import { render } from 'react-dom'; +import { Provider } from 'react-redux'; +import { Router, browserHistory } from 'react-router'; +import { syncHistoryWithStore } from 'react-router-redux'; +import getRoutes from './config/routes'; +import configureStore from './redux/configureStore'; + +const store = configureStore(browserHistory); +const history = syncHistoryWithStore(browserHistory, store); + +const component = ( + +); + +render( + + {component} + , + document.getElementById('app') +); diff --git a/generated/browser/components/About/About.js b/generated/browser/components/About/About.js new file mode 100644 index 0000000..bad8362 --- /dev/null +++ b/generated/browser/components/About/About.js @@ -0,0 +1,30 @@ +'use strict'; + +import React from 'react'; +import { Row, Col, Carousel } from 'react-bootstrap'; +import FullstackPics from '../../resources/FullstackPics'; +import './_About'; + +const About = (props) => { + return ( +
+

+ This website--the very one at which you currently gaze--was created by a basic scaffolding + concocted at Fullstack Academy. Here are some of the people who make it all very real: +

+ + { + FullstackPics.map((pic, i) => { + return ( + + + + ) + }) + } + +
+ ) +} + +export default About; diff --git a/generated/browser/scss/about/main.scss b/generated/browser/components/About/_About.scss similarity index 52% rename from generated/browser/scss/about/main.scss rename to generated/browser/components/About/_About.scss index 8e21a04..f3741c7 100644 --- a/generated/browser/scss/about/main.scss +++ b/generated/browser/components/About/_About.scss @@ -1,8 +1,6 @@ #about { - margin: 0 auto; - width: 90%; - @include clearfix; + text-align: center; p { width: 40%; @@ -18,21 +16,20 @@ width: 55%; overflow: hidden; margin: 0 auto; + .carousel-inner { height: 500px !important; - } - .carousel-control { - background: none; - } - .carousel-indicators { - display: none; - } - img { - max-width: 100%; - max-height: 500px; - display: block; - margin: 0 auto; + + img { + max-width: 100%; + max-height: 500px; + display: block; + margin: 0 auto; + } } } + .carousel-control.left, .carousel-control.right { + background-image: none; + } } diff --git a/generated/browser/components/About/index.js b/generated/browser/components/About/index.js new file mode 100644 index 0000000..a1afdc8 --- /dev/null +++ b/generated/browser/components/About/index.js @@ -0,0 +1 @@ +export default from './About'; diff --git a/generated/browser/components/Docs/Docs.js b/generated/browser/components/Docs/Docs.js new file mode 100644 index 0000000..cc1116b --- /dev/null +++ b/generated/browser/components/Docs/Docs.js @@ -0,0 +1,14 @@ +'use strict'; + +import React from 'react'; +import './_Docs'; + +const Docs = (props) => { + return ( +
+

Documentation can be found in the project's Github wiki.

+
+ ) +} + +export default Docs; diff --git a/generated/browser/components/Docs/_Docs.scss b/generated/browser/components/Docs/_Docs.scss new file mode 100644 index 0000000..d6a2307 --- /dev/null +++ b/generated/browser/components/Docs/_Docs.scss @@ -0,0 +1,5 @@ +#docs { + + text-align: center; + +} diff --git a/generated/browser/components/Docs/index.js b/generated/browser/components/Docs/index.js new file mode 100644 index 0000000..1b243fa --- /dev/null +++ b/generated/browser/components/Docs/index.js @@ -0,0 +1 @@ +export default from './Docs'; diff --git a/generated/browser/components/Home/Home.js b/generated/browser/components/Home/Home.js new file mode 100644 index 0000000..b68e153 --- /dev/null +++ b/generated/browser/components/Home/Home.js @@ -0,0 +1,17 @@ +'use strict'; + +import React from 'react'; +import Logo from '../../shared/Logo'; +import getRandomGreeting from '../../resources/RandomGreetings'; +import './_Home'; + +const Home = (props) => { + return ( +
+ +

{getRandomGreeting()}

+
+ ) +} + +export default Home; diff --git a/generated/browser/components/Home/_Home.scss b/generated/browser/components/Home/_Home.scss new file mode 100644 index 0000000..067de55 --- /dev/null +++ b/generated/browser/components/Home/_Home.scss @@ -0,0 +1,9 @@ +#home { + + text-align: center; + + .logo { + width: 150px; + margin: 0 auto; + } +} diff --git a/generated/browser/components/Home/index.js b/generated/browser/components/Home/index.js new file mode 100644 index 0000000..f6e9bae --- /dev/null +++ b/generated/browser/components/Home/index.js @@ -0,0 +1 @@ +export default from './Home'; diff --git a/generated/browser/components/MembersOnly/MembersOnly.js b/generated/browser/components/MembersOnly/MembersOnly.js new file mode 100644 index 0000000..b7488db --- /dev/null +++ b/generated/browser/components/MembersOnly/MembersOnly.js @@ -0,0 +1,39 @@ +'use strict'; + +import React, { Component } from 'react'; +import { getStash } from '../../api/secretStash'; +import './_MembersOnly'; + +class MembersOnly extends Component { + constructor (props) { + super(props); + + this.state = { + stash: [] + }; + + this.renderSecretStash = this.renderSecretStash.bind(this); + } + + componentWillMount () { + getStash() + .then(stash => { + this.setState({ ...this.state, stash }) + }) + } + + renderSecretStash () { + return this.state.stash.map(item => ()); + } + + render () { + return ( +
+

Members Only Area

+ {this.renderSecretStash()} +
+ ) + } +} + +export default MembersOnly; diff --git a/generated/browser/components/MembersOnly/_MembersOnly.scss b/generated/browser/components/MembersOnly/_MembersOnly.scss new file mode 100644 index 0000000..0472d54 --- /dev/null +++ b/generated/browser/components/MembersOnly/_MembersOnly.scss @@ -0,0 +1,3 @@ +#members-only { + text-align: center; +} diff --git a/generated/browser/components/MembersOnly/index.js b/generated/browser/components/MembersOnly/index.js new file mode 100644 index 0000000..1cf7700 --- /dev/null +++ b/generated/browser/components/MembersOnly/index.js @@ -0,0 +1 @@ +export default from './MembersOnly'; diff --git a/generated/browser/components/Navbar/Navbar.js b/generated/browser/components/Navbar/Navbar.js new file mode 100644 index 0000000..c052df3 --- /dev/null +++ b/generated/browser/components/Navbar/Navbar.js @@ -0,0 +1,94 @@ +'use strict'; + +import React, { PropTypes } from 'react'; +import { IndexLink, Link } from 'react-router'; +import { push } from 'react-router-redux'; +import { Navbar, Nav, NavItem, Button } from 'react-bootstrap'; +import { LinkContainer, IndexLinkContainer } from 'react-router-bootstrap'; +import { logout } from '../../redux/modules/auth'; +import Logo from '../../shared/Logo'; +import './_Navbar'; + +const NAV_ITEMS = [ + { label: 'Home', path: '/' }, + { label: 'About', path: 'about' }, + { label: 'Documentation', path: 'docs' }, + { label: 'Members Only', path: 'membersOnly', auth: true } +] + +const renderNavItems = (user) => { + return ( + + ) +} + +const renderAuthNavItems = (user, dispatch) => { + + function handleLogout () { + dispatch(logout()).then(() => dispatch(push('/login'))) + } + + if (user) { + return ( + + ) + } else { + return ( + + ) + } +} + +const NavBar = ({ user, dispatch }) => { + return ( + + + + + + + {renderNavItems(user)} + {renderAuthNavItems(user, dispatch)} + + ) +} + +NavBar.propTypes = { + user: PropTypes.object, + dispatch: PropTypes.func +} + +export default NavBar; diff --git a/generated/browser/components/Navbar/_Navbar.scss b/generated/browser/components/Navbar/_Navbar.scss new file mode 100644 index 0000000..86d76b2 --- /dev/null +++ b/generated/browser/components/Navbar/_Navbar.scss @@ -0,0 +1,32 @@ +$red: #ed1b24; +$inverse: #222; +$default: #efefef; + +.navbar { + background-color: white; + + .logo { + margin-top: 10px; + width: 30px; + } + + .navbar-nav>.active>a, + .navbar-nav>li>a { + padding: 0; + margin: 15px; + } + + .navbar-nav>.active>a, + .navbar-nav>.active>a:focus, + .navbar-nav>.active>a:hover, + .navbar-nav>li>a:focus, + .navbar-nav>li>a:hover { + color: $red; + background-color: transparent; + } + + .navbar-nav>.active>a { + background-color: transparent; + } + +} diff --git a/generated/browser/components/Navbar/index.js b/generated/browser/components/Navbar/index.js new file mode 100644 index 0000000..802fb9f --- /dev/null +++ b/generated/browser/components/Navbar/index.js @@ -0,0 +1 @@ +export default from './Navbar'; diff --git a/generated/browser/components/index.js b/generated/browser/components/index.js new file mode 100644 index 0000000..fb53ec1 --- /dev/null +++ b/generated/browser/components/index.js @@ -0,0 +1,5 @@ +export About from './About'; +export Docs from './Docs'; +export Home from './Home'; +export MembersOnly from './MembersOnly'; + diff --git a/generated/browser/config/routes.js b/generated/browser/config/routes.js new file mode 100644 index 0000000..ea231d5 --- /dev/null +++ b/generated/browser/config/routes.js @@ -0,0 +1,81 @@ +'use strict'; + +import React from 'react'; +import { Route, IndexRoute } from 'react-router'; +import { + isLoaded as isAuthLoaded, + load as loadAuth +} from '../redux/modules/auth'; +import { About, Docs, Home, MembersOnly } from '../components'; +import { Layout, Login } from '../containers'; +import { NotFound } from '../shared'; + +const getRoutes = (store) => { + const getAuth = (nextState, replace, next) => { + if (!isAuthLoaded(store.getState())) { + store.dispatch(loadAuth()) + .then(() => next()); + } else { + next(); + } + } + + const requireLogin = (nextState, replace, next) => { + function checkAuth () { + const { auth: { user } } = store.getState(); + if (!user) { + replace('/'); + } + next(); + } + + if (!isAuthLoaded(store.getState())) { + store.dispatch(loadAuth()).then(checkAuth); + } else { + checkAuth(); + } + } + + const requireNoUser = (nextState, replace, next) => { + function checkAuth () { + const { auth: { user } } = store.getState(); + if (user) { + replace('/membersOnly'); + } + next(); + } + + if (!isAuthLoaded(store.getState())) { + store.dispatch(loadAuth()).then(checkAuth) + } else { + checkAuth(); + } + } + + return ( + + + { /* Home route */ } + + + { /* Authenticated Routes */ } + + + + + { /* Unauthenticated Routes Only */ } + + + + + { /* Routes */ } + + + + { /* Catch all routes */ } + + + ) +} + +export default getRoutes; diff --git a/generated/browser/containers/Layout/Layout.js b/generated/browser/containers/Layout/Layout.js new file mode 100644 index 0000000..2614c19 --- /dev/null +++ b/generated/browser/containers/Layout/Layout.js @@ -0,0 +1,32 @@ +'use strict'; + +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import Navbar from '../../components/Navbar'; +import './_Layout'; + +class Layout extends Component { + + static propTypes = { + children: PropTypes.object, + user: PropTypes.object, + dispatch: PropTypes.func + } + + render () { + const { children, user, dispatch } = this.props + + return ( +
+ +
+ {children} +
+
+ ) + } +} + +const mapStateToProps = (state) => ({ user: state.auth.user }); + +export default connect(mapStateToProps)(Layout); diff --git a/generated/browser/containers/Layout/_Layout.scss b/generated/browser/containers/Layout/_Layout.scss new file mode 100644 index 0000000..b939552 --- /dev/null +++ b/generated/browser/containers/Layout/_Layout.scss @@ -0,0 +1,9 @@ +body { + font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +#main { + padding-top: 60px; +} + +@import url(https://fonts.googleapis.com/css?family=Roboto:400,100,300,500,700); diff --git a/generated/browser/containers/Layout/index.js b/generated/browser/containers/Layout/index.js new file mode 100644 index 0000000..0720a9d --- /dev/null +++ b/generated/browser/containers/Layout/index.js @@ -0,0 +1 @@ +export default from './Layout'; diff --git a/generated/browser/containers/Login/Login.js b/generated/browser/containers/Login/Login.js new file mode 100644 index 0000000..cb2f1d9 --- /dev/null +++ b/generated/browser/containers/Login/Login.js @@ -0,0 +1,30 @@ +'use strict'; + +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { push } from 'react-router-redux'; +import { AuthForm } from '../../shared'; +import { login } from '../../redux/modules/auth'; + +class Login extends Component { + + static propTypes = { + dispatch: PropTypes.func + } + + handleLogin = (credentials) => { + const { dispatch } = this.props + dispatch(login(credentials)).then(() => dispatch(push('/membersOnly'))) + } + + render () { + return ( + + ) + } +} + +export default connect()(Login); diff --git a/generated/browser/containers/Login/index.js b/generated/browser/containers/Login/index.js new file mode 100644 index 0000000..5fa85e6 --- /dev/null +++ b/generated/browser/containers/Login/index.js @@ -0,0 +1 @@ +export default from './Login'; diff --git a/generated/browser/containers/index.js b/generated/browser/containers/index.js new file mode 100644 index 0000000..e3c3516 --- /dev/null +++ b/generated/browser/containers/index.js @@ -0,0 +1,2 @@ +export Layout from './Layout'; +export Login from './Login'; diff --git a/generated/browser/js/.eslintrc.json b/generated/browser/js/.eslintrc.json deleted file mode 100644 index 3b74b6a..0000000 --- a/generated/browser/js/.eslintrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "env": { - "browser": true - }, - "globals": { - "app": true, - "_": true - } -} diff --git a/generated/browser/js/about/about.html b/generated/browser/js/about/about.html deleted file mode 100644 index c8406a7..0000000 --- a/generated/browser/js/about/about.html +++ /dev/null @@ -1,11 +0,0 @@ -
-

- This website--the very one at which you currently gaze--was created by a basic scaffolding - concocted at Fullstack Academy. Here are some of the people who make it all very real: -

- - - - - -
\ No newline at end of file diff --git a/generated/browser/js/about/about.js b/generated/browser/js/about/about.js deleted file mode 100644 index 67c42d1..0000000 --- a/generated/browser/js/about/about.js +++ /dev/null @@ -1,17 +0,0 @@ -app.config(function ($stateProvider) { - - // Register our *about* state. - $stateProvider.state('about', { - url: '/about', - controller: 'AboutController', - templateUrl: 'js/about/about.html' - }); - -}); - -app.controller('AboutController', function ($scope, FullstackPics) { - - // Images of beautiful Fullstack people. - $scope.images = _.shuffle(FullstackPics); - -}); diff --git a/generated/browser/js/app.js b/generated/browser/js/app.js deleted file mode 100644 index a5de5cb..0000000 --- a/generated/browser/js/app.js +++ /dev/null @@ -1,63 +0,0 @@ -'use strict'; -window.app = angular.module('FullstackGeneratedApp', ['fsaPreBuilt', 'ui.router', 'ui.bootstrap', 'ngAnimate']); - -app.config(function ($urlRouterProvider, $locationProvider) { - // This turns off hashbang urls (/#about) and changes it to something normal (/about) - $locationProvider.html5Mode(true); - // If we go to a URL that ui-router doesn't have registered, go to the "/" url. - $urlRouterProvider.otherwise('/'); - // Trigger page refresh when accessing an OAuth route - $urlRouterProvider.when('/auth/:provider', function () { - window.location.reload(); - }); -}); - -// This app.run is for listening to errors broadcasted by ui-router, usually originating from resolves -app.run(function ($rootScope) { - $rootScope.$on('$stateChangeError', function (event, toState, toParams, fromState, fromParams, thrownError) { - console.info(`The following error was thrown by ui-router while transitioning to state "${toState.name}". The origin of this error is probably a resolve function:`); - console.error(thrownError); - }); -}); - -// This app.run is for controlling access to specific states. -app.run(function ($rootScope, AuthService, $state) { - - // The given state requires an authenticated user. - var destinationStateRequiresAuth = function (state) { - return state.data && state.data.authenticate; - }; - - // $stateChangeStart is an event fired - // whenever the process of changing a state begins. - $rootScope.$on('$stateChangeStart', function (event, toState, toParams) { - - if (!destinationStateRequiresAuth(toState)) { - // The destination state does not require authentication - // Short circuit with return. - return; - } - - if (AuthService.isAuthenticated()) { - // The user is authenticated. - // Short circuit with return. - return; - } - - // Cancel navigating to new state. - event.preventDefault(); - - AuthService.getLoggedInUser().then(function (user) { - // If a user is retrieved, then renavigate to the destination - // (the second time, AuthService.isAuthenticated() will work) - // otherwise, if no user is logged in, go to "login" state. - if (user) { - $state.go(toState.name, toParams); - } else { - $state.go('login'); - } - }); - - }); - -}); diff --git a/generated/browser/js/common/directives/fullstack-logo/fullstack-logo.html b/generated/browser/js/common/directives/fullstack-logo/fullstack-logo.html deleted file mode 100644 index aab0a2a..0000000 --- a/generated/browser/js/common/directives/fullstack-logo/fullstack-logo.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/generated/browser/js/common/directives/fullstack-logo/fullstack-logo.js b/generated/browser/js/common/directives/fullstack-logo/fullstack-logo.js deleted file mode 100644 index b6768eb..0000000 --- a/generated/browser/js/common/directives/fullstack-logo/fullstack-logo.js +++ /dev/null @@ -1,6 +0,0 @@ -app.directive('fullstackLogo', function () { - return { - restrict: 'E', - templateUrl: 'js/common/directives/fullstack-logo/fullstack-logo.html' - }; -}); diff --git a/generated/browser/js/common/directives/navbar/navbar.html b/generated/browser/js/common/directives/navbar/navbar.html deleted file mode 100644 index 9fdafe9..0000000 --- a/generated/browser/js/common/directives/navbar/navbar.html +++ /dev/null @@ -1,17 +0,0 @@ - \ No newline at end of file diff --git a/generated/browser/js/common/directives/navbar/navbar.js b/generated/browser/js/common/directives/navbar/navbar.js deleted file mode 100644 index b3c8237..0000000 --- a/generated/browser/js/common/directives/navbar/navbar.js +++ /dev/null @@ -1,48 +0,0 @@ -app.directive('navbar', function ($rootScope, AuthService, AUTH_EVENTS, $state) { - - return { - restrict: 'E', - scope: {}, - templateUrl: 'js/common/directives/navbar/navbar.html', - link: function (scope) { - - scope.items = [ - { label: 'Home', state: 'home' }, - { label: 'About', state: 'about' }, - { label: 'Documentation', state: 'docs' }, - { label: 'Members Only', state: 'membersOnly', auth: true } - ]; - - scope.user = null; - - scope.isLoggedIn = function () { - return AuthService.isAuthenticated(); - }; - - scope.logout = function () { - AuthService.logout().then(function () { - $state.go('home'); - }); - }; - - var setUser = function () { - AuthService.getLoggedInUser().then(function (user) { - scope.user = user; - }); - }; - - var removeUser = function () { - scope.user = null; - }; - - setUser(); - - $rootScope.$on(AUTH_EVENTS.loginSuccess, setUser); - $rootScope.$on(AUTH_EVENTS.logoutSuccess, removeUser); - $rootScope.$on(AUTH_EVENTS.sessionTimeout, removeUser); - - } - - }; - -}); diff --git a/generated/browser/js/common/directives/rando-greeting/rando-greeting.html b/generated/browser/js/common/directives/rando-greeting/rando-greeting.html deleted file mode 100644 index b25b763..0000000 --- a/generated/browser/js/common/directives/rando-greeting/rando-greeting.html +++ /dev/null @@ -1 +0,0 @@ -

{{ greeting }}

\ No newline at end of file diff --git a/generated/browser/js/common/directives/rando-greeting/rando-greeting.js b/generated/browser/js/common/directives/rando-greeting/rando-greeting.js deleted file mode 100644 index ebab94a..0000000 --- a/generated/browser/js/common/directives/rando-greeting/rando-greeting.js +++ /dev/null @@ -1,11 +0,0 @@ -app.directive('randoGreeting', function (RandomGreetings) { - - return { - restrict: 'E', - templateUrl: 'js/common/directives/rando-greeting/rando-greeting.html', - link: function (scope) { - scope.greeting = RandomGreetings.getRandomGreeting(); - } - }; - -}); diff --git a/generated/browser/js/common/factories/FullstackPics.js b/generated/browser/js/common/factories/FullstackPics.js deleted file mode 100644 index baa2e28..0000000 --- a/generated/browser/js/common/factories/FullstackPics.js +++ /dev/null @@ -1,30 +0,0 @@ -app.factory('FullstackPics', function () { - return [ - 'https://pbs.twimg.com/media/B7gBXulCAAAXQcE.jpg:large', - 'https://fbcdn-sphotos-c-a.akamaihd.net/hphotos-ak-xap1/t31.0-8/10862451_10205622990359241_8027168843312841137_o.jpg', - 'https://pbs.twimg.com/media/B-LKUshIgAEy9SK.jpg', - 'https://pbs.twimg.com/media/B79-X7oCMAAkw7y.jpg', - 'https://pbs.twimg.com/media/B-Uj9COIIAIFAh0.jpg:large', - 'https://pbs.twimg.com/media/B6yIyFiCEAAql12.jpg:large', - 'https://pbs.twimg.com/media/CE-T75lWAAAmqqJ.jpg:large', - 'https://pbs.twimg.com/media/CEvZAg-VAAAk932.jpg:large', - 'https://pbs.twimg.com/media/CEgNMeOXIAIfDhK.jpg:large', - 'https://pbs.twimg.com/media/CEQyIDNWgAAu60B.jpg:large', - 'https://pbs.twimg.com/media/CCF3T5QW8AE2lGJ.jpg:large', - 'https://pbs.twimg.com/media/CAeVw5SWoAAALsj.jpg:large', - 'https://pbs.twimg.com/media/CAaJIP7UkAAlIGs.jpg:large', - 'https://pbs.twimg.com/media/CAQOw9lWEAAY9Fl.jpg:large', - 'https://pbs.twimg.com/media/B-OQbVrCMAANwIM.jpg:large', - 'https://pbs.twimg.com/media/B9b_erwCYAAwRcJ.png:large', - 'https://pbs.twimg.com/media/B5PTdvnCcAEAl4x.jpg:large', - 'https://pbs.twimg.com/media/B4qwC0iCYAAlPGh.jpg:large', - 'https://pbs.twimg.com/media/B2b33vRIUAA9o1D.jpg:large', - 'https://pbs.twimg.com/media/BwpIwr1IUAAvO2_.jpg:large', - 'https://pbs.twimg.com/media/BsSseANCYAEOhLw.jpg:large', - 'https://pbs.twimg.com/media/CJ4vLfuUwAAda4L.jpg:large', - 'https://pbs.twimg.com/media/CI7wzjEVEAAOPpS.jpg:large', - 'https://pbs.twimg.com/media/CIdHvT2UsAAnnHV.jpg:large', - 'https://pbs.twimg.com/media/CGCiP_YWYAAo75V.jpg:large', - 'https://pbs.twimg.com/media/CIS4JPIWIAI37qu.jpg:large' - ]; -}); diff --git a/generated/browser/js/common/factories/RandomGreetings.js b/generated/browser/js/common/factories/RandomGreetings.js deleted file mode 100644 index 55d7390..0000000 --- a/generated/browser/js/common/factories/RandomGreetings.js +++ /dev/null @@ -1,29 +0,0 @@ -app.factory('RandomGreetings', function () { - - var getRandomFromArray = function (arr) { - return arr[Math.floor(Math.random() * arr.length)]; - }; - - var greetings = [ - 'Hello, world!', - 'At long last, I live!', - 'Hello, simple human.', - 'What a beautiful day!', - 'I\'m like any other project, except that I am yours. :)', - 'This empty string is for Lindsay Levine.', - 'こんにちは、ユーザー様。', - 'Welcome. To. WEBSITE.', - ':D', - 'Yes, I think we\'ve met before.', - 'Gimme 3 mins... I just grabbed this really dope frittata', - 'If Cooper could offer only one piece of advice, it would be to nevSQUIRREL!', - ]; - - return { - greetings: greetings, - getRandomGreeting: function () { - return getRandomFromArray(greetings); - } - }; - -}); diff --git a/generated/browser/js/docs/docs.html b/generated/browser/js/docs/docs.html deleted file mode 100644 index 54da1f7..0000000 --- a/generated/browser/js/docs/docs.html +++ /dev/null @@ -1,4 +0,0 @@ -

- Documentation can be found in - the project's Github wiki. -

diff --git a/generated/browser/js/docs/docs.js b/generated/browser/js/docs/docs.js deleted file mode 100644 index 4df733b..0000000 --- a/generated/browser/js/docs/docs.js +++ /dev/null @@ -1,6 +0,0 @@ -app.config(function ($stateProvider) { - $stateProvider.state('docs', { - url: '/docs', - templateUrl: 'js/docs/docs.html' - }); -}); diff --git a/generated/browser/js/fsa/fsa-pre-built.js b/generated/browser/js/fsa/fsa-pre-built.js deleted file mode 100644 index 6f5bd13..0000000 --- a/generated/browser/js/fsa/fsa-pre-built.js +++ /dev/null @@ -1,130 +0,0 @@ -(function () { - - 'use strict'; - - // Hope you didn't forget Angular! Duh-doy. - if (!window.angular) throw new Error('I can\'t find Angular!'); - - var app = angular.module('fsaPreBuilt', []); - - app.factory('Socket', function () { - if (!window.io) throw new Error('socket.io not found!'); - return window.io(window.location.origin); - }); - - // AUTH_EVENTS is used throughout our app to - // broadcast and listen from and to the $rootScope - // for important events about authentication flow. - app.constant('AUTH_EVENTS', { - loginSuccess: 'auth-login-success', - loginFailed: 'auth-login-failed', - logoutSuccess: 'auth-logout-success', - sessionTimeout: 'auth-session-timeout', - notAuthenticated: 'auth-not-authenticated', - notAuthorized: 'auth-not-authorized' - }); - - app.factory('AuthInterceptor', function ($rootScope, $q, AUTH_EVENTS) { - var statusDict = { - 401: AUTH_EVENTS.notAuthenticated, - 403: AUTH_EVENTS.notAuthorized, - 419: AUTH_EVENTS.sessionTimeout, - 440: AUTH_EVENTS.sessionTimeout - }; - return { - responseError: function (response) { - $rootScope.$broadcast(statusDict[response.status], response); - return $q.reject(response) - } - }; - }); - - app.config(function ($httpProvider) { - $httpProvider.interceptors.push([ - '$injector', - function ($injector) { - return $injector.get('AuthInterceptor'); - } - ]); - }); - - app.service('AuthService', function ($http, Session, $rootScope, AUTH_EVENTS, $q) { - - function onSuccessfulLogin(response) { - var user = response.data.user; - Session.create(user); - $rootScope.$broadcast(AUTH_EVENTS.loginSuccess); - return user; - } - - // Uses the session factory to see if an - // authenticated user is currently registered. - this.isAuthenticated = function () { - return !!Session.user; - }; - - this.getLoggedInUser = function (fromServer) { - - // If an authenticated session exists, we - // return the user attached to that session - // with a promise. This ensures that we can - // always interface with this method asynchronously. - - // Optionally, if true is given as the fromServer parameter, - // then this cached value will not be used. - - if (this.isAuthenticated() && fromServer !== true) { - return $q.when(Session.user); - } - - // Make request GET /session. - // If it returns a user, call onSuccessfulLogin with the response. - // If it returns a 401 response, we catch it and instead resolve to null. - return $http.get('/session').then(onSuccessfulLogin).catch(function () { - return null; - }); - - }; - - this.login = function (credentials) { - return $http.post('/login', credentials) - .then(onSuccessfulLogin) - .catch(function () { - return $q.reject({ message: 'Invalid login credentials.' }); - }); - }; - - this.logout = function () { - return $http.get('/logout').then(function () { - Session.destroy(); - $rootScope.$broadcast(AUTH_EVENTS.logoutSuccess); - }); - }; - - }); - - app.service('Session', function ($rootScope, AUTH_EVENTS) { - - var self = this; - - $rootScope.$on(AUTH_EVENTS.notAuthenticated, function () { - self.destroy(); - }); - - $rootScope.$on(AUTH_EVENTS.sessionTimeout, function () { - self.destroy(); - }); - - this.user = null; - - this.create = function (user) { - this.user = user; - }; - - this.destroy = function () { - this.user = null; - }; - - }); - -}()); diff --git a/generated/browser/js/home/home.html b/generated/browser/js/home/home.html deleted file mode 100644 index 6141819..0000000 --- a/generated/browser/js/home/home.html +++ /dev/null @@ -1,4 +0,0 @@ -
- - -
diff --git a/generated/browser/js/home/home.js b/generated/browser/js/home/home.js deleted file mode 100644 index 55f55e0..0000000 --- a/generated/browser/js/home/home.js +++ /dev/null @@ -1,6 +0,0 @@ -app.config(function ($stateProvider) { - $stateProvider.state('home', { - url: '/', - templateUrl: 'js/home/home.html' - }); -}); diff --git a/generated/browser/js/login/login.html b/generated/browser/js/login/login.html deleted file mode 100644 index 4fee030..0000000 --- a/generated/browser/js/login/login.html +++ /dev/null @@ -1,16 +0,0 @@ -
- - - {{ error }} - - -
- - -
-
- - -
- -
\ No newline at end of file diff --git a/generated/browser/js/login/login.js b/generated/browser/js/login/login.js deleted file mode 100644 index afb6bcb..0000000 --- a/generated/browser/js/login/login.js +++ /dev/null @@ -1,28 +0,0 @@ -app.config(function ($stateProvider) { - - $stateProvider.state('login', { - url: '/login', - templateUrl: 'js/login/login.html', - controller: 'LoginCtrl' - }); - -}); - -app.controller('LoginCtrl', function ($scope, AuthService, $state) { - - $scope.login = {}; - $scope.error = null; - - $scope.sendLogin = function (loginInfo) { - - $scope.error = null; - - AuthService.login(loginInfo).then(function () { - $state.go('home'); - }).catch(function () { - $scope.error = 'Invalid login credentials.'; - }); - - }; - -}); diff --git a/generated/browser/js/members-only/members-only.js b/generated/browser/js/members-only/members-only.js deleted file mode 100644 index d95bdc1..0000000 --- a/generated/browser/js/members-only/members-only.js +++ /dev/null @@ -1,32 +0,0 @@ -app.config(function ($stateProvider) { - - $stateProvider.state('membersOnly', { - url: '/members-area', - template: '', - controller: function ($scope, SecretStash) { - SecretStash.getStash().then(function (stash) { - $scope.stash = stash; - }); - }, - // The following data.authenticate is read by an event listener - // that controls access to this state. Refer to app.js. - data: { - authenticate: true - } - }); - -}); - -app.factory('SecretStash', function ($http) { - - var getStash = function () { - return $http.get('/api/members/secret-stash').then(function (response) { - return response.data; - }); - }; - - return { - getStash: getStash - }; - -}); diff --git a/generated/browser/redux/configureStore.js b/generated/browser/redux/configureStore.js new file mode 100644 index 0000000..85b2745 --- /dev/null +++ b/generated/browser/redux/configureStore.js @@ -0,0 +1,28 @@ +'use strict'; + +import { createStore, combineReducers, applyMiddleware, compose } from 'redux' +import { routerMiddleware, routerReducer as routing } from 'react-router-redux' +import createLogger from 'redux-logger' +import thunk from 'redux-thunk' +import promise from './middleware/promise' +import auth from './modules/auth' + +export default function configureStore (history, initialState) { + + const reducer = combineReducers({ + auth, + routing + }) + + const middleware = [thunk, promise, routerMiddleware(history)] + if (process.env.NODE_ENV !== 'production') { + middleware.push(createLogger()) + } + + const enhancers = compose( + applyMiddleware(...middleware), + window.devToolsExtension ? window.devToolsExtension() : f => f + ) + + return createStore(reducer, initialState, enhancers) +} diff --git a/generated/browser/redux/middleware/promise.js b/generated/browser/redux/middleware/promise.js new file mode 100644 index 0000000..97b673e --- /dev/null +++ b/generated/browser/redux/middleware/promise.js @@ -0,0 +1,29 @@ +'use strict'; + +import { parseJSON, parseData } from '../../utils' + +export default () => next => action => { + + const { promise, types, ...rest } = action + + if (!promise) { + return next(action) + } + + const [REQUEST, SUCCESS, FAILURE] = types + next({ ...rest, type: REQUEST }) + + return promise + .then(parseData) + .then( + result => next({ ...rest, result, type: SUCCESS }), + error => next({ ...rest, error, type: FAILURE}) + ) + .catch(error => { + next({ + ...rest, + error: `MIDDLEWARE ERROR: ${error.message}`, + type: FAILURE + }) + }) +} diff --git a/generated/browser/redux/modules/auth/auth.test.js b/generated/browser/redux/modules/auth/auth.test.js new file mode 100644 index 0000000..4d5fdfa --- /dev/null +++ b/generated/browser/redux/modules/auth/auth.test.js @@ -0,0 +1,54 @@ +'use strict'; + +import { expect } from 'chai'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import promise from '../../middleware/promise'; +import reducer from '../auth'; +import * as test from './tests'; + +const middlewares = [ thunk, promise ]; +const mockStore = configureMockStore(middlewares); + +describe('MODULE - auth:', () => { + describe('ACTIONS', () => { + let store = mockStore({ session: {} }); + + // isLoaded action test + test.isLoaded(mockStore); + + // load action tests + test.load(mockStore); + + // login action tests + test.login(mockStore); + + // logout action tests + test.logout(mockStore); + + }); + + describe('REDUCER', () => { + + const initialState = { + loaded: false + }; + + it('returns the initial state', () => { + expect(reducer(undefined, {})).to.deep.equal(initialState) + }); + + describe('handles actions', () => { + // LOAD action tests + test.loadActions(); + + // LOGIN action tests + test.loginActions(); + + // LOGOUT action tests + test.logoutActions(); + }); + + }); + +}); diff --git a/generated/browser/redux/modules/auth/index.js b/generated/browser/redux/modules/auth/index.js new file mode 100644 index 0000000..7931383 --- /dev/null +++ b/generated/browser/redux/modules/auth/index.js @@ -0,0 +1,105 @@ +'use strict'; + +import { push } from 'react-router-redux'; +import { auth } from '../../../api'; + +export const LOAD = 'LOAD'; +export const LOAD_SUCCESS = 'LOAD_SUCCESS'; +export const LOAD_FAILURE = 'LOAD_FAILURE'; +export const LOGIN = 'LOGIN'; +export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; +export const LOGIN_FAILURE = 'LOGIN_FAILURE'; +export const LOGOUT = 'LOGOUT'; +export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; +export const LOGOUT_FAILURE = 'LOGOUT_FAILURE'; + +export function isLoaded (globalState) { + return globalState.auth && globalState.auth.loaded +} + +export const load = () => (dispatch, getState) => { + dispatch({ type: LOAD }); + const { auth: { user } } = getState(); + + if (user) { + dispatch({ type: LOAD_SUCCESS, user }); + } else { + return auth.fetchSession() + .then( + result => dispatch({ type: LOAD_SUCCESS, result }), + error => dispatch({ type: LOAD_FAILURE, error }) + ) + .catch(error => { + dispatch({ + type: LOAD_FAILURE, + error: error.message || `An error occured` + }) + }); + } +} + +export function login (credentials) { + return { + types: [LOGIN, LOGIN_SUCCESS, LOGIN_FAILURE], + promise: auth.tryLogin(credentials) + } +} + +export function logout () { + return { + types: [LOGOUT, LOGOUT_SUCCESS, LOGOUT_FAILURE], + promise: auth.tryLogout() + } +} + +const initialState = { + loaded: false +} + +export default function reducer (state = initialState, action) { + switch (action.type) { + case LOAD: + return { + ...state, + loading: true, + loadError: null + } + case LOGIN: + case LOGOUT: + return { + ...state, + loading: true, + error: null + } + case LOAD_SUCCESS: + case LOGIN_SUCCESS: + return { + ...state, + loading: false, + loaded: true, + user: action.result.user + } + case LOGOUT_SUCCESS: + return { + ...state, + loading: false, + loaded: false, + user: null + } + case LOAD_FAILURE: + return { + ...state, + loading: false, + loadError: action.error.response + } + case LOGIN_FAILURE: + case LOGOUT_FAILURE: + return { + ...state, + loading: false, + error: action.error.response + } + default: + return state + } +} diff --git a/generated/browser/redux/modules/auth/tests/index.js b/generated/browser/redux/modules/auth/tests/index.js new file mode 100644 index 0000000..d0f40e5 --- /dev/null +++ b/generated/browser/redux/modules/auth/tests/index.js @@ -0,0 +1,7 @@ +export isLoaded from './isLoaded.test'; +export load from './load.test'; +export loadActions from './loadActions.test'; +export login from './login.test'; +export loginActions from './loginActions.test'; +export logout from './logout.test'; +export logoutActions from './logoutActions.test'; diff --git a/generated/browser/redux/modules/auth/tests/isLoaded.test.js b/generated/browser/redux/modules/auth/tests/isLoaded.test.js new file mode 100644 index 0000000..633349b --- /dev/null +++ b/generated/browser/redux/modules/auth/tests/isLoaded.test.js @@ -0,0 +1,20 @@ +'use strict'; + +import { expect } from 'chai'; +import { isLoaded } from '../../auth'; + +export default function (mockStore) { + describe('isLoaded', () => { + let store; + + it('returns true if session has been loaded', () => { + store = mockStore({ auth: { loaded: true }}); + expect(isLoaded(store.getState())).to.be.true; + }); + + it('returns false if session has not been loaded', () => { + store = mockStore({ auth: { loaded: false }}); + expect(isLoaded(store.getState())).to.be.false; + }); + }); +} diff --git a/generated/browser/redux/modules/auth/tests/load.test.js b/generated/browser/redux/modules/auth/tests/load.test.js new file mode 100644 index 0000000..1c727a3 --- /dev/null +++ b/generated/browser/redux/modules/auth/tests/load.test.js @@ -0,0 +1,64 @@ +'use strict'; + +import { expect } from 'chai'; +import nock from 'nock'; +import { + load, + LOAD, LOAD_SUCCESS, LOAD_FAILURE +} from '../../auth'; + +const credentials = { + email: 'test@test.com', + password: 'test1234' +}; + +export default function (mockStore) { + describe('load', () => { + let store; + + beforeEach(() => { + store = mockStore({ auth: {} }); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + const sessionAPICall = nock(`http://localhost:${process.env.PORT || 1337}`) + .get('/session'); + + it('creates LOAD when initially dispatched', () => { + sessionAPICall.reply(200, credentials); + + return store.dispatch(load()) + .then(() => { + const actionTypes = store.getActions().map(action => action.type) + expect(actionTypes).to.include(LOAD) + }); + }); + + it('creates LOAD_SUCCESS with result when load was successfully done', () => { + sessionAPICall.reply(200, credentials); + + return store.dispatch(load()) + .then(() => { + const actions = store.getActions().filter(action => { + return action.result && action.type === LOAD_SUCCESS + }); + expect(actions).to.have.length(1); + expect(actions[0].type).to.equal(LOAD_SUCCESS); + expect(actions[0].result).to.deep.equal(credentials); + }); + }); + + it ('creates LOAD_FAILURE when load request fails', () => { + sessionAPICall.replyWithError({ message: 'Error occured' }); + + return store.dispatch(load()) + .then(() => { + const actionTypes = store.getActions().map(action => action.type); + expect(actionTypes).to.include(LOAD_FAILURE); + }); + }); + }); +} diff --git a/generated/browser/redux/modules/auth/tests/loadActions.test.js b/generated/browser/redux/modules/auth/tests/loadActions.test.js new file mode 100644 index 0000000..30e1bfd --- /dev/null +++ b/generated/browser/redux/modules/auth/tests/loadActions.test.js @@ -0,0 +1,71 @@ +'use strict'; + +import { expect } from 'chai'; +import reducer, { LOAD, LOAD_SUCCESS, LOAD_FAILURE } from '../../auth'; + +const successRes = { + user: { email: 'test@test.com', password: 'test1234' } +}; + +const failureRes = { + response: { data: 'ERROR' } +}; + +export default function () { + describe('LOAD', () => { + const nextState = reducer( + { loading: false, loadError: failureRes }, + { type: LOAD } + ); + + it('by setting \'loading\' to true', () => { + expect(nextState.loading).to.be.true; + }); + + it('by setting \'loadError\' to null', () => { + expect(nextState.loadError).to.be.null; + }); + }); + + describe('LOAD_SUCCESS', () => { + const nextState = reducer( + { loading: true, loaded: false }, + { type: LOAD_SUCCESS, result: successRes } + ); + + it('by setting \'loading\' to false', () => { + expect(nextState.loading).to.be.false; + }); + + it('by setting \'loaded\' to true', () => { + expect(nextState.loaded).to.be.true; + }); + + it('by setting \'user\'', () => { + expect(nextState.user).to.exist; + }); + }); + + describe('LOAD_FAILURE', () => { + const nextState = reducer( + { loading: true, loaded: false }, + { type: LOAD_FAILURE, error: failureRes} + ); + + it('by setting \'loading\' to false', () => { + expect(nextState.loading).to.be.false; + }); + + it('by setting \'loadError\'', () => { + expect(nextState.loadError).to.exist; + }); + + it('by not setting \'loaded\' to true', () => { + expect(nextState.loaded).to.be.false; + }); + + it('by not setting \'user\'', () => { + expect(nextState.user).to.not.exist; + }); + }); +} diff --git a/generated/browser/redux/modules/auth/tests/login.test.js b/generated/browser/redux/modules/auth/tests/login.test.js new file mode 100644 index 0000000..f095ed9 --- /dev/null +++ b/generated/browser/redux/modules/auth/tests/login.test.js @@ -0,0 +1,66 @@ +'use strict'; + +import { expect } from 'chai'; +import nock from 'nock'; +import { + login, + LOGIN, LOGIN_SUCCESS, LOGIN_FAILURE +} from '../../auth'; + +const credentials = { + email: 'test@test.com', + password: 'test1234' +}; + +export default function (mockStore) { + describe('login', () => { + let store; + + beforeEach(() => { + store = mockStore({ auth: {} }); + }) + + afterEach(() => { + nock.cleanAll(); + }) + + const loginAPICall = () => nock(`http://localhost:${process.env.PORT || 1337}`) + .post('/login', credentials); + + it('creates LOGIN when initially dispatched', () => { + loginAPICall().reply(200, credentials); + + return store.dispatch(login(credentials)) + .then(() => { + const actionTypes = store.getActions().map(action => action.type) + expect(actionTypes).to.include(LOGIN) + }); + }) + + it('creates LOGIN_SUCCESS when login was successfully done', () => { + loginAPICall().reply(200, credentials); + + return store.dispatch(login(credentials)) + .then(() => { + const actions = store.getActions().filter(action => { + return action.result && action.type === LOGIN_SUCCESS + }); + expect(actions).to.have.length(1); + expect(actions[0].type).to.equal(LOGIN_SUCCESS); + expect(actions[0].result).to.deep.equal(credentials); + }); + }) + + it('creates LOGIN_FAILURE when login request fails', () => { + loginAPICall().replyWithError({ message: 'Invalid credentials' }); + + return store.dispatch(login(credentials)) + .then(() => { + const actionTypes = store.getActions().map(action => action.type); + expect(actionTypes).to.include(LOGIN_FAILURE); + }); + }); + }); +} + + diff --git a/generated/browser/redux/modules/auth/tests/loginActions.test.js b/generated/browser/redux/modules/auth/tests/loginActions.test.js new file mode 100644 index 0000000..86cb76e --- /dev/null +++ b/generated/browser/redux/modules/auth/tests/loginActions.test.js @@ -0,0 +1,71 @@ +'use strict'; + +import { expect } from 'chai'; +import reducer, { LOGIN, LOGIN_SUCCESS, LOGIN_FAILURE } from '../../auth'; + +const successRes = { + user: { email: 'test@test.com', password: 'test1234' } +}; + +const failureRes = { + response: { data: 'ERROR' } +}; + +export default function () { + describe('LOGIN', () => { + const nextState = reducer( + { loading: false, error: failureRes }, + { type: LOGIN } + ); + + it('by setting \'loading\' to true', () => { + expect(nextState.loading).to.be.true; + }); + + it('by setting \'error\' to null', () => { + expect(nextState.error).to.be.null; + }); + }); + + describe('LOGIN_SUCCESS', () => { + const nextState = reducer( + { loading: true, loaded: false }, + { type: LOGIN_SUCCESS, result: successRes } + ); + + it('by setting \'loading\' to false', () => { + expect(nextState.loading).to.be.false; + }); + + it('by setting \'loaded\' to true', () => { + expect(nextState.loaded).to.be.true; + }); + + it('by setting \'user\'', () => { + expect(nextState.user).to.exist; + }); + }); + + describe('LOGIN_FAILURE', () => { + const nextState = reducer( + { loading: true, loaded: false }, + { type: LOGIN_FAILURE, error: failureRes} + ); + + it('by setting \'loading\' to false', () => { + expect(nextState.loading).to.be.false; + }); + + it('by setting \'error\'', () => { + expect(nextState.error).to.exist; + }); + + it('by not setting \'loaded\' to true', () => { + expect(nextState.loaded).to.be.false; + }); + + it('by not setting \'user\'', () => { + expect(nextState.user).to.not.exist; + }); + }); +} diff --git a/generated/browser/redux/modules/auth/tests/logout.test.js b/generated/browser/redux/modules/auth/tests/logout.test.js new file mode 100644 index 0000000..8acead3 --- /dev/null +++ b/generated/browser/redux/modules/auth/tests/logout.test.js @@ -0,0 +1,60 @@ +'use strict'; + +import { expect } from 'chai'; +import nock from 'nock'; +import { + logout, + LOGOUT, LOGOUT_SUCCESS, LOGOUT_FAILURE +} from '../../auth'; + +const credentials = { + email: 'test@test.com', + password: 'test1234' +}; + +export default function (mockStore) { + describe('logout', () => { + let store; + + beforeEach(() => { + store = mockStore({ auth: {} }); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + const logoutAPICall = nock(`http://localhost:${process.env.PORT || 1337}`) + .get('/logout'); + + it('creates LOGOUT when initially dispatched', () => { + logoutAPICall.reply(200); + + return store.dispatch(logout()) + .then(() => { + const actionTypes = store.getActions().map(action => action.type); + expect(actionTypes).to.include(LOGOUT); + }); + }); + + it('creates LOGOUT_SUCCESS when login was successfully done', () => { + logoutAPICall.reply(200); + + return store.dispatch(logout()) + .then(() => { + const actionTypes = store.getActions().map(action => action.type); + expect(actionTypes).to.include(LOGOUT_SUCCESS); + }); + }); + + it('creates LOGOUT_FAILURE when login request fails', () => { + logoutAPICall.replyWithError({ message: 'Logout Failed' }); + + return store.dispatch(logout()) + .then(() => { + const actionTypes = store.getActions().map(action => action.type); + expect(actionTypes).to.include(LOGOUT_FAILURE); + }); + }); + }); +} diff --git a/generated/browser/redux/modules/auth/tests/logoutActions.test.js b/generated/browser/redux/modules/auth/tests/logoutActions.test.js new file mode 100644 index 0000000..0f8f4ba --- /dev/null +++ b/generated/browser/redux/modules/auth/tests/logoutActions.test.js @@ -0,0 +1,72 @@ +'use strict'; + +import { expect } from 'chai'; +import reducer, { LOGOUT, LOGOUT_SUCCESS, LOGOUT_FAILURE } from '../../auth'; + +const credentials = { + email: 'test@test.com', + password: 'test1234' +}; + +const failureRes = { + response: { data: 'ERROR' } +}; + +export default function () { + describe('LOGOUT', () => { + const nextState = reducer( + { loading: false, error: failureRes, user: credentials }, + { type: LOGOUT } + ); + + it('by setting \'loading\' to true', () => { + expect(nextState.loading).to.be.true; + }); + + it('by setting \'error\' to null', () => { + expect(nextState.error).to.be.null; + }); + }); + + describe('LOGOUT_SUCCESS', () => { + const nextState = reducer( + { loading: true, loaded: true, user: credentials }, + { type: LOGOUT_SUCCESS } + ); + + it('by setting \'loading\' to false', () => { + expect(nextState.loading).to.be.false; + }); + + it('by setting \'loaded\' to false', () => { + expect(nextState.loaded).to.be.false; + }); + + it('by setting \'user\' to null', () => { + expect(nextState.user).to.be.null; + }); + }); + + describe('LOGOUT_FAILURE', () => { + const nextState = reducer( + { loading: true, loaded: true, user: credentials }, + { type: LOGOUT_FAILURE, error: failureRes } + ); + + it('by setting \'loading\' to false', () => { + expect(nextState.loading).to.be.false; + }); + + it('by setting \'error\'', () => { + expect(nextState.error).to.exist; + }); + + it('by not setting \'loaded\' to false', () => { + expect(nextState.loaded).to.be.true; + }); + + it('by not setting \'user\' to null', () => { + expect(nextState.user).to.exist; + }); + }); +} diff --git a/generated/browser/resources/FullstackPics.js b/generated/browser/resources/FullstackPics.js new file mode 100644 index 0000000..66d5c6f --- /dev/null +++ b/generated/browser/resources/FullstackPics.js @@ -0,0 +1,32 @@ +'use strict'; + +const FullstackPics = [ + 'https://pbs.twimg.com/media/B7gBXulCAAAXQcE.jpg:large', + 'https://fbcdn-sphotos-c-a.akamaihd.net/hphotos-ak-xap1/t31.0-8/10862451_10205622990359241_8027168843312841137_o.jpg', + 'https://pbs.twimg.com/media/B-LKUshIgAEy9SK.jpg', + 'https://pbs.twimg.com/media/B79-X7oCMAAkw7y.jpg', + 'https://pbs.twimg.com/media/B-Uj9COIIAIFAh0.jpg:large', + 'https://pbs.twimg.com/media/B6yIyFiCEAAql12.jpg:large', + 'https://pbs.twimg.com/media/CE-T75lWAAAmqqJ.jpg:large', + 'https://pbs.twimg.com/media/CEvZAg-VAAAk932.jpg:large', + 'https://pbs.twimg.com/media/CEgNMeOXIAIfDhK.jpg:large', + 'https://pbs.twimg.com/media/CEQyIDNWgAAu60B.jpg:large', + 'https://pbs.twimg.com/media/CCF3T5QW8AE2lGJ.jpg:large', + 'https://pbs.twimg.com/media/CAeVw5SWoAAALsj.jpg:large', + 'https://pbs.twimg.com/media/CAaJIP7UkAAlIGs.jpg:large', + 'https://pbs.twimg.com/media/CAQOw9lWEAAY9Fl.jpg:large', + 'https://pbs.twimg.com/media/B-OQbVrCMAANwIM.jpg:large', + 'https://pbs.twimg.com/media/B9b_erwCYAAwRcJ.png:large', + 'https://pbs.twimg.com/media/B5PTdvnCcAEAl4x.jpg:large', + 'https://pbs.twimg.com/media/B4qwC0iCYAAlPGh.jpg:large', + 'https://pbs.twimg.com/media/B2b33vRIUAA9o1D.jpg:large', + 'https://pbs.twimg.com/media/BwpIwr1IUAAvO2_.jpg:large', + 'https://pbs.twimg.com/media/BsSseANCYAEOhLw.jpg:large', + 'https://pbs.twimg.com/media/CJ4vLfuUwAAda4L.jpg:large', + 'https://pbs.twimg.com/media/CI7wzjEVEAAOPpS.jpg:large', + 'https://pbs.twimg.com/media/CIdHvT2UsAAnnHV.jpg:large', + 'https://pbs.twimg.com/media/CGCiP_YWYAAo75V.jpg:large', + 'https://pbs.twimg.com/media/CIS4JPIWIAI37qu.jpg:large' +]; + +export default FullstackPics; diff --git a/generated/browser/resources/RandomGreetings.js b/generated/browser/resources/RandomGreetings.js new file mode 100644 index 0000000..4c66f88 --- /dev/null +++ b/generated/browser/resources/RandomGreetings.js @@ -0,0 +1,22 @@ +'use strict'; + +const greetings = [ + 'Hello, world!', + 'At long last, I live!', + 'Hello, simple human.', + 'What a beautiful day!', + 'I\'m like any other project, except that I am yours. :)', + 'This empty string is for Lindsay Levine.', + 'こんにちは、ユーザー様。', + 'Welcome. To. WEBSITE.', + ':D', + 'Yes, I think we\'ve met before.', + 'Gimme 3 mins... I just grabbed this really dope frittata', + 'If Cooper could offer only one piece of advice, it would be to nevSQUIRREL!', +]; + +const getRandomFromArray = (arr) => arr[Math.floor(Math.random() * arr.length)]; + +const getRandomGreeting = () => getRandomFromArray(greetings); + +export default getRandomGreeting; diff --git a/generated/browser/scss/directives/_main.scss b/generated/browser/scss/directives/_main.scss deleted file mode 100644 index bcc46a0..0000000 --- a/generated/browser/scss/directives/_main.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "fullstack-logo"; -@import "navbar"; \ No newline at end of file diff --git a/generated/browser/scss/directives/navbar.scss b/generated/browser/scss/directives/navbar.scss deleted file mode 100644 index 40e613b..0000000 --- a/generated/browser/scss/directives/navbar.scss +++ /dev/null @@ -1,62 +0,0 @@ -navbar { - - display: block; - background: white; - border-bottom: 1px solid black; - box-shadow: 1px 1px 14px -3px black; - - .container { - width: 91%; - max-width: 1340px; - padding-top: 20px; - } - - fullstack-logo { - margin-top: 5px; - margin-right: 20px; - width: 40px; - } - - a { - color: black; - font-size: 16px; - } - - .nav > li > a { - cursor: pointer; - transition: color 0.2s; - &.active { - color: darken(#f31214, 10%); - } - &:hover { - background: none; - color: #f31214; - } - &:focus { - background: none; - } - } - - .login-button { - float: right; - border: 1px solid black; - margin-top: 10px; - background: none; - outline: none !important; - } - - .welcome { - float: right; - margin-top: 12px; - span { - color: black; - font-size: 16px; - margin-right: 5px; - } - a { - color: #f30046; - cursor: pointer; - } - } - -} diff --git a/generated/browser/scss/home/main.scss b/generated/browser/scss/home/main.scss deleted file mode 100644 index f50b10f..0000000 --- a/generated/browser/scss/home/main.scss +++ /dev/null @@ -1,12 +0,0 @@ -#home { - fullstack-logo { - padding-top: 30px; - width: 150px; - margin: 0 auto; - } - h1 { - margin-top: 40px; - text-align: center; - } -} - diff --git a/generated/browser/scss/login/main.scss b/generated/browser/scss/login/main.scss deleted file mode 100644 index 66aa76c..0000000 --- a/generated/browser/scss/login/main.scss +++ /dev/null @@ -1,4 +0,0 @@ -#login-form { - width: 60%; - margin: 0 auto; -} \ No newline at end of file diff --git a/generated/browser/scss/main.scss b/generated/browser/scss/main.scss deleted file mode 100644 index 0ea90a3..0000000 --- a/generated/browser/scss/main.scss +++ /dev/null @@ -1,26 +0,0 @@ -* { - box-sizing: border-box; -} - -body { - background: #fdfdfd; -} - -@mixin clearfix { - &:after { - content: ""; - display: table; - clear: both; - } -} - -#main { - padding-top: 60px; - margin: 0 auto; -} - -@import 'directives/main'; -@import 'home/main'; -@import 'about/main'; -@import 'tutorial/main'; -@import 'login/main'; diff --git a/generated/browser/scss/tutorial/main.scss b/generated/browser/scss/tutorial/main.scss deleted file mode 100644 index 6c95225..0000000 --- a/generated/browser/scss/tutorial/main.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import 'tutorial-section'; -@import 'tutorial-video'; -@import 'tutorial-section-menu'; - -#tutorial { -} \ No newline at end of file diff --git a/generated/browser/scss/tutorial/tutorial-section-menu.scss b/generated/browser/scss/tutorial/tutorial-section-menu.scss deleted file mode 100644 index e9c88a0..0000000 --- a/generated/browser/scss/tutorial/tutorial-section-menu.scss +++ /dev/null @@ -1,21 +0,0 @@ -tutorial-section-menu { - - display: block; - @include clearfix; - - ul { - margin-left: 23px; - list-style-type: none; - font-weight: 300; - li { - float: left; - margin-right: 40px; - padding: 10px; - cursor: pointer; - &:hover { - background: lighten(gray, 45%); - } - } - } - -} \ No newline at end of file diff --git a/generated/browser/scss/tutorial/tutorial-section.scss b/generated/browser/scss/tutorial/tutorial-section.scss deleted file mode 100644 index bf98b0c..0000000 --- a/generated/browser/scss/tutorial/tutorial-section.scss +++ /dev/null @@ -1,14 +0,0 @@ -tutorial-section { - - display: block; - width: 90%; - margin: 10px auto; - padding: 20px 0 60px 0; - - & > h1 { - margin: 20px 0 40px 0; - text-align: center; - font-weight: bold; - } - -} \ No newline at end of file diff --git a/generated/browser/scss/tutorial/tutorial-video.scss b/generated/browser/scss/tutorial/tutorial-video.scss deleted file mode 100644 index d0f0f48..0000000 --- a/generated/browser/scss/tutorial/tutorial-video.scss +++ /dev/null @@ -1,18 +0,0 @@ -tutorial-video { - - display: block; - width: 600px; - margin: 0 auto; - - h1 { - font-weight: 300; - font-size: 24px; - margin-bottom: 15px; - } - - iframe { - width: 600px; - height: 400px; - } - -} \ No newline at end of file diff --git a/generated/browser/shared/AuthForm/AuthForm.js b/generated/browser/shared/AuthForm/AuthForm.js new file mode 100644 index 0000000..4f5b6c7 --- /dev/null +++ b/generated/browser/shared/AuthForm/AuthForm.js @@ -0,0 +1,81 @@ +'use strict'; + +import React, { Component, PropTypes } from 'react' +import { + FormGroup, + FormControl, + ControlLabel, + HelpBlock, + Button, + Row, + Col +} from 'react-bootstrap' + +export default class AuthForm extends Component { + + static propTypes = { + buttonLabel: PropTypes.string, + buttonStyle: PropTypes.string, + onSubmit: PropTypes.func + } + + static defaultProps = { + buttonStyle: 'default', + buttonLabel: 'Submit' + } + + constructor (props) { + super(props) + this.state = { + form: { + email: '', + password: '' + } + } + } + + handleSubmit = (e) => { + e.preventDefault() + this.props.onSubmit(this.state.form) + const newState = {...this.state} + newState.form = { email: '', password: '' } + this.setState(newState) + } + + handleChange = (e, field) => { + const newState = {...this.state} + newState.form[field] = e.target.value + this.setState(newState) + } + + render () { + const { form } = this.state + const { buttonLabel, buttonStyle } = this.props + + return ( + +
+ + Email Address + this.handleChange(e, 'email')} + /> + + + Password + this.handleChange(e, 'password')} + /> + + +
+ + ) + } +} diff --git a/generated/browser/shared/AuthForm/index.js b/generated/browser/shared/AuthForm/index.js new file mode 100644 index 0000000..78e48ec --- /dev/null +++ b/generated/browser/shared/AuthForm/index.js @@ -0,0 +1 @@ +export default from './AuthForm'; diff --git a/generated/browser/shared/Logo/Logo.js b/generated/browser/shared/Logo/Logo.js new file mode 100644 index 0000000..a264216 --- /dev/null +++ b/generated/browser/shared/Logo/Logo.js @@ -0,0 +1,14 @@ +'use strict'; + +import React, { PropTypes } from 'react'; +import './_Logo'; + +const Logo = (props) => { + return ( +
+ +
+ ) +} + +export default Logo diff --git a/generated/browser/scss/directives/fullstack-logo.scss b/generated/browser/shared/Logo/_Logo.scss similarity index 57% rename from generated/browser/scss/directives/fullstack-logo.scss rename to generated/browser/shared/Logo/_Logo.scss index 28bbec0..a34beaa 100644 --- a/generated/browser/scss/directives/fullstack-logo.scss +++ b/generated/browser/shared/Logo/_Logo.scss @@ -1,7 +1,8 @@ -fullstack-logo { - display: block; +.logo { + img { display: block; width: 100%; } -} \ No newline at end of file + +} diff --git a/generated/browser/shared/Logo/index.js b/generated/browser/shared/Logo/index.js new file mode 100644 index 0000000..0a77052 --- /dev/null +++ b/generated/browser/shared/Logo/index.js @@ -0,0 +1 @@ +export default from './Logo'; diff --git a/generated/browser/shared/NotFound/NotFound.js b/generated/browser/shared/NotFound/NotFound.js new file mode 100644 index 0000000..5c10092 --- /dev/null +++ b/generated/browser/shared/NotFound/NotFound.js @@ -0,0 +1,14 @@ +'use strict'; + +import React from 'react' + +const NotFound = (props) => { + return ( +
+

404

+

Page Not Found

+
+ ) +} + +export default NotFound diff --git a/generated/browser/shared/NotFound/index.js b/generated/browser/shared/NotFound/index.js new file mode 100644 index 0000000..50068c5 --- /dev/null +++ b/generated/browser/shared/NotFound/index.js @@ -0,0 +1 @@ +export default from './NotFound'; diff --git a/generated/browser/shared/index.js b/generated/browser/shared/index.js new file mode 100644 index 0000000..26e8d39 --- /dev/null +++ b/generated/browser/shared/index.js @@ -0,0 +1,3 @@ +export AuthForm from './AuthForm'; +export Logo from './Logo'; +export NotFound from './NotFound'; diff --git a/generated/browser/utils/index.js b/generated/browser/utils/index.js new file mode 100644 index 0000000..4e5eaa7 --- /dev/null +++ b/generated/browser/utils/index.js @@ -0,0 +1,8 @@ +'use strict'; + +export function parseJSON (response) { + return response.text() + .then(text => text ? JSON.parse(text) : {}) +} + +export const parseData = res => res.data; diff --git a/generated/gitignore.txt b/generated/gitignore.txt index fb0e6f3..f3ec14e 100644 --- a/generated/gitignore.txt +++ b/generated/gitignore.txt @@ -1,6 +1,7 @@ .idea node_modules npm-debug.log -public +build .DS_Store coverage +.nyc_output diff --git a/generated/gulpfile.js b/generated/gulpfile.js deleted file mode 100644 index 7463d84..0000000 --- a/generated/gulpfile.js +++ /dev/null @@ -1,188 +0,0 @@ -// All used modules. -var gulp = require('gulp'); -var babel = require('gulp-babel'); -var runSeq = require('run-sequence'); -var plumber = require('gulp-plumber'); -var concat = require('gulp-concat'); -var rename = require('gulp-rename'); -var sass = require('gulp-sass'); -var livereload = require('gulp-livereload'); -var minifyCSS = require('gulp-minify-css'); -var ngAnnotate = require('gulp-ng-annotate'); -var uglify = require('gulp-uglify'); -var sourcemaps = require('gulp-sourcemaps'); -var eslint = require('gulp-eslint'); -var karma = require('karma'); -var mocha = require('gulp-spawn-mocha'); -var istanbul = require('gulp-istanbul'); -var notify = require('gulp-notify'); - -// Development tasks -// -------------------------------------------------------------- - -// Live reload business. -gulp.task('reload', function () { - livereload.reload(); -}); - -gulp.task('reloadCSS', function () { - return gulp.src('./public/style.css').pipe(livereload()); -}); - -gulp.task('lintJS', function () { - - return gulp.src(['./browser/js/**/*.js', './server/**/*.js']) - .pipe(plumber({ - errorHandler: notify.onError('Linting FAILED! Check your gulp process.') - })) - .pipe(eslint()) - .pipe(eslint.format()) - .pipe(eslint.failOnError()); - -}); - -gulp.task('buildJS', ['lintJS'], function () { - return gulp.src(['./browser/js/app.js', './browser/js/**/*.js']) - .pipe(plumber()) - .pipe(sourcemaps.init()) - .pipe(concat('main.js')) - .pipe(babel({ - presets: ['es2015'] - })) - .pipe(sourcemaps.write()) - .pipe(gulp.dest('./public')); -}); - -gulp.task('testServerJS', function () { - require('babel-register'); - //testing environment variable - process.env.NODE_ENV = 'testing'; - return gulp.src('./tests/server/**/*.js', { - read: false - }).pipe(mocha({ reporter: 'spec' })); -}); - -gulp.task('testServerJSWithCoverage', function (done) { - //testing environment variable - process.env.NODE_ENV = 'testing'; - gulp.src('./server/**/*.js') - .pipe(istanbul({ - includeUntested: true - })) - .pipe(istanbul.hookRequire()) - .on('finish', function () { - gulp.src('./tests/server/**/*.js', {read: false}) - .pipe(mocha({reporter: 'spec'})) - .pipe(istanbul.writeReports({ - dir: './coverage/server/', - reporters: ['html', 'text'] - })) - .on('end', done); - }); -}); - -gulp.task('testBrowserJS', function (done) { - //testing environment variable - process.env.NODE_ENV = 'testing'; - var server = new karma.Server({ - configFile: __dirname + '/tests/browser/karma.conf.js', - singleRun: true - }, done); - server.start(); -}); - -gulp.task('buildCSS', function () { - - var sassCompilation = sass(); - sassCompilation.on('error', console.error.bind(console)); - - return gulp.src('./browser/scss/main.scss') - .pipe(plumber({ - errorHandler: notify.onError('SASS processing failed! Check your gulp process.') - })) - .pipe(sourcemaps.init()) - .pipe(sassCompilation) - .pipe(sourcemaps.write()) - .pipe(rename('style.css')) - .pipe(gulp.dest('./public')); -}); - -// Production tasks -// -------------------------------------------------------------- - -gulp.task('lintJSProduction', function () { - - return gulp.src(['./browser/js/**/*.js', './server/**/*.js']) - .pipe(plumber({ - errorHandler: notify.onError('Linting FAILED! Check your gulp process.') - })) - .pipe(eslint({ - rules: { - 'no-debugger': 2 // 1 in dev - } - })) - .pipe(eslint.format()) - .pipe(eslint.failOnError()); - -}); - -gulp.task('buildCSSProduction', function () { - return gulp.src('./browser/scss/main.scss') - .pipe(sass()) - .pipe(rename('style.css')) - .pipe(minifyCSS()) - .pipe(gulp.dest('./public')) -}); - -gulp.task('buildJSProduction', function () { - return gulp.src(['./browser/js/app.js', './browser/js/**/*.js']) - .pipe(concat('main.js')) - .pipe(babel({ - presets: ['es2015'] - })) - .pipe(ngAnnotate()) - .pipe(uglify()) - .pipe(gulp.dest('./public')); -}); - -gulp.task('buildProduction', ['buildCSSProduction', 'buildJSProduction']); - -// Composed tasks -// -------------------------------------------------------------- - -gulp.task('build', function () { - if (process.env.NODE_ENV === 'production') { - runSeq(['buildJSProduction', 'buildCSSProduction']); - } else { - runSeq(['buildJS', 'buildCSS']); - } -}); - -gulp.task('default', function () { - - gulp.start('build'); - - // Run when anything inside of browser/js changes. - gulp.watch('browser/js/**', function () { - runSeq('buildJS', 'reload'); - }); - - // Run when anything inside of browser/scss changes. - gulp.watch('browser/scss/**', function () { - runSeq('buildCSS', 'reloadCSS'); - }); - - gulp.watch('server/**/*.js', ['lintJS']); - - // Reload when a template (.html) file changes. - gulp.watch(['browser/**/*.html', 'server/app/views/*.html'], ['reload']); - - // Run server tests when a server file or server test file changes. - gulp.watch(['tests/server/**/*.js', 'server/app/**/*.js'], ['testServerJS']); - - // Run browser testing when a browser test file changes. - gulp.watch('tests/browser/**/*', ['testBrowserJS']); - - livereload.listen(); - -}); diff --git a/generated/package.json b/generated/package.json index 2aaccfa..a6ac9a2 100644 --- a/generated/package.json +++ b/generated/package.json @@ -5,18 +5,30 @@ "main": "server/start.js", "scripts": { "start": "nodemon --watch server -e js,html server/start.js", - "postinstall": "gulp build" + "postinstall": "npm run build", + "build": "webpack --config webpack.dev.config.js", + "build:production": "NODE_ENV='production' webpack -p --config webpack.prod.config.js", + "testBrowserJS": "NODE_ENV='testing' ./node_modules/.bin/mocha --compilers js:babel-register ./browser/**/*.test.js", + "testBrowserJS:watch": "NODE_ENV='testing' ./node_modules/.bin/mocha -w --compilers js:babel-register ./browser/**/*.test.js", + "testServerJS": "NODE_ENV='testing' mocha --compilers js:babel-register ./server/**/*.test.js", + "testServerJS:watch": "NODE_ENV='testing' ./node_modules/.bin/mocha -w --compilers js:babel-register ./server**/*.test.js", + "test": "NODE_ENV='testing' ./node_modules/.bin/mocha --compilers js:babel-register ./browser/**/*.test.js ./server/**/*.test.js", + "cover": "nyc --reporter=lcov --reporter=text --reporter=html --require babel-register npm test" }, "engines": { "node": ">=4.0.0" }, "dependencies": { - "angular": "^1.5.0-beta.0", - "angular-animate": "^1.4.7", - "angular-mocks": "^1.4.0", - "angular-ui-bootstrap": "^0.14.3", - "angular-ui-router": "^0.2.15", - "babel-preset-es2015": "^6.6.0", + "autoprefixer": "^6.5.0", + "axios": "^0.14.0", + "babel-eslint": "^7.0.0", + "babel-loader": "^6.2.5", + "babel-polyfill": "^6.13.0", + "babel-preset-es2015": "^6.14.0", + "babel-preset-react": "^6.11.1", + "babel-preset-react-hmre": "^1.1.1", + "babel-preset-stage-1": "^6.13.0", + "babel-preset-stage-2": "^6.13.0", "babel-register": "^6.6.0", "bluebird": "^3.3.3", "body-parser": "^1.12.0", @@ -25,35 +37,21 @@ "chalk": "^1.0.0", "connect-session-sequelize": "^3.0.0", "cookie-parser": "^1.3.4", + "css-loader": "^0.25.0", "eslint": "^3.4.0", "eslint-config-fullstack": "^1.5.0", + "eslint-loader": "^1.5.0", "express": "^4.12.0", "express-session": "^1.10.3", - "gulp": "^3.8.11", - "gulp-babel": "^6.1.2", - "gulp-concat": "^2.5.2", - "gulp-eslint": "^3.0.1", - "gulp-istanbul": "^0.9.0", - "gulp-livereload": "^3.7.0", - "gulp-minify-css": "^0.4.6", - "gulp-ng-annotate": "^0.5.2", - "gulp-notify": "^2.2.0", - "gulp-plumber": "^0.6.6", - "gulp-rename": "^1.2.0", - "gulp-sass": "^2.0.4", - "gulp-sourcemaps": "^1.3.0", - "gulp-spawn-mocha": "^3.1.0", - "gulp-uglify": "^1.1.0", + "extract-text-webpack-plugin": "^1.0.1", + "html-webpack-plugin": "^2.22.0", "istanbul": "^0.4.5", - "karma": "^0.13.14", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "0.2.3", - "karma-coverage": "^0.2.7", - "karma-mocha": "^0.1.10", - "karma-mocha-reporter": "^1.0.2", "lodash": "^3.9.3", "mocha": "^3.0.2", + "nock": "^8.0.0", + "node-sass": "^3.10.0", "nodemon": "^1.3.7", + "nyc": "^8.3.0", "passport": "^0.2.1", "passport-facebook": "^1.0.3", "passport-google-oauth": "^0.1.5", @@ -62,12 +60,31 @@ "pg": "^4.5.5", "pg-hstore": "^2.3.2", "pg-native": "^1.10.0", + "postcss-loader": "^0.13.0", + "precss": "^1.4.0", + "react": "^15.3.2", + "react-bootstrap": "^0.30.3", + "react-dom": "^15.3.2", + "react-redux": "^4.4.5", + "react-router": "^2.8.1", + "react-router-bootstrap": "^0.23.1", + "react-router-redux": "^4.0.6", + "redux": "^3.6.0", + "redux-logger": "^2.6.1", + "redux-mock-store": "^1.2.1", + "redux-thunk": "^2.1.0", "run-sequence": "^1.0.2", + "sass-loader": "^4.0.2", "sequelize": "^3.23.3", "serve-favicon": "^2.2.0", "sinon": "^1.13.0", "socket.io": "^1.3.4", "socket.io-client": "^1.3.5", - "supertest": "^0.15.0" + "style-loader": "^0.13.1", + "supertest": "^0.15.0", + "webpack": "^1.13.2", + "webpack-dev-middleware": "^1.8.3", + "webpack-dev-server": "^1.16.1", + "webpack-hot-middleware": "^2.12.2" } } diff --git a/generated/server/app/configure/app-variables.js b/generated/server/app/configure/app-variables.js index 3ddd92d..d729400 100644 --- a/generated/server/app/configure/app-variables.js +++ b/generated/server/app/configure/app-variables.js @@ -4,7 +4,8 @@ var chalk = require('chalk'); var util = require('util'); var rootPath = path.join(__dirname, '../../../'); -var indexPath = path.join(rootPath, './server/app/views/index.html'); +var indexTemplatePath = path.join(rootPath, './server/app/views/index.html'); +var indexPath = path.join(rootPath, './build/index.html'); var faviconPath = path.join(rootPath, './server/app/views/favicon.ico'); var env = require(path.join(rootPath, './server/env')); diff --git a/generated/server/app/configure/index.js b/generated/server/app/configure/index.js index e5111d0..f6af3f7 100644 --- a/generated/server/app/configure/index.js +++ b/generated/server/app/configure/index.js @@ -13,6 +13,7 @@ module.exports = function (app, db) { require('./app-variables')(app); require('./static-middleware')(app); require('./parsing-middleware')(app); + require('./webpack-middleware')(app); // Logging middleware, set as application // variable inside of server/app/configure/app-variables.js diff --git a/generated/server/app/configure/static-middleware.js b/generated/server/app/configure/static-middleware.js index f1a6b9b..1d80fbe 100644 --- a/generated/server/app/configure/static-middleware.js +++ b/generated/server/app/configure/static-middleware.js @@ -9,11 +9,13 @@ module.exports = function (app) { var npmPath = path.join(root, './node_modules'); var publicPath = path.join(root, './public'); + var buildPath = path.join(root, './build'); var browserPath = path.join(root, './browser'); app.use(favicon(app.getValue('faviconPath'))); app.use(express.static(npmPath)); app.use(express.static(publicPath)); + app.use(express.static(buildPath)); app.use(express.static(browserPath)); }; diff --git a/generated/server/app/configure/webpack-middleware.js b/generated/server/app/configure/webpack-middleware.js new file mode 100644 index 0000000..1d42320 --- /dev/null +++ b/generated/server/app/configure/webpack-middleware.js @@ -0,0 +1,20 @@ +'use strict'; + +const path = require('path'); +const NODE_ENV = process.env.NODE_ENV; + +module.exports = function (app) { + if (NODE_ENV !== 'production' && NODE_ENV !== 'testing') { + const webpack = require('webpack'); + const webpackHotMiddleware = require('webpack-hot-middleware'); + const webpackDevMiddleware = require('webpack-dev-middleware'); + const config = require(path.join(app.getValue('projectRoot'), 'webpack.dev.config.js')); + const compiler = webpack(config); + + app.use(webpackHotMiddleware(compiler)); + app.use(webpackDevMiddleware(compiler, { + noInfo: true, + publicPath: config.output.publicPath + })) + } +} diff --git a/generated/server/app/views/index.html b/generated/server/app/views/index.html index a5e18ae..6974e14 100644 --- a/generated/server/app/views/index.html +++ b/generated/server/app/views/index.html @@ -4,18 +4,8 @@ Fullstack Academy Generated Application - - - - - - - - - - - -
+ +
diff --git a/generated/tests/server/.eslintrc.json b/generated/server/tests/server/.eslintrc.json similarity index 100% rename from generated/tests/server/.eslintrc.json rename to generated/server/tests/server/.eslintrc.json diff --git a/generated/tests/server/models/user-test.js b/generated/server/tests/server/models/user.test.js similarity index 100% rename from generated/tests/server/models/user-test.js rename to generated/server/tests/server/models/user.test.js diff --git a/generated/tests/server/routes/members-only-test.js b/generated/server/tests/server/routes/members-only.test.js similarity index 100% rename from generated/tests/server/routes/members-only-test.js rename to generated/server/tests/server/routes/members-only.test.js diff --git a/generated/tests/browser/.eslintrc.json b/generated/tests/browser/.eslintrc.json deleted file mode 100644 index a3336db..0000000 --- a/generated/tests/browser/.eslintrc.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "env": { - "browser": true, - "mocha": true - }, - "globals": { - "app": true, - "_": true, - "module": true, - "inject": true, - "expect": true, - "sinon": true - } -} diff --git a/generated/tests/browser/fsa-prebuilt/auth-service-test.js b/generated/tests/browser/fsa-prebuilt/auth-service-test.js deleted file mode 100644 index 25ff2d5..0000000 --- a/generated/tests/browser/fsa-prebuilt/auth-service-test.js +++ /dev/null @@ -1,288 +0,0 @@ -describe('AuthService', function () { - - beforeEach(module('fsaPreBuilt')); - - var $httpBackend; - var $rootScope; - beforeEach('Get tools', inject(function (_$httpBackend_, _$rootScope_) { - $httpBackend = _$httpBackend_; - $rootScope = _$rootScope_; - })); - - var AUTH_EVENTS; - var AuthService; - var Session; - beforeEach('Get factories', inject(function (_AuthService_, _Session_, _AUTH_EVENTS_) { - AuthService = _AuthService_; - Session = _Session_; - AUTH_EVENTS = _AUTH_EVENTS_; - Session.destroy(); - })); - - it('should be an object', function () { - expect(AuthService).to.be.an('object'); - }); - - describe('isAuthenicated', function () { - - it('should return true if a Session exists', function () { - Session.create({email: 'cool@gmail.com'}); - expect(AuthService.isAuthenticated()).to.be.ok; - }); - - it('should return false if a Session does not exist', function () { - Session.destroy(); - expect(AuthService.isAuthenticated()).to.not.be.ok; - }); - - }); - - describe('getLoggedInUser', function () { - - it('should return the user from the Session if already authenticated', function (done) { - var x = {}; - Session.create(x); - AuthService.getLoggedInUser().then(function (user) { - expect(user).to.be.equal(x); - done(); - }); - $rootScope.$digest(); // In order to resolve $q promise. - }); - - describe('when user not already authenticated', function () { - - afterEach(function () { - $httpBackend.verifyNoOutstandingExpectation(); - $httpBackend.verifyNoOutstandingRequest(); - }); - - it('should make a request to GET /session', function (done) { - - $httpBackend.expectGET('/session'); - $httpBackend.whenGET('/session').respond({user: {}}); - - AuthService.getLoggedInUser().then(function () { - done(); - }); - - $httpBackend.flush(); - - }); - - describe('on successful response', function () { - - var potus = { - email: 'obama@gmail.com', president: true - }; - - beforeEach(function () { - $httpBackend.whenGET('/session').respond({user: potus}); - }); - - it('should resolve to the responded user from /session', function (done) { - - AuthService.getLoggedInUser().then(function (user) { - expect(user).to.be.deep.equal(potus); - done(); - }); - - $httpBackend.flush(); - - }); - - it('should set the session', function (done) { - - AuthService.getLoggedInUser().then(function (user) { - expect(Session.user).to.be.deep.equal(potus); - done(); - }); - - $httpBackend.flush(); - - }); - - it('should fire off AUTH_EVENTS.loginSuccess', function (done) { - - var spy = sinon.spy(); - - $rootScope.$on(AUTH_EVENTS.loginSuccess, spy); - - AuthService.getLoggedInUser().then(function () { - expect(spy.called).to.be.ok; - done(); - }); - - $httpBackend.flush(); - - }); - - }); - - it('should resolve to the reponse from /session if it is OK', function (done) { - - var potus = { - email: 'obama@gmail.com', president: true - }; - - $httpBackend.whenGET('/session').respond({user: potus}); - - AuthService.getLoggedInUser().then(function (user) { - expect(user).to.be.deep.equal(potus); - done(); - }); - - $httpBackend.flush(); - - }); - - it('should resolve to null if the response was 401/erroneous', function (done) { - - $httpBackend.whenGET('/session').respond(401); - - AuthService.getLoggedInUser().then(function (user) { - expect(user).to.be.equal(null); - done(); - }); - - $httpBackend.flush(); - - }); - - }); - - }); - - describe('login', function () { - - afterEach(function () { - $httpBackend.verifyNoOutstandingExpectation(); - $httpBackend.verifyNoOutstandingRequest(); - }); - - it('should make a request POST /login with the given data', function (done) { - - var potus = { - email: 'obama@gmail.com', - password: 'potus' - }; - - $httpBackend.expectPOST('/login', potus).respond(200, {}); - - AuthService.login(potus).then(done); - - $httpBackend.flush(); - - }); - - it('should forward invalid credentials error on unsuccessful response', function (done) { - - $httpBackend.expectPOST('/login').respond(401); - - AuthService.login({ email: 'fakedude@gmail.com', password: 'nop' }).catch(function (err) { - expect(err.message).to.be.equal('Invalid login credentials.'); - done(); - }); - - $httpBackend.flush(); - - }); - - describe('on successful response', function () { - - var user = {email: 'coolguy@beans.com'}; - var login = {email: 'coolguy@beans.com', password: 'sweetjuicy'}; - - beforeEach(function () { - $httpBackend.whenPOST('/login').respond({user: user}); - }); - - it('should resolve to the responded user', function (done) { - - AuthService.login(login).then(function (user) { - expect(user).to.be.deep.equal(user); - done(); - }); - - $httpBackend.flush(); - - }); - - it('should set Session', function (done) { - - Session.destroy(); - - AuthService.login(login).then(function (user) { - expect(Session.user).to.be.deep.equal(user); - done(); - }); - - $httpBackend.flush(); - - }); - - it('should fire off AUTH_EVENTS.loginSuccess', function (done) { - - var spy = sinon.spy(); - - $rootScope.$on(AUTH_EVENTS.loginSuccess, spy); - - AuthService.login(login).then(function () { - expect(spy.called).to.be.ok; - done(); - }); - - $httpBackend.flush(); - - }); - - }); - - }); - - describe('logout', function () { - - beforeEach(function () { - $httpBackend.expectGET('/logout').respond(200); - }); - - afterEach(function () { - $httpBackend.verifyNoOutstandingExpectation(); - $httpBackend.verifyNoOutstandingRequest(); - }); - - it('should make a request GET /logout', function (done) { - AuthService.logout().then(done); - $httpBackend.flush(); - }); - - it('should destroy the session', function (done) { - - Session.create({ email: 'obama@gmai.com' }); - - AuthService.logout().then(function () { - expect(Session.user).to.be.equal(null); - done(); - }); - - $httpBackend.flush(); - - }); - - it('should broadcast the logoutSuccess AUTH_EVENT', function (done) { - - var spy = sinon.spy(); - - $rootScope.$on(AUTH_EVENTS.logoutSuccess, spy); - - AuthService.logout().then(function () { - expect(spy.called).to.be.ok; - done(); - }); - - $httpBackend.flush(); - - }); - - }); - -}); diff --git a/generated/tests/browser/fsa-prebuilt/session-test.js b/generated/tests/browser/fsa-prebuilt/session-test.js deleted file mode 100644 index 020522d..0000000 --- a/generated/tests/browser/fsa-prebuilt/session-test.js +++ /dev/null @@ -1,76 +0,0 @@ -describe('Session Service', function () { - - beforeEach(module('fsaPreBuilt')); - - var $rootScope; - beforeEach(inject(function (_$rootScope_) { - $rootScope = _$rootScope_; - })); - - var Session; - var AUTH_EVENTS; - beforeEach(inject(function ($injector) { - Session = $injector.get('Session'); - AUTH_EVENTS = $injector.get('AUTH_EVENTS'); - })); - - it('should be an object', function () { - expect(Session).to.be.an('object'); - }); - - it('should by default have user as null', function () { - expect(Session.user).to.be.equal(null); - }); - - describe('create method', function () { - - it('should when called with a user argument' + - 'set the user to session', function () { - var user = {}; - Session.create(user); - expect(Session.user).to.be.equal(user); - }); - - }); - - describe('destroy method', function () { - - it('should set user to null', function () { - - Session.user = {}; - - Session.destroy(); - - expect(Session.user).to.be.equal(null); - - }); - - }); - - describe('event listening', function () { - - it('should call destroy when notAuthenticated event is fired', function () { - - var spy = sinon.spy(Session, 'destroy'); - - $rootScope.$broadcast(AUTH_EVENTS.notAuthenticated); - - expect(spy.called).to.be.ok; - spy.restore(); - - }); - - it('should call destroy when sessionTimeout event is fired', function () { - - var spy = sinon.spy(Session, 'destroy'); - - $rootScope.$broadcast(AUTH_EVENTS.sessionTimeout); - - expect(spy.called).to.be.ok; - spy.restore(); - - }); - - }); - -}); diff --git a/generated/tests/browser/karma.conf.js b/generated/tests/browser/karma.conf.js deleted file mode 100644 index 107785a..0000000 --- a/generated/tests/browser/karma.conf.js +++ /dev/null @@ -1,48 +0,0 @@ -/* eslint-env node */ -var path = require('path'); - -module.exports = function (config) { - - var filesCollection = [ - 'node_modules/lodash/index.js', - 'node_modules/angular/angular.js', - 'node_modules/angular-animate/angular-animate.js', - 'node_modules/angular-ui-router/release/angular-ui-router.js', - 'node_modules/angular-ui-bootstrap/ui-bootstrap.js', - 'node_modules/angular-ui-bootstrap/ui-bootstrap-tpls.js', - 'node_modules/socket.io-client/socket.io.js', - 'public/main.js', - 'node_modules/sinon/pkg/sinon.js', - 'node_modules/angular-mocks/angular-mocks.js', - 'tests/browser/**/*.js' - ]; - - var excludeFiles = [ - 'tests/browser/karma.conf.js' - ]; - - var configObj = { - browsers: ['Chrome'], - frameworks: ['mocha', 'chai'], - basePath: path.join(__dirname, '../../'), - files: filesCollection, - exclude: excludeFiles, - reporters: ['mocha', 'coverage'], - preprocessors: { - 'public/main.js': 'coverage' - }, - coverageReporter: { - dir: 'coverage/browser/', - reporters: [{ - type: 'text', - subdir: '.' - }, { - type: 'html', - subdir: '.' - }] - } - }; - - config.set(configObj); - -}; diff --git a/generated/webpack.dev.config.js b/generated/webpack.dev.config.js new file mode 100644 index 0000000..ea2699a --- /dev/null +++ b/generated/webpack.dev.config.js @@ -0,0 +1,63 @@ +'use strict' + +const path = require('path'); +const webpack = require('webpack'); +const autoprefixer = require('autoprefixer'); +const precss = require('precss'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const indexPath = path.join(__dirname, './server', 'app', 'views', 'index.html'); + +module.exports = { + devtool: 'eval', + entry: [ + 'babel-polyfill', + 'webpack-hot-middleware/client', + './browser/app.js' + ], + output: { + path: path.join(__dirname, 'build'), + filename: 'bundle.js', + publicPath: '/' + }, + resolve: { + extensions: ['', '.js', '.jsx', '.css', '.scss'], + modulesDirectories: ['browser', 'node_modules'] + }, + module: { + preloaders: [ + { + test: /\.jsx?$/, + loaders: ['eslint'] + } + ], + loaders: [ + { + test: /\.jsx?$/, + loader: 'babel', + exclude: /(node_modules)|(bower_components)/ + }, + { + test: /\.s?css$/, + loader: ExtractTextPlugin.extract('style', 'css?-autoprefixer!postcss!sass?sourceMap'), + include: /(browser)|(node_modules)/ + } + ] + }, + postcss: function () { + return [autoprefixer, precss] + }, + plugins: [ + new HtmlWebpackPlugin({ + inject: true, + template: indexPath + }), + new webpack.NoErrorsPlugin(), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('development') + }), + new webpack.HotModuleReplacementPlugin(), + new ExtractTextPlugin('style.css', { allChunks: true }) + ] +} + diff --git a/generated/webpack.prod.config.js b/generated/webpack.prod.config.js new file mode 100644 index 0000000..d9fdad7 --- /dev/null +++ b/generated/webpack.prod.config.js @@ -0,0 +1,85 @@ +'use strict'; + +const path = require('path'); +const webpack = require('webpack'); +const autoprefixer = require('autoprefixer'); +const precss = require('precss'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const indexPath = path.join(__dirname, './server', 'app', 'views', 'index.html'); + +module.exports = { + devtool: 'source-map', + entry: [ + 'babel-polyfill', + './browser/app.js' + ], + output: { + path: path.join(__dirname, 'build'), + filename: 'bundle.js', + publicPath: '/' + }, + resolve: { + extensions: ['', '.js', '.jsx', '.css', '.scss'], + modulesDirectories: ['browser', 'node_modules'] + }, + module: { + loaders: [ + { + test: /\.jsx?$/, + loader: 'babel', + exclude: /(node_modules)|(bower_components)/ + }, + { + test: /\.s?css$/, + loader: ExtractTextPlugin.extract('style', 'css?-autoprefixer!postcss!sass'), + include: /(browser)|(node_modules)/ + } + ] + }, + postcss: function () { + return [autoprefixer, precss] + }, + plugins: [ + new HtmlWebpackPlugin({ + inject: true, + template: indexPath, + minify: { + removeComments: true, + collapseWhitespace: true, + removeRedundantAttributes: true, + useShortDoctype: true, + removeEmptyAttributes: true, + removeStyleLinkTypeAttributes: true, + keepClosingSlash: true, + minifyJS: true, + minifyCSS: true, + minifyURLs: true + } + }), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('production') + }), + new webpack.optimize.OccurrenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin({ + compressor: { + screw_ie8: true, + warnings: false + }, + minimize: true, + compress: { + screw_ie8: true, + warnings: false + }, + mangle: { + screw_ie8: true + }, + output: { + comments: false, + screw_ie8: true + } + }), + new ExtractTextPlugin('style.css', { allChunks: true }) + ] +} +