├── .gitignore ├── .jshintignore ├── .jshintrc ├── .nvmrc ├── Gemfile ├── Gemfile.lock ├── README.md ├── actions └── cards_action.js ├── assets └── images │ ├── fpo.jpg │ ├── logo-big.png │ ├── logo-empty-state.png │ ├── logo-main.png │ └── select-menu-arrow.png ├── bin ├── development_deploy ├── production_deploy ├── setup └── test_deploy ├── components ├── __tests__ │ ├── account-test.js │ ├── alert-test.js │ ├── app-test.js │ ├── bye-test.js │ ├── card-test.js │ ├── card_form-test.js │ ├── cards-test.js │ ├── cards_container-test.js │ ├── dashboard-test.js │ ├── deregister-test.js │ ├── destroy_card-test.js │ ├── destroy_card_container-test.js │ ├── edit_card_container-test.js │ ├── login-test.js │ ├── logout-test.js │ ├── navigation-test.js │ ├── new_card_container-test.js │ ├── no_card-test.js │ ├── password_reset-test.js │ ├── register-test.js │ ├── show_card-test.js │ ├── show_card_container-test.js │ └── subscribe-test.js ├── account.jsx ├── alert.jsx ├── app.jsx ├── authenticated.jsx ├── bye.jsx ├── card.jsx ├── card_form.jsx ├── cards.jsx ├── cards_container.jsx ├── dashboard.jsx ├── deregister.jsx ├── destroy_card.jsx ├── destroy_card_container.jsx ├── edit_card_container.jsx ├── logged_in.jsx ├── login.jsx ├── logout.jsx ├── navigation.jsx ├── new_card_container.jsx ├── no_cards.jsx ├── password_reset.jsx ├── register.jsx ├── show_card.jsx ├── show_card_container.jsx └── subscribe.jsx ├── config ├── routes.js ├── routing_rules.xml └── security.json ├── constants └── cards_constants.js ├── dao ├── __tests__ │ └── mailchimp_dao-test.js └── mailchimp_dao.js ├── dist └── bundle │ ├── apple-touch-icon-152x152.png │ ├── favicon-32x32.png │ ├── index.html │ └── robot.txt ├── env.example.js ├── helpers ├── __tests__ │ └── text_inputs-test.js └── text_input.js ├── main.jsx ├── package.json ├── preprocessor.js ├── services ├── __tests__ │ ├── authentication_service-test.js │ ├── firebase_service-test.js │ └── mailchimp_service-test.js ├── authentication_service.js ├── firebase_service.js ├── firebase_service │ ├── __tests__ │ │ ├── cards-test.js │ │ └── users-test.js │ ├── cards.js │ ├── cards │ │ ├── __tests__ │ │ │ ├── create-test.js │ │ │ ├── destroy-test.js │ │ │ └── update-test.js │ │ ├── create.js │ │ ├── destroy.js │ │ └── update.js │ ├── users.js │ └── users │ │ ├── __tests__ │ │ ├── create-test.js │ │ ├── destroy-test.js │ │ ├── find.js │ │ └── reset_password.js │ │ ├── create.js │ │ ├── destroy.js │ │ ├── find.js │ │ └── reset_password.js └── mailchimp_service.js ├── stores ├── __tests__ │ └── cards_store-test.js └── cards_store.js ├── styles ├── add-card.scss ├── alert-messages.scss ├── buttons.scss ├── card-details.scss ├── cards.scss ├── footer.scss ├── forms.scss ├── general.scss ├── helpers.scss ├── hero.scss ├── layout.scss ├── main.scss ├── mixins.scss ├── navigation.scss ├── page-header.scss └── variables.scss ├── support ├── google_analytics_tracking.js └── stub_router_context.js ├── utilities ├── __tests__ │ ├── dispatcher-test.js │ ├── lockdown-test.js │ └── logger-test.js ├── dispatcher.js ├── lockdown.js ├── logger.js └── logger │ ├── __tests__ │ ├── notice-test.js │ └── warn-test.js │ ├── airbrake.js │ ├── notice.js │ ├── notice │ ├── __tests__ │ │ ├── cards-test.js │ │ └── users-test.js │ ├── cards.js │ └── users.js │ ├── runner.js │ ├── warn.js │ └── warn │ ├── __tests__ │ ├── cards-test.js │ └── users-test.js │ ├── cards.js │ └── users.js ├── webpack.config.js └── webpack.config.production.js /.gitignore: -------------------------------------------------------------------------------- 1 | env.js 2 | node_modules 3 | dist/bundle/bundle.js 4 | dist/bundle/*.png 5 | dist/design/bundle.js 6 | coverage 7 | *.log 8 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | support/google_analytics_tracking 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "jasmine" : true, 3 | "node" : true, 4 | 5 | "globals" : { 6 | "window" : true, 7 | "document" : true, 8 | "jest" : true, 9 | "$" : true, 10 | "_" : true, 11 | "__GOOGLE_ANALYTICS_KEY__": true, 12 | "__FIREBASE_URL__": true, 13 | "__AIRBRAKE_PRODUCT_ID__": true, 14 | "__AIRBRAKE_PRODUCT_KEY__": true, 15 | "__LOCAL_STORAGE_KEY__": true, 16 | "__LOCKDOWN_KEY__": true, 17 | "__MAILCHIMP_API_KEY__": true, 18 | "__MAILCHIMP_LIST_ID__": true, 19 | "__TEST__": false, 20 | "__DEV__": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 0.10.37 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "sass" 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | sass (3.4.13) 5 | 6 | PLATFORMS 7 | ruby 8 | 9 | DEPENDENCIES 10 | sass 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReactJS Example 2 | 3 | ## Prerequisites 4 | 5 | You will need the following things properly installed on your computer. 6 | 7 | * [Git](http://git-scm.com/) 8 | * [Node.js](http://nodejs.org/) (with NPM) 9 | 10 | ## Installation 11 | 12 | * `git clone ` this repository 13 | * change into the new directory 14 | * `bin/setup` 15 | 16 | ## Running / Development 17 | 18 | * `npm start` 19 | * Visit your app at [http://localhost:8080/webpack-dev-server/](http://localhost:8080/webpack-dev-server/). 20 | 21 | ### Running Tests 22 | 23 | * `npm test` 24 | 25 | ### Building 26 | 27 | * `npm run build:development` (development) 28 | * `npm run build:production` (production) 29 | -------------------------------------------------------------------------------- /actions/cards_action.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Dispatcher = require('../utilities/dispatcher'); 4 | var Constants = require('../constants/cards_constants'); 5 | 6 | module.exports = { 7 | find: function(id) { 8 | Dispatcher.dispatch({ 9 | actionType: Constants.CARDS_FIND, 10 | id: id 11 | }); 12 | }, 13 | list: function(userId) { 14 | Dispatcher.dispatch({ 15 | actionType: Constants.CARDS_LIST, 16 | userId: userId 17 | }); 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /assets/images/fpo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChuckJHardy/ReactJS-Example/9ca31313c95d916a7026588ea00798f439afd1e3/assets/images/fpo.jpg -------------------------------------------------------------------------------- /assets/images/logo-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChuckJHardy/ReactJS-Example/9ca31313c95d916a7026588ea00798f439afd1e3/assets/images/logo-big.png -------------------------------------------------------------------------------- /assets/images/logo-empty-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChuckJHardy/ReactJS-Example/9ca31313c95d916a7026588ea00798f439afd1e3/assets/images/logo-empty-state.png -------------------------------------------------------------------------------- /assets/images/logo-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChuckJHardy/ReactJS-Example/9ca31313c95d916a7026588ea00798f439afd1e3/assets/images/logo-main.png -------------------------------------------------------------------------------- /assets/images/select-menu-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChuckJHardy/ReactJS-Example/9ca31313c95d916a7026588ea00798f439afd1e3/assets/images/select-menu-arrow.png -------------------------------------------------------------------------------- /bin/development_deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | blue="\033[34m" 4 | reset="\033[0m" 5 | red="\033[31m" 6 | green="\033[32m" 7 | 8 | function warn { 9 | echo "$1" > /dev/stderr 10 | } 11 | 12 | function info { 13 | warn "$green$1$reset" 14 | } 15 | 16 | function notice { 17 | warn "$blue$1$reset" 18 | } 19 | 20 | set -e 21 | 22 | notice " 23 | ------------------------------------ 24 | * Development Deploy 25 | ------------------------------------" 26 | 27 | read -p "Are you sure? (yes/n)? " CONT 28 | if [ "$CONT" == "yes" ]; then 29 | info "Testing..." 30 | npm test 31 | info "Building..." 32 | npm run build:development 33 | info "Syncing..." 34 | s3cmd sync -P --verbose --delete-removed --recursive ./dist/bundle/* s3://dev.smartpickings.com 35 | else 36 | warn "Bye :)" 37 | fi 38 | -------------------------------------------------------------------------------- /bin/production_deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | blue="\033[34m" 4 | reset="\033[0m" 5 | red="\033[31m" 6 | green="\033[32m" 7 | 8 | function warn { 9 | echo "$1" > /dev/stderr 10 | } 11 | 12 | function info { 13 | warn "$green$1$reset" 14 | } 15 | 16 | function notice { 17 | warn "$blue$1$reset" 18 | } 19 | 20 | set -e 21 | 22 | notice " 23 | ------------------------------------ 24 | * Production Deploy 25 | ------------------------------------" 26 | 27 | read -p "Are you sure? (yes/n)? " CONT 28 | if [ "$CONT" == "yes" ]; then 29 | info "Testing..." 30 | npm test 31 | info "Building..." 32 | npm run build:production 33 | info "Syncing..." 34 | s3cmd sync -P --verbose --delete-removed --recursive ./dist/bundle/* s3://app.smartpickings.com 35 | else 36 | warn "Bye :)" 37 | fi 38 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | blue="\033[34m" 4 | reset="\033[0m" 5 | red="\033[31m" 6 | green="\033[32m" 7 | 8 | function warn { 9 | echo "$1" > /dev/stderr 10 | } 11 | 12 | function info { 13 | warn "$green$1$reset" 14 | } 15 | 16 | function notice { 17 | warn "$blue$1$reset" 18 | } 19 | 20 | notice " 21 | ------------------------------------ 22 | * Installation Instructions 23 | ------------------------------------ 24 | Ensure all required Environment Configurations are set... 25 | 26 | $ vi env.js 27 | module.exports = {}; 28 | 29 | This setup script will... 30 | 31 | 1. Check NodeJS and NPM are installed correctly 32 | 2. Install Dependencies 33 | 3. Build 34 | 4. Run Tests" 35 | 36 | which node &> /dev/null 37 | if [ $? -ne 0 ]; then 38 | warn "NodeJS Missing: http://nodejs.org" 39 | exit 69 40 | fi 41 | 42 | which npm &> /dev/null 43 | if [ $? -ne 0 ]; then 44 | warn "NPM Missing: https://www.npmjs.com" 45 | exit 69 46 | fi 47 | 48 | gem which bundler &> /dev/null 49 | if [ $? -ne 0 ]; then 50 | warn "Bundler Missing: Try 'gem install bundler'." 51 | exit 69 52 | fi 53 | 54 | which S3cmd &> /dev/null 55 | if [ $? -ne 0 ]; then 56 | warn "S3cmd Missing: Try 'brew install S3cmd'" 57 | exit 69 58 | fi 59 | 60 | set -e 61 | 62 | any_variable_missing=false 63 | 64 | info " 65 | ------------------------------------ 66 | * Install Dependencies 67 | ------------------------------------" 68 | 69 | bundle install 70 | npm install webpack webpack-dev-server -g 71 | npm install 72 | 73 | info " 74 | ------------------------------------ 75 | * Building Project 76 | ------------------------------------" 77 | 78 | npm run build:development 79 | npm run build:design 80 | npm test 81 | -------------------------------------------------------------------------------- /bin/test_deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | blue="\033[34m" 4 | reset="\033[0m" 5 | red="\033[31m" 6 | green="\033[32m" 7 | 8 | function warn { 9 | echo "$1" > /dev/stderr 10 | } 11 | 12 | function info { 13 | warn "$green$1$reset" 14 | } 15 | 16 | function notice { 17 | warn "$blue$1$reset" 18 | } 19 | 20 | set -e 21 | 22 | notice " 23 | ------------------------------------ 24 | * Test Deploy 25 | ------------------------------------" 26 | 27 | read -p "Are you sure? (yes/n)? " CONT 28 | if [ "$CONT" == "yes" ]; then 29 | info "Syncing..." 30 | s3cmd sync -P --verbose --delete-removed --recursive ./dist/bundle/* s3://test.smartpickings.com 31 | else 32 | warn "Bye :)" 33 | fi 34 | -------------------------------------------------------------------------------- /components/__tests__/account-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../account'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var StubRouterContent = require('../../support/stub_router_context'); 9 | 10 | describe('Account', function () { 11 | var Account = require('../account'); 12 | 13 | var subject = function(stub) { 14 | var Wrapper = StubRouterContent.wrapper(Account, {}, stub); 15 | return TestUtils.renderIntoDocument(); 16 | }; 17 | 18 | it('renders', function() { 19 | expect(subject().getDOMNode().textContent) 20 | .toContain('Delete Account'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /components/__tests__/alert-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../alert'); 4 | 5 | var React = require('react'); 6 | var TestUtils = require('react/lib/ReactTestUtils') 7 | 8 | describe('Alert', function () { 9 | var Alert = require('../alert'); 10 | 11 | var subject = function(message) { 12 | return TestUtils.renderIntoDocument( 13 | 14 | ); 15 | }; 16 | 17 | describe('With Message', function () { 18 | var message = 'Hello'; 19 | 20 | it('renders message', function() { 21 | expect(subject(message).getDOMNode().textContent) 22 | .toContain(message); 23 | }); 24 | 25 | it('renders with alert class', function() { 26 | var divs = TestUtils.scryRenderedDOMComponentsWithClass(subject(message), 'alert'); 27 | expect(divs.length).toEqual(1); 28 | }); 29 | 30 | it('renders with alert-danger class', function() { 31 | var divs = TestUtils.scryRenderedDOMComponentsWithClass(subject(message), 'alert-danger'); 32 | expect(divs.length).toEqual(1); 33 | }); 34 | }); 35 | 36 | describe('Without Message', function () { 37 | var message = null; 38 | 39 | it('renders with alert-null class', function() { 40 | var divs = TestUtils.scryRenderedDOMComponentsWithClass(subject(message), 'alert-null'); 41 | expect(divs.length).toEqual(1); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /components/__tests__/app-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../app'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var Router = require('react-router'); 8 | var TestUtils = require('react/lib/ReactTestUtils') 9 | var StubRouterContent = require('../../support/stub_router_context'); 10 | 11 | describe('App', function () { 12 | var App = require('../app'); 13 | 14 | var subject = function(stub, callback) { 15 | stub = stub || {}; 16 | 17 | var Wrapper = StubRouterContent.wrapper(App, {}, stub, callback); 18 | return TestUtils.renderIntoDocument(); 19 | }; 20 | 21 | describe('#getInitialState', function () { 22 | it('alerts danger is set', function() { 23 | subject({}, function(component) { 24 | expect(component.state.alerts.danger).toEqual(null); 25 | }); 26 | }); 27 | }); 28 | 29 | describe('.firebase', function () { 30 | it('returns firebase instance', function() { 31 | expect(App.firebase).toBeDefined(); 32 | }); 33 | }); 34 | 35 | describe('.warden', function () { 36 | it('returns warden instance', function() { 37 | expect(App.warden).toBeDefined(); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /components/__tests__/bye-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../bye'); 4 | 5 | var React = require('react'); 6 | var TestUtils = require('react/lib/ReactTestUtils') 7 | 8 | describe('Bye', function () { 9 | var Bye = require('../bye'); 10 | 11 | var subject = function() { 12 | return TestUtils.renderIntoDocument( 13 | 14 | ); 15 | }; 16 | 17 | it('renders', function() { 18 | expect(subject().getDOMNode().textContent) 19 | .toContain('Sad to see you leave.'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /components/__tests__/card-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../card'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var Assign = require('react/lib/Object.assign'); 9 | var StubRouterContent = require('../../support/stub_router_context'); 10 | 11 | describe('Card', function () { 12 | var Card = require('../card'); 13 | 14 | var subject = function(args) { 15 | args = args || {}; 16 | 17 | var defaults = { 18 | record: { 19 | id: '-JnrJpdjoBsiL-aRq16l', 20 | location: 'testLocation', 21 | description: 'testDescription', 22 | price: '£5' 23 | } 24 | }; 25 | 26 | data = Assign(defaults, args); 27 | 28 | var Wrapper = StubRouterContent.wrapper(Card, { 29 | id: data.record.id, 30 | record: data.record 31 | }, {}); 32 | 33 | return TestUtils.renderIntoDocument(); 34 | }; 35 | 36 | it('renders description', function() { 37 | expect(subject().getDOMNode().innerHTML) 38 | .toContain(data.record.description); 39 | }); 40 | 41 | it('renders location', function() { 42 | expect(subject().getDOMNode().innerHTML) 43 | .toContain(data.record.location); 44 | }); 45 | 46 | it('renders price', function() { 47 | expect(subject().getDOMNode().innerHTML) 48 | .toContain(data.record.price); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /components/__tests__/card_form-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../card_form'); 4 | jest.dontMock('get-form-data'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | 9 | describe('CardForm', function() { 10 | var CardForm = require('../card_form'); 11 | 12 | var asserts = {}; 13 | 14 | var address = 'testAddress'; 15 | var price = 'testPrice'; 16 | var description = 'testDescription'; 17 | var bedrooms = '3'; 18 | var bathrooms = '6'; 19 | 20 | var submitLabel = 'testSubmitLabel'; 21 | 22 | var handleSubmit = function(data) { 23 | asserts['handleSubmit'] = data; 24 | }; 25 | 26 | var subject = function() { 27 | var card = TestUtils.renderIntoDocument( 28 | 32 | ); 33 | 34 | card.refs.location.getDOMNode().value = address; 35 | card.refs.price.getDOMNode().value = price; 36 | card.refs.description.getDOMNode().value = description; 37 | card.refs.bedrooms.getDOMNode().value = bedrooms; 38 | card.refs.bathrooms.getDOMNode().value = bathrooms; 39 | 40 | return card; 41 | }; 42 | 43 | it('renders with expected submit label', function() { 44 | expect(subject().getDOMNode().innerHTML).toContain(submitLabel); 45 | }); 46 | 47 | it('calls handleSubmit with expected arguments', function() { 48 | var localSubject = subject(); 49 | 50 | TestUtils.Simulate.submit(localSubject.refs.form.getDOMNode()); 51 | 52 | expect(asserts.handleSubmit).toEqual({ 53 | bathrooms: '6', 54 | bedrooms: '3', 55 | description: 'testDescription', 56 | location: 'testAddress', 57 | price: 'testPrice' 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /components/__tests__/cards-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('lodash'); 4 | jest.dontMock('../cards'); 5 | jest.dontMock('../../support/stub_router_context'); 6 | 7 | var React = require('react'); 8 | var TestUtils = require('react/lib/ReactTestUtils') 9 | var Assign = require('react/lib/Object.assign'); 10 | var StubRouterContent = require('../../support/stub_router_context'); 11 | 12 | describe('Cards', function() { 13 | var Cards = require('../cards'); 14 | var Card = require('../card'); 15 | 16 | var props = { 17 | cards: { 18 | 1: {}, 19 | 2: {}, 20 | 3: {}, 21 | } 22 | }; 23 | 24 | var subject = function(stub) { 25 | var Wrapper = StubRouterContent.wrapper(Cards, props, stub); 26 | return TestUtils.renderIntoDocument(); 27 | }; 28 | 29 | it('renders cards', function() { 30 | var RC = TestUtils.scryRenderedComponentsWithType(subject(), Card); 31 | expect(RC.length).toEqual(3); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /components/__tests__/cards_container-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../cards_container'); 4 | 5 | var React = require('react'); 6 | var TestUtils = require('react/lib/ReactTestUtils') 7 | 8 | describe('Cards', function() { 9 | var App = require('../app'); 10 | var CardsContainer = require('../cards_container'); 11 | var Cards = require('../cards'); 12 | var NoCards = require('../no_cards'); 13 | var CardsAction = require('../../actions/cards_action'); 14 | var CardsStore = require('../../stores/cards_store'); 15 | 16 | var subject = function(cards) { 17 | CardsAction.list = jest.genMockFunction(); 18 | CardsStore.list = jest.genMockFunction().mockReturnValue(cards); 19 | App.warden = { 20 | getLocalStorageUser: function() { return '22'; } 21 | }; 22 | 23 | return TestUtils.renderIntoDocument( 24 | 25 | ); 26 | }; 27 | 28 | it('calls CardsAction on mount', function() { 29 | subject({}); 30 | expect(CardsAction.list).toBeCalledWith(App.warden.getLocalStorageUser()); 31 | }); 32 | 33 | it('addChangeListener is assigned on mount', function() { 34 | var localSubject = subject({}); 35 | expect(CardsStore.addChangeListener).toBeCalledWith(localSubject.onChange); 36 | }); 37 | 38 | it('removeChangeListener is assigned on unmount', function() { 39 | var localSubject = subject({}); 40 | localSubject.componentWillUnmount(); 41 | expect(CardsStore.removeChangeListener).toBeCalledWith(localSubject.onChange); 42 | }); 43 | 44 | it('sets cards state onChange', function() { 45 | var localSubject = subject({}); 46 | localSubject.onChange(); 47 | 48 | expect(CardsStore.list.mock.calls.length).toEqual(2); 49 | }); 50 | 51 | it('renders Cards component', function() { 52 | var RC = TestUtils.scryRenderedComponentsWithType(subject({ 53 | 1: {}, 54 | }), Cards); 55 | expect(RC.length).toEqual(1); 56 | }); 57 | 58 | it('renders NoCards component when there are not cards', function() { 59 | var RC = TestUtils.scryRenderedComponentsWithType(subject({}), NoCards); 60 | expect(RC.length).toEqual(1); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /components/__tests__/dashboard-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../dashboard'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var StubRouterContent = require('../../support/stub_router_context'); 9 | 10 | describe('Dashboard', function () { 11 | var Dashboard = require('../dashboard'); 12 | var CardsContainer = require('../cards_container'); 13 | 14 | var subject = function(stub) { 15 | var Wrapper = StubRouterContent.wrapper(Dashboard, {}, stub); 16 | return TestUtils.renderIntoDocument(); 17 | }; 18 | 19 | it('renders cards', function() { 20 | var RC = TestUtils.scryRenderedComponentsWithType(subject(), CardsContainer); 21 | expect(RC.length).toEqual(1); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /components/__tests__/deregister-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../deregister'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var StubRouterContent = require('../../support/stub_router_context'); 9 | 10 | var App = require('../../components/app'); 11 | var FirebaseService = require('../../services/firebase_service'); 12 | 13 | describe('Deregister', function() { 14 | var Deregister = require('../deregister'); 15 | 16 | var assets = {}; 17 | var setAlert = function(message) { assets['setAlert'] = message; }; 18 | 19 | var subject = function() { 20 | return TestUtils.renderIntoDocument( 21 | 22 | ); 23 | }; 24 | 25 | beforeEach(function() { 26 | FirebaseService.users.destroy = jest.genMockFunction(); 27 | }); 28 | 29 | describe('#handleSubmit', function() { 30 | var adapter = jest.genMockFn(); 31 | var email = 'test@example.com'; 32 | var password = 'password'; 33 | 34 | describe('When Cancelled', function() { 35 | beforeEach(function() { 36 | window.confirm = jest.genMockFunction().mockReturnValue(false); 37 | }); 38 | 39 | it('does not call firebase', function() { 40 | expect(FirebaseService.users.destroy).not.toBeCalled(); 41 | }); 42 | }); 43 | 44 | describe('When Confirmed', function() { 45 | beforeEach(function() { 46 | window.confirm = jest.genMockFunction().mockReturnValue(true); 47 | }); 48 | 49 | it('calls sendToFirebase with expected arguments', function() { 50 | App.firebase = jest.genMockFunction().mockReturnValue(adapter); 51 | 52 | var localSubject = subject(); 53 | 54 | localSubject.refs.email.getDOMNode().value = email; 55 | localSubject.refs.password.getDOMNode().value = password; 56 | 57 | localSubject.handleSubmit({preventDefault: jest.genMockFn()}) 58 | 59 | expect(FirebaseService.users.destroy).toBeCalledWith( 60 | adapter, 61 | email, 62 | password, 63 | function() {}, 64 | function() {}, 65 | function() {}, 66 | function() {} 67 | ); 68 | }); 69 | }); 70 | }); 71 | 72 | describe('#handlerInvalidUser', function() { 73 | it('calls setAlert', function() { 74 | subject().handlerInvalidUser(); 75 | expect(assets.setAlert).toEqual('Nope, its confirmed, you dont exist'); 76 | }); 77 | }); 78 | 79 | describe('#handlerInvalidPassword', function() { 80 | it('updates state', function() { 81 | var localSubject = subject(); 82 | expect(localSubject.state.invalidPassword).toEqual(false); 83 | localSubject.handlerInvalidPassword(); 84 | expect(localSubject.state.invalidPassword).toEqual(true); 85 | }); 86 | }); 87 | 88 | describe('#handlerError', function() { 89 | it('calls setAlert', function() { 90 | subject().handlerError(); 91 | expect(assets.setAlert).toEqual('Something failed. Developers have been informed.'); 92 | }); 93 | }); 94 | 95 | describe('#handlerSuccess', function() { 96 | var data = { uid: 123 }; 97 | 98 | beforeEach(function() { 99 | App.warden = { 100 | logout: function() { assets['warden'] = 'success'; } 101 | }; 102 | }); 103 | 104 | it('calls off to warden for login and transitions to login', function() { 105 | var localSubject = subject(); 106 | 107 | localSubject.context = { 108 | router: StubRouterContent.stubber({ 109 | replaceWith: function(route) { assets['handlerSuccess'] = route; } 110 | }) 111 | } 112 | 113 | localSubject.handlerSuccess(data); 114 | 115 | expect(assets.handlerSuccess).toEqual('/bye'); 116 | expect(assets.warden).toEqual('success'); 117 | }); 118 | }); 119 | 120 | describe('Errors', function() { 121 | describe('When Invalid Password', function() { 122 | it('includes expected copy', function() { 123 | var localSubject = subject(); 124 | 125 | expect(localSubject.getDOMNode().innerHTML) 126 | .not.toContain('form-field-error'); 127 | 128 | localSubject.setState({invalidPassword: true}); 129 | 130 | expect(localSubject.getDOMNode().innerHTML) 131 | .toContain('Incorrect Password'); 132 | 133 | expect(localSubject.getDOMNode().innerHTML) 134 | .toContain('form-field form-field-error'); 135 | }); 136 | }); 137 | }); 138 | 139 | it('renders', function() { 140 | expect(subject().getDOMNode().textContent) 141 | .toContain('Delete Account'); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /components/__tests__/destroy_card-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../destroy_card'); 4 | 5 | var React = require('react'); 6 | var TestUtils = require('react/lib/ReactTestUtils') 7 | 8 | describe('DestroyCard', function () { 9 | var DestroyCard = require('../destroy_card'); 10 | 11 | var asserts = {}; 12 | var confirmationText = 'testConfirm'; 13 | var destroy = function(text) { asserts['destroy'] = text; }; 14 | 15 | var subject = function() { 16 | return TestUtils.renderIntoDocument( 17 | 21 | ); 22 | }; 23 | 24 | it('renders instructional text', function() { 25 | expect(subject().getDOMNode().textContent) 26 | .toContain('Type `testConfirm`'); 27 | }); 28 | 29 | it('triggers destroy with expected arguments', function() { 30 | var localSubject = subject(); 31 | var input = React.findDOMNode(localSubject.refs.confirmation); 32 | var submit = React.findDOMNode(localSubject.refs.submit); 33 | 34 | input.value = confirmationText + ' '; 35 | TestUtils.Simulate.click(submit); 36 | 37 | expect(asserts.destroy).toEqual(confirmationText); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /components/__tests__/destroy_card_container-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../destroy_card_container'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var StubRouterContent = require('../../support/stub_router_context'); 9 | 10 | var App = require('../../components/app'); 11 | var FirebaseService = require('../../services/firebase_service'); 12 | 13 | describe('DestroyCardContainer', function () { 14 | var DestroyCardContainer = require('../destroy_card_container'); 15 | var DestroyCard = require('../destroy_card'); 16 | 17 | var asserts = {}; 18 | var adapter = jest.genMockFn(); 19 | var cardId = '-JnrJpdjoBsiL-aRq16l'; 20 | 21 | var subject = function() { 22 | var Wrapper = StubRouterContent.wrapper(DestroyCardContainer, { 23 | setAlert: function(text) { 24 | asserts['setAlert'] = text; 25 | } 26 | }, { 27 | getCurrentParams: function() { 28 | return { 29 | cardId: cardId, 30 | }; 31 | }, 32 | replaceWith: function(route) { 33 | asserts['replaceWith'] = route; 34 | } 35 | }); 36 | 37 | return TestUtils.renderIntoDocument(); 38 | }; 39 | 40 | beforeEach(function() { 41 | FirebaseService.cards.destroy = jest.genMockFunction(); 42 | App.firebase = jest.genMockFunction().mockReturnValue(adapter); 43 | }); 44 | 45 | it('renders cards', function() { 46 | var RC = TestUtils.scryRenderedComponentsWithType(subject(), DestroyCard); 47 | expect(RC.length).toEqual(1); 48 | }); 49 | 50 | describe('#getCardId', function() { 51 | it('returns card id from route', function() { 52 | expect(subject().refs.component.getCardId()).toEqual(cardId); 53 | }); 54 | }); 55 | 56 | describe('#handlerError', function() { 57 | it('redirects when text matches', function() { 58 | subject().refs.component.handlerError(); 59 | expect(asserts.setAlert).toEqual('Something failed. Developers have been informed.'); 60 | }); 61 | }); 62 | 63 | describe('#handlerSuccess', function() { 64 | it('redirects when text matches', function() { 65 | subject().refs.component.handlerSuccess(); 66 | expect(asserts.replaceWith).toEqual('dashboard'); 67 | }); 68 | }); 69 | 70 | describe('#destroy', function() { 71 | it('redirects when text matches', function() { 72 | var localSubject = subject(); 73 | 74 | localSubject.refs.component.destroy('confirm'); 75 | 76 | expect(FirebaseService.cards.destroy).toBeCalledWith( 77 | adapter, 78 | cardId, 79 | function() {}, 80 | function() {} 81 | ); 82 | }); 83 | 84 | it('sets alert when text matches', function() { 85 | subject().refs.component.destroy('somethingWrong'); 86 | expect(asserts.setAlert).toEqual('You typed somethingWrong. You should have typed confirm.'); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /components/__tests__/edit_card_container-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../edit_card_container'); 4 | jest.dontMock('../card_form'); 5 | jest.dontMock('../../support/stub_router_context'); 6 | 7 | var React = require('react'); 8 | var TestUtils = require('react/lib/ReactTestUtils') 9 | var StubRouterContent = require('../../support/stub_router_context'); 10 | 11 | var App = require('../../components/app'); 12 | var FirebaseService = require('../../services/firebase_service'); 13 | 14 | describe('EditCardContainer', function() { 15 | var EditCardContainer = require('../edit_card_container'); 16 | var CardForm = require('../card_form'); 17 | var CardsAction = require('../../actions/cards_action'); 18 | var CardsStore = require('../../stores/cards_store'); 19 | 20 | var asserts = {}; 21 | var adapter = jest.genMockFn(); 22 | var cardId = '-JnrJpdjoBsiL-aRq16l'; 23 | var card = { 24 | location: 'testLocation', 25 | price: 'testPrice', 26 | description: 'testDescription', 27 | bedrooms: '9', 28 | bathrooms: '10', 29 | }; 30 | 31 | var subject = function() { 32 | var Wrapper = StubRouterContent.wrapper(EditCardContainer, { 33 | setAlert: function(text) { 34 | asserts['setAlert'] = text; 35 | } 36 | }, { 37 | getCurrentParams: function() { 38 | return { 39 | cardId: cardId, 40 | }; 41 | }, 42 | replaceWith: function(route, params, query) { 43 | asserts['replaceWith'] = { 44 | route: route, 45 | params: params, 46 | query: query 47 | }; 48 | } 49 | }); 50 | 51 | return TestUtils.renderIntoDocument(); 52 | }; 53 | 54 | beforeEach(function() { 55 | FirebaseService.cards.update = jest.genMockFunction(); 56 | App.firebase = jest.genMockFunction().mockReturnValue(adapter); 57 | CardsStore.card = jest.genMockFunction().mockReturnValue(card); 58 | }); 59 | 60 | it('sets cards state onChange', function() { 61 | var localSubject = subject({}); 62 | localSubject.refs.component.onChange(); 63 | 64 | expect(CardsStore.card.mock.calls.length).toEqual(2); 65 | }); 66 | 67 | it('calls CardsAction on mount', function() { 68 | subject({}); 69 | expect(CardsAction.find).toBeCalledWith(cardId); 70 | }); 71 | 72 | it('addChangeListener is assigned on mount', function() { 73 | var localSubject = subject({}); 74 | expect(CardsStore.addChangeListener) 75 | .toBeCalledWith(localSubject.refs.component.onChange); 76 | }); 77 | 78 | it('removeChangeListener is assigned on unmount', function() { 79 | var localSubject = subject({}); 80 | localSubject.refs.component.componentWillUnmount(); 81 | expect(CardsStore.removeChangeListener) 82 | .toBeCalledWith(localSubject.refs.component.onChange); 83 | }); 84 | 85 | describe('#handlerError', function() { 86 | it('calls setAlert', function() { 87 | subject().refs.component.handlerError(); 88 | expect(asserts.setAlert).toEqual('Something failed. Developers have been informed.'); 89 | }); 90 | }); 91 | 92 | describe('#getCardId', function() { 93 | it('returns card id from route', function() { 94 | expect(subject().refs.component.getCardId()).toEqual(cardId); 95 | }); 96 | }); 97 | 98 | describe('#update', function() { 99 | it('redirects when text matches', function() { 100 | var localSubject = subject(); 101 | var data = { 'something' : 'else' }; 102 | 103 | localSubject.refs.component.update(data); 104 | 105 | expect(FirebaseService.cards.update).toBeCalledWith( 106 | adapter, 107 | cardId, 108 | data, 109 | function() {}, 110 | function() {} 111 | ); 112 | }); 113 | }); 114 | 115 | describe('#handlerError', function() { 116 | it('redirects when text matches', function() { 117 | subject().refs.component.handlerError(); 118 | expect(asserts.setAlert).toEqual('Something failed. Developers have been informed.'); 119 | }); 120 | }); 121 | 122 | describe('#handlerSuccess', function() { 123 | it('redirects when text matches', function() { 124 | subject().refs.component.handlerSuccess(); 125 | expect(asserts.replaceWith).toEqual({ 126 | route: 'show_card', 127 | params: { cardId: cardId }, 128 | query: {} 129 | }); 130 | }); 131 | }); 132 | 133 | it('renders cards', function() { 134 | var RC = TestUtils.scryRenderedComponentsWithType(subject(), CardForm); 135 | expect(RC.length).toEqual(1); 136 | }); 137 | 138 | it('populates fields', function() { 139 | localSubject = subject(); 140 | 141 | expect(localSubject.getDOMNode().innerHTML).toContain(card.location); 142 | expect(localSubject.getDOMNode().innerHTML).toContain(card.price); 143 | expect(localSubject.getDOMNode().innerHTML).toContain(card.description); 144 | expect(localSubject.getDOMNode().innerHTML).toContain(card.bedrooms); 145 | expect(localSubject.getDOMNode().innerHTML).toContain(card.bathrooms); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /components/__tests__/login-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../login'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var StubRouterContent = require('../../support/stub_router_context'); 9 | 10 | var App = require('../../components/app'); 11 | var FirebaseService = require('../../services/firebase_service'); 12 | 13 | describe('Login', function() { 14 | var Login = require('../login'); 15 | 16 | var assets = {}; 17 | var email = 'test@example.com'; 18 | var password = 'password'; 19 | var setAlert = function(message) { assets['setAlert'] = message; }; 20 | 21 | var subject = function() { 22 | return TestUtils.renderIntoDocument( 23 | 24 | ); 25 | }; 26 | 27 | beforeEach(function() { 28 | FirebaseService.users.create = jest.genMockFunction(); 29 | }); 30 | 31 | describe('#handlerError', function() { 32 | it('calls setAlert', function() { 33 | subject().handlerError(); 34 | expect(assets.setAlert).toEqual('Something failed. Developers have been informed.'); 35 | }); 36 | }); 37 | 38 | describe('#handlePasswordReset', function() { 39 | it('transitions to password reset', function() { 40 | var localSubject = subject(); 41 | 42 | localSubject.context = { 43 | router: StubRouterContent.stubber({ 44 | transitionTo: function(route, params, query) { 45 | assets['handlePasswordReset'] = { 46 | route: route, 47 | params: params, 48 | query: query 49 | }; 50 | } 51 | }) 52 | }; 53 | 54 | localSubject.refs.email.getDOMNode().value = email; 55 | 56 | localSubject.handlePasswordReset(); 57 | expect(assets.handlePasswordReset).toEqual({ 58 | route: 'password_reset', 59 | params: {}, 60 | query: { email: email } 61 | }) 62 | }); 63 | }); 64 | 65 | describe('#handleSubmit', function() { 66 | var adapter = jest.genMockFn(); 67 | 68 | it('calls sendToFirebase with expected arguments', function() { 69 | App.firebase = jest.genMockFunction().mockReturnValue(adapter); 70 | 71 | var localSubject = subject(); 72 | 73 | localSubject.refs.email.getDOMNode().value = email; 74 | localSubject.refs.password.getDOMNode().value = password; 75 | 76 | localSubject.handleSubmit({preventDefault: jest.genMockFn()}) 77 | 78 | expect(FirebaseService.users.find).toBeCalledWith( 79 | adapter, 80 | email, 81 | password, 82 | function() {}, 83 | function() {} 84 | ); 85 | }); 86 | }); 87 | 88 | describe('#handlerSuccess', function() { 89 | var data = { uid: 123 }; 90 | 91 | beforeEach(function() { 92 | App.warden = { 93 | login: function(uid) { assets['warden'] = uid; } 94 | }; 95 | }); 96 | 97 | it('calls off to warden for login and transitions to login', function() { 98 | var localSubject = subject(); 99 | 100 | localSubject.context = { 101 | router: StubRouterContent.stubber({ 102 | replaceWith: function(route) { assets['handlerSuccess'] = route; } 103 | }) 104 | } 105 | 106 | localSubject.handlerSuccess(data); 107 | 108 | expect(assets.handlerSuccess).toEqual('dashboard') 109 | expect(assets.warden).toEqual(data.uid) 110 | }); 111 | }); 112 | 113 | describe('#populateFormValuesFromQuery', function() { 114 | describe('When Email Passed', function() { 115 | it('sets the email input to the email address', function() { 116 | var localSubject = subject(); 117 | 118 | localSubject.context = { 119 | router: StubRouterContent.stubber({ 120 | getCurrentQuery: function() { return { email: email } } 121 | }) 122 | } 123 | 124 | localSubject.populateFormValuesFromQuery(); 125 | expect(localSubject.refs.email.getDOMNode().value).toEqual(email) 126 | }); 127 | }); 128 | 129 | describe('When No Email was Passed', function() { 130 | it('does not change the email input value', function() { 131 | var localSubject = subject(); 132 | var currentEmailInputValue = localSubject.refs.email.getDOMNode().value; 133 | localSubject.populateFormValuesFromQuery(); 134 | expect(localSubject.refs.email.getDOMNode().value).toEqual(currentEmailInputValue) 135 | }); 136 | }); 137 | }); 138 | 139 | 140 | it('renders', function() { 141 | expect(subject().getDOMNode().textContent) 142 | .toContain('Email'); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /components/__tests__/logout-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../logout'); 4 | 5 | var React = require('react'); 6 | var TestUtils = require('react/lib/ReactTestUtils') 7 | 8 | var App = require('../app'); 9 | 10 | describe('Logout', function () { 11 | var Logout = require('../logout'); 12 | 13 | var mockWarden = { 14 | logout: jest.genMockFn() 15 | }; 16 | 17 | var subject = function() { 18 | return TestUtils.renderIntoDocument( 19 | 20 | ); 21 | }; 22 | 23 | beforeEach(function() { 24 | App.warden = mockWarden; 25 | }); 26 | 27 | it('calls off to App Warden for logout', function() { 28 | subject(); 29 | expect(mockWarden.logout).toBeCalled(); 30 | }); 31 | 32 | it('renders', function() { 33 | expect(subject().getDOMNode().textContent) 34 | .toContain('Bye'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /components/__tests__/navigation-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../navigation'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var StubRouterContent = require('../../support/stub_router_context'); 9 | 10 | describe('Navigation', function () { 11 | var Navigation = require('../navigation'); 12 | 13 | function test(path, link) { 14 | it('renders expected link', function() { 15 | expect(subject({ 16 | getCurrentPathname: function() { return path; } 17 | }).getDOMNode().textContent).toContain(link); 18 | }); 19 | } 20 | 21 | var subject = function(stub) { 22 | var Wrapper = StubRouterContent.wrapper(Navigation, {}, stub); 23 | return TestUtils.renderIntoDocument(); 24 | }; 25 | 26 | test('/bye', 'Register'); 27 | test('/login', 'Register'); 28 | test('/register', 'Login'); 29 | test('/logout', 'Login'); 30 | test('/password_reset', 'Login'); 31 | test('/', 'Account'); 32 | test('/', 'Logout'); 33 | test('/dashboard', 'Account'); 34 | test('/dashboard', 'Logout'); 35 | test('/new_card', 'Account'); 36 | test('/new_card', 'Logout'); 37 | test('/not_a_valid_path', 'Logout'); 38 | }); 39 | -------------------------------------------------------------------------------- /components/__tests__/new_card_container-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../new_card_container'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var StubRouterContent = require('../../support/stub_router_context'); 9 | 10 | var App = require('../../components/app'); 11 | var FirebaseService = require('../../services/firebase_service'); 12 | 13 | describe('NewCardContainer', function() { 14 | var NewCardContainer = require('../new_card_container'); 15 | var CardForm = require('../card_form'); 16 | 17 | var asserts = {}; 18 | var adapter = jest.genMockFn(); 19 | 20 | var subject = function() { 21 | var Wrapper = StubRouterContent.wrapper(NewCardContainer, { 22 | setAlert: function(text) { 23 | asserts['setAlert'] = text; 24 | } 25 | }, { 26 | getCurrentParams: function() { 27 | return { 28 | cardId: cardId, 29 | }; 30 | }, 31 | transitionTo: function(route) { 32 | asserts['transitionTo'] = route; 33 | } 34 | }); 35 | 36 | return TestUtils.renderIntoDocument(); 37 | }; 38 | 39 | beforeEach(function() { 40 | FirebaseService.cards.update = jest.genMockFunction(); 41 | App.firebase = jest.genMockFunction().mockReturnValue(adapter); 42 | App.warden = { 43 | getLocalStorageUser: function() { return '22'; } 44 | }; 45 | }); 46 | 47 | it('renders cards', function() { 48 | var RC = TestUtils.scryRenderedComponentsWithType(subject(), CardForm); 49 | expect(RC.length).toEqual(1); 50 | }); 51 | 52 | describe('#handlerError', function() { 53 | it('calls setAlert', function() { 54 | subject().refs.component.handlerError(); 55 | expect(asserts.setAlert).toEqual('Something failed. Developers have been informed.'); 56 | }); 57 | }); 58 | 59 | describe('#create', function() { 60 | it('redirects when text matches', function() { 61 | var localSubject = subject(); 62 | var data = { 'something' : 'else' }; 63 | 64 | localSubject.refs.component.create(data); 65 | 66 | expect(FirebaseService.cards.create).toBeCalledWith( 67 | adapter, 68 | App.warden.getLocalStorageUser(), 69 | data, 70 | function() {}, 71 | function() {} 72 | ); 73 | }); 74 | }); 75 | 76 | describe('#handlerError', function() { 77 | it('redirects when text matches', function() { 78 | subject().refs.component.handlerError(); 79 | expect(asserts.setAlert).toEqual('Something failed. Developers have been informed.'); 80 | }); 81 | }); 82 | 83 | describe('#handlerSuccess', function() { 84 | it('redirects when text matches', function() { 85 | subject().refs.component.handlerSuccess(); 86 | expect(asserts.transitionTo).toEqual('/'); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /components/__tests__/no_card-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../no_cards'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var StubRouterContent = require('../../support/stub_router_context'); 9 | 10 | describe('NoCards', function () { 11 | var NoCards = require('../no_cards'); 12 | 13 | var subject = function(stub) { 14 | var Wrapper = StubRouterContent.wrapper(NoCards, {}, stub); 15 | return TestUtils.renderIntoDocument(); 16 | }; 17 | 18 | it('renders', function() { 19 | expect(subject().getDOMNode().innerHTML) 20 | .toContain('Add a property'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /components/__tests__/password_reset-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../password_reset'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var StubRouterContent = require('../../support/stub_router_context'); 9 | 10 | var App = require('../../components/app'); 11 | var FirebaseService = require('../../services/firebase_service'); 12 | 13 | describe('PasswordReset', function() { 14 | var PasswordReset = require('../password_reset'); 15 | 16 | var assets = {}; 17 | var email = 'test@example.com'; 18 | var setAlert = function(message) { assets['setAlert'] = message; }; 19 | 20 | var subject = function() { 21 | return TestUtils.renderIntoDocument( 22 | 23 | ); 24 | }; 25 | 26 | beforeEach(function() { 27 | FirebaseService.users.resetPassword = jest.genMockFunction(); 28 | }); 29 | 30 | describe('Errors', function() { 31 | describe('When Invalid User', function() { 32 | it('includes expected copy', function() { 33 | var localSubject = subject(); 34 | 35 | expect(localSubject.getDOMNode().innerHTML) 36 | .not.toContain('form-field-error'); 37 | 38 | localSubject.setState({invalidUser: true}); 39 | 40 | expect(localSubject.getDOMNode().innerHTML) 41 | .toContain('Invalid User'); 42 | 43 | expect(localSubject.getDOMNode().innerHTML) 44 | .toContain('form-field form-field-error'); 45 | }); 46 | }); 47 | }); 48 | 49 | describe('#handlerError', function() { 50 | it('calls setAlert', function() { 51 | subject().handlerError(); 52 | expect(assets.setAlert).toEqual('Something failed. Developers have been informed.'); 53 | }); 54 | }); 55 | 56 | describe('#handlerInvalidUser', function() { 57 | it('updates state', function() { 58 | var localSubject = subject(); 59 | expect(localSubject.state.invalidUser).toEqual(false); 60 | localSubject.handlerInvalidUser(); 61 | expect(localSubject.state.invalidUser).toEqual(true); 62 | }); 63 | }); 64 | 65 | describe('#populateFormValuesFromQuery', function() { 66 | describe('When Email Passed', function() { 67 | it('sets the email input to the email address', function() { 68 | var localSubject = subject(); 69 | 70 | localSubject.context = { 71 | router: StubRouterContent.stubber({ 72 | getCurrentQuery: function() { return { email: email } } 73 | }) 74 | } 75 | 76 | localSubject.populateFormValuesFromQuery(); 77 | expect(localSubject.refs.email.getDOMNode().value).toEqual(email) 78 | }); 79 | }); 80 | 81 | describe('When No Email was Passed', function() { 82 | it('does not change the email input value', function() { 83 | var localSubject = subject(); 84 | var currentEmailInputValue = localSubject.refs.email.getDOMNode().value; 85 | localSubject.populateFormValuesFromQuery(); 86 | expect(localSubject.refs.email.getDOMNode().value).toEqual(currentEmailInputValue) 87 | }); 88 | }); 89 | }); 90 | 91 | describe('#handleSubmit', function() { 92 | var adapter = jest.genMockFn(); 93 | 94 | it('calls sendToFirebase with expected arguments', function() { 95 | App.firebase = jest.genMockFunction().mockReturnValue(adapter); 96 | 97 | var localSubject = subject(); 98 | 99 | localSubject.refs.email.getDOMNode().value = email; 100 | 101 | localSubject.handleSubmit({preventDefault: jest.genMockFn()}) 102 | 103 | expect(FirebaseService.users.resetPassword).toBeCalledWith( 104 | adapter, 105 | email, 106 | function() {}, 107 | function() {}, 108 | function() {} 109 | ); 110 | }); 111 | }); 112 | 113 | describe('#handlerSuccess', function() { 114 | it('transitions to login', function() { 115 | var localSubject = subject(); 116 | 117 | localSubject.context = { 118 | router: StubRouterContent.stubber({ 119 | transitionTo: function(route, params, query) { 120 | assets['handlerSuccess'] = { 121 | route: route, 122 | params: params, 123 | query: query 124 | }; 125 | } 126 | }) 127 | }; 128 | 129 | localSubject.refs.email.getDOMNode().value = email; 130 | 131 | localSubject.handlerSuccess(); 132 | expect(assets.handlerSuccess).toEqual({ 133 | route: 'login', 134 | params: {}, 135 | query: { email: email } 136 | }) 137 | }); 138 | }); 139 | 140 | it('renders', function() { 141 | expect(subject().getDOMNode().textContent) 142 | .toContain('Reset'); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /components/__tests__/register-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../register'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | jest.dontMock('../../helpers/text_input'); 6 | 7 | var React = require('react'); 8 | var TestUtils = require('react/lib/ReactTestUtils') 9 | var StubRouterContent = require('../../support/stub_router_context'); 10 | 11 | var App = require('../../components/app'); 12 | var FirebaseService = require('../../services/firebase_service'); 13 | 14 | describe('Register', function() { 15 | var Register = require('../register'); 16 | 17 | var assets = {}; 18 | var setAlert = function(message) { assets['setAlert'] = message; }; 19 | 20 | var subject = function() { 21 | return TestUtils.renderIntoDocument( 22 | 23 | ); 24 | }; 25 | 26 | beforeEach(function() { 27 | FirebaseService.users.create = jest.genMockFunction(); 28 | }); 29 | 30 | describe('#handleSubmit', function() { 31 | var adapter = jest.genMockFn(); 32 | var email = 'test@example.com'; 33 | var password = 'password'; 34 | 35 | it('calls sendToFirebase with expected arguments', function() { 36 | App.firebase = jest.genMockFunction().mockReturnValue(adapter); 37 | 38 | var localSubject = subject(); 39 | 40 | localSubject.refs.email.getDOMNode().value = email; 41 | localSubject.refs.password.getDOMNode().value = password; 42 | 43 | localSubject.handleSubmit({preventDefault: jest.genMockFn()}) 44 | 45 | expect(FirebaseService.users.create).toBeCalledWith( 46 | adapter, 47 | email, 48 | password, 49 | function() {}, 50 | function() {}, 51 | function() {}, 52 | function() {}, 53 | function() {} 54 | ); 55 | }); 56 | }); 57 | 58 | describe('#handlerEmailTaken', function() { 59 | it('updates state', function() { 60 | var localSubject = subject(); 61 | expect(localSubject.state.emailTaken).toEqual(false); 62 | localSubject.handlerEmailTaken(); 63 | expect(localSubject.state.emailTaken).toEqual(true); 64 | }); 65 | }); 66 | 67 | describe('#handlerInvalidEmail', function() { 68 | it('updates state', function() { 69 | var localSubject = subject(); 70 | expect(localSubject.state.invalidEmail).toEqual(false); 71 | localSubject.handlerInvalidEmail(); 72 | expect(localSubject.state.invalidEmail).toEqual(true); 73 | }); 74 | }); 75 | 76 | describe('#handlerError', function() { 77 | it('calls setAlert', function() { 78 | subject().handlerError(); 79 | expect(assets.setAlert).toEqual('Something failed. Developers have been informed.'); 80 | }); 81 | }); 82 | 83 | describe('#handlerLockdown', function() { 84 | it('calls setAlert', function() { 85 | var message = 'Access Denied'; 86 | subject().handlerLockdown(message); 87 | expect(assets.setAlert).toEqual(message); 88 | }); 89 | }); 90 | 91 | describe('#handlerSuccess', function() { 92 | var data = { uid: 123 }; 93 | 94 | beforeEach(function() { 95 | App.warden = { 96 | login: function(uid) { assets['warden'] = uid; } 97 | }; 98 | }); 99 | 100 | it('calls off to warden for login and transitions to login', function() { 101 | var localSubject = subject(); 102 | 103 | localSubject.context = { 104 | router: StubRouterContent.stubber({ 105 | replaceWith: function(route) { assets['handlerSuccess'] = route; } 106 | }) 107 | } 108 | 109 | localSubject.handlerSuccess(data); 110 | 111 | expect(assets.handlerSuccess).toEqual('dashboard'); 112 | expect(assets.warden).toEqual(data.uid); 113 | }); 114 | }); 115 | 116 | describe('Errors', function() { 117 | describe('When Email Taken', function() { 118 | it('includes expected copy', function() { 119 | var localSubject = subject(); 120 | 121 | expect(localSubject.getDOMNode().innerHTML) 122 | .not.toContain('form-field-error'); 123 | 124 | localSubject.setState({emailTaken: true}); 125 | 126 | expect(localSubject.getDOMNode().innerHTML) 127 | .toContain('Email Taken'); 128 | 129 | expect(localSubject.getDOMNode().innerHTML) 130 | .toContain('form-field form-field-error'); 131 | }); 132 | }); 133 | 134 | describe('When Invalid Email', function() { 135 | it('includes expected copy', function() { 136 | var localSubject = subject(); 137 | 138 | expect(localSubject.getDOMNode().innerHTML) 139 | .not.toContain('form-field-error'); 140 | 141 | localSubject.setState({invalidEmail: true}); 142 | 143 | expect(localSubject.getDOMNode().innerHTML) 144 | .toContain('Invalid Email Address'); 145 | 146 | expect(localSubject.getDOMNode().innerHTML) 147 | .toContain('form-field form-field-error'); 148 | }); 149 | }); 150 | }); 151 | 152 | it('renders', function() { 153 | expect(subject().getDOMNode().textContent) 154 | .toContain('Email'); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /components/__tests__/show_card-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../show_card'); 4 | 5 | var React = require('react'); 6 | var TestUtils = require('react/lib/ReactTestUtils') 7 | 8 | describe('ShowCard', function () { 9 | var ShowCard = require('../show_card'); 10 | 11 | var card = { 12 | price: 'testPrice', 13 | location: 'testLocation', 14 | bedrooms: 'testBedrooms', 15 | bathrooms: 'testBathrooms' 16 | }; 17 | 18 | var subject = function() { 19 | return TestUtils.renderIntoDocument( 20 | 25 | ); 26 | }; 27 | 28 | it('renders price', function() { 29 | expect(subject().getDOMNode().innerHTML).toContain(card.price); 30 | }); 31 | 32 | it('renders location', function() { 33 | expect(subject().getDOMNode().innerHTML).toContain(card.location); 34 | }); 35 | 36 | it('renders bedrooms', function() { 37 | expect(subject().getDOMNode().innerHTML).toContain(card.bedrooms); 38 | }); 39 | 40 | it('renders bathrooms', function() { 41 | expect(subject().getDOMNode().innerHTML).toContain(card.bathrooms); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /components/__tests__/show_card_container-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../show_card_container'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var StubRouterContent = require('../../support/stub_router_context'); 9 | 10 | describe('ShowCard', function() { 11 | var ShowCardContainer= require('../show_card_container'); 12 | var ShowCard = require('../show_card'); 13 | var CardsAction = require('../../actions/cards_action'); 14 | var CardsStore = require('../../stores/cards_store'); 15 | 16 | var cardId = '-JnrJpdjoBsiL-aRq16l'; 17 | var asserts = {}; 18 | 19 | var subject = function(card) { 20 | CardsAction.find = jest.genMockFunction(); 21 | CardsStore.card = jest.genMockFunction().mockReturnValue(card); 22 | 23 | var Wrapper = StubRouterContent.wrapper(ShowCardContainer, {}, { 24 | getCurrentParams: function() { 25 | return { 26 | cardId: cardId, 27 | }; 28 | }, 29 | transitionTo: function(name, args) { 30 | asserts['destroyName'] = name; 31 | asserts['destroyArgs'] = args; 32 | } 33 | }); 34 | 35 | return TestUtils.renderIntoDocument(); 36 | }; 37 | 38 | it('sets cards state onChange', function() { 39 | var localSubject = subject({}); 40 | localSubject.refs.component.onChange(); 41 | 42 | expect(CardsStore.card.mock.calls.length).toEqual(2); 43 | }); 44 | 45 | it('calls CardsAction on mount', function() { 46 | subject({}); 47 | expect(CardsAction.find).toBeCalledWith(cardId); 48 | }); 49 | 50 | it('addChangeListener is assigned on mount', function() { 51 | var localSubject = subject({}); 52 | expect(CardsStore.addChangeListener) 53 | .toBeCalledWith(localSubject.refs.component.onChange); 54 | }); 55 | 56 | it('removeChangeListener is assigned on unmount', function() { 57 | var localSubject = subject({}); 58 | localSubject.refs.component.componentWillUnmount(); 59 | expect(CardsStore.removeChangeListener) 60 | .toBeCalledWith(localSubject.refs.component.onChange); 61 | }); 62 | 63 | it('transition to edit card on edit call', function() { 64 | var localSubject = subject({}); 65 | localSubject.refs.component.edit(); 66 | 67 | expect(asserts.destroyName).toEqual('edit_card'); 68 | expect(asserts.destroyArgs).toEqual({ cardId: cardId }); 69 | }); 70 | 71 | it('transition to destroy card on destroy call', function() { 72 | var localSubject = subject({}); 73 | localSubject.refs.component.destroy(); 74 | 75 | expect(asserts.destroyName).toEqual('destroy_card'); 76 | expect(asserts.destroyArgs).toEqual({ cardId: cardId }); 77 | }); 78 | 79 | it('renders ShowCard component', function() { 80 | var RC = TestUtils.scryRenderedComponentsWithType(subject({}), ShowCard); 81 | expect(RC.length).toEqual(1); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /components/__tests__/subscribe-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../subscribe'); 4 | jest.dontMock('../../support/stub_router_context'); 5 | 6 | var React = require('react'); 7 | var TestUtils = require('react/lib/ReactTestUtils') 8 | var jQuery = require('jquery'); 9 | 10 | var App = require('../../components/app'); 11 | var MailchimpService = require('../../services/mailchimp_service'); 12 | 13 | describe('Subscribe', function() { 14 | var Subscribe = require('../subscribe'); 15 | 16 | var assets = {}; 17 | var email = 'test@example.com'; 18 | 19 | var setAlert = function(message) { assets['setAlert'] = message; }; 20 | 21 | var subject = function() { 22 | return TestUtils.renderIntoDocument( 23 | 24 | ); 25 | }; 26 | 27 | beforeEach(function() { 28 | MailchimpService.subscribe = jest.genMockFunction(); 29 | jQuery = jest.genMockFunction().mockReturnThis(); 30 | }); 31 | 32 | describe('#handleSubmit', function() { 33 | it('calls sendToFirebase with expected arguments', function() { 34 | var localSubject = subject(); 35 | 36 | localSubject.refs.email.getDOMNode().value = email; 37 | 38 | localSubject.handleSubmit({preventDefault: jest.genMockFn()}) 39 | 40 | expect(MailchimpService.subscribe).toBeCalledWith( 41 | jQuery, 42 | email, 43 | function() {}, 44 | function() {} 45 | ); 46 | }); 47 | }); 48 | 49 | describe('#handlerError', function() { 50 | it('calls setAlert', function() { 51 | subject().handlerError(); 52 | expect(assets.setAlert).toEqual('Something failed. Developers have been informed.'); 53 | }); 54 | }); 55 | 56 | describe('#handlerSuccess', function() { 57 | var localSubject = subject(); 58 | localSubject.handlerSuccess(); 59 | 60 | it('renders', function() { 61 | expect(localSubject.getDOMNode().textContent) 62 | .toContain('Thanks'); 63 | }); 64 | }); 65 | 66 | it('renders', function() { 67 | expect(subject().getDOMNode().textContent) 68 | .toContain('Subscribe'); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /components/account.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var Link = Router.Link; 7 | 8 | module.exports = React.createClass({ 9 | displayName: 'Account', 10 | 11 | render: function() { 12 | return ( 13 |
14 | Delete Account 15 |
16 | ); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /components/alert.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | module.exports = React.createClass({ 6 | displayName: 'Alert', 7 | 8 | propTypes: { 9 | message: React.PropTypes.string, 10 | }, 11 | 12 | classNames: function() { 13 | return this.props.message ? 'alert alert-danger' : 'alert-null'; 14 | }, 15 | 16 | render: function() { 17 | return ( 18 |
{this.props.message}
19 | ); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /components/app.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | var Firebase = require('firebase'); 6 | 7 | var Alert = require('./alert'); 8 | var AuthenticationService = require('../services/authentication_service'); 9 | var Navigation = require('./navigation'); 10 | 11 | var Link = Router.Link; 12 | var RouteHandler = Router.RouteHandler; 13 | 14 | var firebaseInstance = function(endpoint) { 15 | endpoint = endpoint || ''; 16 | return new Firebase((__FIREBASE_URL__ + endpoint)); 17 | }; 18 | 19 | module.exports = React.createClass({ 20 | displayName: 'Application', 21 | 22 | statics: { 23 | warden: AuthenticationService, 24 | firebase: firebaseInstance 25 | }, 26 | 27 | getInitialState: function() { 28 | return { 29 | alerts: { 30 | danger: null 31 | } 32 | }; 33 | }, 34 | 35 | setAlert: function(danger) { 36 | this.setState({ 37 | alerts: { 38 | danger: danger 39 | } 40 | }); 41 | 42 | setTimeout(function() { 43 | this.setAlert(null); 44 | }.bind(this), 3000); 45 | }, 46 | 47 | render: function() { 48 | return ( 49 |
50 |
51 | 52 |

Smart Pickings

53 | 54 | 55 | 56 |
57 | 58 | 59 | 60 |
61 | 62 |
63 |
64 | ); 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /components/authenticated.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var App = require('./app'); 6 | 7 | module.exports = function(WrappedComponent) { 8 | return React.createClass({ 9 | displayName: 'Authenticated', 10 | 11 | statics: { 12 | willTransitionTo: function(transition) { 13 | if (!App.warden.loggedIn()) { 14 | transition.redirect('/login', {}, {'nextPath' : transition.path}); 15 | } 16 | } 17 | }, 18 | 19 | render: function() { 20 | return (); 21 | } 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /components/bye.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var App = require('./app'); 6 | 7 | module.exports = React.createClass({ 8 | displayName: 'Bye', 9 | 10 | render: function() { 11 | return ( 12 |
13 | Sad to see you leave. 14 |
15 | ); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /components/card.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var Link = Router.Link; 7 | 8 | module.exports = React.createClass({ 9 | displayName: 'Card', 10 | 11 | propTypes: { 12 | record: React.PropTypes.object.isRequired, 13 | }, 14 | 15 | render: function() { 16 | return ( 17 |
18 |
19 |
20 | 21 |
22 |
23 |
24 | 25 | {this.props.record.price} 26 | 27 |
28 |
{this.props.record.description}
29 |
{this.props.record.location}
30 |
31 |
32 |
33 | ); 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /components/card_form.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var GetFormData = require('get-form-data'); 6 | 7 | module.exports = React.createClass({ 8 | displayName: 'CardForm', 9 | 10 | propTypes: { 11 | submitLabel: React.PropTypes.string.isRequired, 12 | handleSubmit: React.PropTypes.func.isRequired, 13 | }, 14 | 15 | handleSubmit: function(e) { 16 | e.preventDefault(); 17 | 18 | this.props.handleSubmit( 19 | new GetFormData(this.refs.form.getDOMNode(), { trim: true }) 20 | ); 21 | }, 22 | 23 | render: function() { 24 | return ( 25 |
26 |
27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 |
37 | 38 | 39 |
40 |
41 | 42 |
43 | 55 |
56 |
57 |
58 | 59 |
60 | 72 |
73 |
74 |
75 |
76 |
No image
77 |
78 | Add an image 79 |
80 |
81 |
82 | 83 |
84 |
85 |
86 | ); 87 | } 88 | }); 89 | -------------------------------------------------------------------------------- /components/cards.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | var _ = require('lodash'); 6 | 7 | var Card = require('./card'); 8 | 9 | var Link = Router.Link; 10 | 11 | module.exports = React.createClass({ 12 | displayName: 'Cards', 13 | 14 | renderCard: function(record, key) { 15 | return ; 16 | }, 17 | 18 | render: function() { 19 | return ( 20 |
21 |
22 | Bedrooms 23 | Price 24 | Bathrooms 25 |
26 |
27 | Add a new property 28 |
29 | 30 | {_.map(this.props.cards, this.renderCard)} 31 |
32 | ); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /components/cards_container.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var App = require('../components/app'); 6 | var CardsAction = require('../actions/cards_action'); 7 | var CardsStore = require('../stores/cards_store'); 8 | 9 | var Cards = require('./cards'); 10 | var NoCards = require('./no_cards'); 11 | 12 | function getStateFromStores() { 13 | return { 14 | cards: CardsStore.list(), 15 | }; 16 | } 17 | 18 | function hasCards(cards) { 19 | return Object.keys(cards).length > 0; 20 | } 21 | 22 | module.exports = React.createClass({ 23 | displayName: 'CardsContainer', 24 | 25 | getInitialState: function() { 26 | return getStateFromStores(); 27 | }, 28 | 29 | componentWillMount: function() { 30 | CardsAction.list(App.warden.getLocalStorageUser()); 31 | CardsStore.addChangeListener(this.onChange); 32 | }, 33 | componentWillUnmount: function() { 34 | CardsStore.removeChangeListener(this.onChange); 35 | }, 36 | 37 | onChange: function() { 38 | this.setState(getStateFromStores()); 39 | }, 40 | renderContent: function() { 41 | if (hasCards(this.state.cards)) { 42 | return ; 43 | } else { 44 | return ; 45 | } 46 | }, 47 | 48 | render: function() { 49 | return this.renderContent(); 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /components/dashboard.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var CardsContainer = require('./cards_container'); 7 | 8 | var Link = Router.Link; 9 | 10 | module.exports = React.createClass({ 11 | displayName: 'Dashboard', 12 | 13 | render: function() { 14 | return (); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /components/deregister.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var App = require('../components/app'); 6 | var FirebaseService = require('../services/firebase_service'); 7 | var Router = require('react-router'); 8 | 9 | var logoImage = require('../assets/images/logo-big.png'); 10 | 11 | module.exports = React.createClass({ 12 | displayName: 'Deregister', 13 | 14 | contextTypes: { 15 | router: React.PropTypes.func 16 | }, 17 | 18 | getInitialState: function() { 19 | return { 20 | invalidPassword: false, 21 | }; 22 | }, 23 | 24 | formElementClassNames: function() { 25 | if (this.state.invalidPassword) { 26 | return 'form-field form-field-error'; 27 | } else { 28 | return 'form-field'; 29 | } 30 | }, 31 | handleSubmit: function(e) { 32 | e.preventDefault(); 33 | 34 | if (window.confirm('Are you sure?')) { 35 | this.sendToFirebase( 36 | this.refs.email.getDOMNode().value.trim(), 37 | this.refs.password.getDOMNode().value.trim() 38 | ); 39 | } 40 | }, 41 | handlerInvalidUser: function() { 42 | this.props.setAlert('Nope, its confirmed, you dont exist'); 43 | }, 44 | handlerInvalidPassword: function() { 45 | this.setState({ invalidPassword: true }); 46 | }, 47 | handlerError: function(error) { 48 | this.props.setAlert('Something failed. Developers have been informed.'); 49 | }, 50 | handlerSuccess: function(data) { 51 | App.warden.logout(); 52 | this.context.router.replaceWith('/bye'); 53 | }, 54 | sendToFirebase: function(email, password) { 55 | FirebaseService.users.destroy( 56 | App.firebase(), 57 | email, 58 | password, 59 | this.handlerInvalidUser, 60 | this.handlerInvalidPassword, 61 | this.handlerError, 62 | this.handlerSuccess 63 | ); 64 | }, 65 | renderErrorElement: function() { 66 | if (this.state.invalidPassword) { 67 | return
Incorrect Password
; 68 | } 69 | }, 70 | 71 | render: function() { 72 | return ( 73 |
74 | 75 |
Delete Account
76 |
77 |
78 | 79 | 80 |
81 |
82 | 83 | 84 | {this.renderErrorElement()} 85 |
86 |
87 | 88 |
89 |
90 |
91 | ); 92 | } 93 | }); 94 | -------------------------------------------------------------------------------- /components/destroy_card.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | module.exports = React.createClass({ 6 | displayName: 'DestroyCard', 7 | 8 | propTypes: { 9 | confirmationText: React.PropTypes.string.isRequired, 10 | destroy: React.PropTypes.func.isRequired, 11 | }, 12 | 13 | handleSubmit: function(e) { 14 | e.preventDefault(); 15 | this.props.destroy(this.refs.confirmation.getDOMNode().value.trim()); 16 | }, 17 | 18 | render: function() { 19 | return ( 20 |
21 |
Delete Property
22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 |
30 |
31 |
32 | ); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /components/destroy_card_container.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var App = require('../components/app'); 6 | var DestroyCard = require('./destroy_card'); 7 | var FirebaseService = require('../services/firebase_service'); 8 | 9 | var confirmationText = 'confirm'; 10 | 11 | function canDestroy(text) { return text.toLowerCase() === confirmationText; } 12 | 13 | module.exports = React.createClass({ 14 | displayName: 'DestroyCardContainer', 15 | 16 | contextTypes: { 17 | router: React.PropTypes.func 18 | }, 19 | 20 | propTypes: { 21 | setAlert: React.PropTypes.func.isRequired, 22 | }, 23 | 24 | destroy: function(typedText) { 25 | if (canDestroy(typedText)) { 26 | this.sendToFirebase(); 27 | } else { 28 | this.setAlert(typedText); 29 | } 30 | }, 31 | getCardId: function() { 32 | return this.context.router.getCurrentParams().cardId; 33 | }, 34 | handlerError: function(error) { 35 | this.props.setAlert('Something failed. Developers have been informed.'); 36 | }, 37 | handlerSuccess: function() { 38 | this.context.router.replaceWith('dashboard'); 39 | }, 40 | sendToFirebase: function() { 41 | FirebaseService.cards.destroy( 42 | App.firebase(), 43 | this.getCardId(), 44 | this.handlerError, 45 | this.handlerSuccess 46 | ); 47 | }, 48 | setAlert: function(typedText) { 49 | this.props.setAlert( 50 | 'You typed ' + 51 | typedText + 52 | '. You should have typed ' + 53 | confirmationText + 54 | '.' 55 | ); 56 | }, 57 | 58 | render: function() { 59 | return ; 63 | } 64 | }); 65 | -------------------------------------------------------------------------------- /components/edit_card_container.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var App = require('../components/app'); 6 | var CardForm = require('../components/card_form'); 7 | var CardsAction = require('../actions/cards_action'); 8 | var CardsStore = require('../stores/cards_store'); 9 | var FirebaseService = require('../services/firebase_service'); 10 | 11 | function getStateFromStores() { 12 | return { 13 | card: CardsStore.card(), 14 | }; 15 | } 16 | 17 | function populateField(ref, state, value) { 18 | ref[value].getDOMNode().value = state[value]; 19 | } 20 | 21 | module.exports = React.createClass({ 22 | displayName: 'EditCardContainer', 23 | 24 | contextTypes: { 25 | router: React.PropTypes.func 26 | }, 27 | 28 | propTypes: { 29 | setAlert: React.PropTypes.func.isRequired, 30 | }, 31 | 32 | getInitialState: function() { 33 | return getStateFromStores(); 34 | }, 35 | 36 | componentWillMount: function() { 37 | CardsStore.addChangeListener(this.onChange); 38 | CardsAction.find(this.getCardId()); 39 | }, 40 | componentDidMount: function() { 41 | this.setDefaultValues(); 42 | }, 43 | componentWillUnmount: function() { 44 | CardsStore.removeChangeListener(this.onChange); 45 | }, 46 | 47 | getCardId: function() { 48 | return this.context.router.getCurrentParams().cardId; 49 | }, 50 | handlerError: function(error) { 51 | this.props.setAlert('Something failed. Developers have been informed.'); 52 | }, 53 | handlerSuccess: function() { 54 | this.context.router.replaceWith('show_card', { cardId: this.getCardId() }, {}); 55 | }, 56 | onChange: function() { 57 | this.setState(getStateFromStores()); 58 | }, 59 | update: function(data) { 60 | FirebaseService.cards.update( 61 | App.firebase(), 62 | this.getCardId(), 63 | data, 64 | this.handlerError, 65 | this.handlerSuccess 66 | ); 67 | }, 68 | setDefaultValues: function() { 69 | var formRefs = this.refs.form.refs; 70 | 71 | populateField(formRefs, this.state.card, 'location'); 72 | populateField(formRefs, this.state.card, 'price'); 73 | populateField(formRefs, this.state.card, 'description'); 74 | populateField(formRefs, this.state.card, 'bedrooms'); 75 | populateField(formRefs, this.state.card, 'bathrooms'); 76 | }, 77 | 78 | render: function() { 79 | return ( 80 | 85 | ); 86 | } 87 | }); 88 | -------------------------------------------------------------------------------- /components/logged_in.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var Authenticated = require('./authenticated'); 7 | 8 | var RouteHandler = Router.RouteHandler; 9 | 10 | module.exports = new Authenticated(React.createClass({ 11 | displayName: 'LoggedIn', 12 | 13 | render: function() { 14 | return ( 15 | 16 | ); 17 | } 18 | })); 19 | -------------------------------------------------------------------------------- /components/login.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var App = require('../components/app'); 6 | var FirebaseService = require('../services/firebase_service'); 7 | 8 | var logoImage = require('../assets/images/logo-big.png'); 9 | 10 | module.exports = React.createClass({ 11 | displayName: 'Login', 12 | 13 | contextTypes: { 14 | router: React.PropTypes.func 15 | }, 16 | 17 | componentDidMount: function() { 18 | this.populateFormValuesFromQuery(); 19 | }, 20 | 21 | grabEmail: function() { 22 | return this.refs.email.getDOMNode().value.trim(); 23 | }, 24 | handlePasswordReset: function(e) { 25 | this.context.router.transitionTo('password_reset', {}, { email: this.grabEmail() }); 26 | }, 27 | handleSubmit: function(e) { 28 | e.preventDefault(); 29 | 30 | this.sendToFirebase( 31 | this.grabEmail(), 32 | this.refs.password.getDOMNode().value.trim() 33 | ); 34 | }, 35 | handlerError: function(error) { 36 | this.props.setAlert('Something failed. Developers have been informed.'); 37 | }, 38 | handlerSuccess: function(data) { 39 | App.warden.login(data.uid); 40 | this.context.router.replaceWith('dashboard'); 41 | }, 42 | populateFormValuesFromQuery: function() { 43 | if (this.context.router) { 44 | var passedEmail = this.context.router.getCurrentQuery().email; 45 | 46 | if (passedEmail) { 47 | this.refs.email.getDOMNode().value = passedEmail; 48 | } 49 | } 50 | }, 51 | sendToFirebase: function(email, password) { 52 | FirebaseService.users.find( 53 | App.firebase(), 54 | email, 55 | password, 56 | this.handlerError, 57 | this.handlerSuccess 58 | ); 59 | }, 60 | 61 | render: function() { 62 | return ( 63 |
64 | 65 |
Login
66 |
67 |
68 | 69 | 70 |
71 |
72 | 73 | 74 |
75 | 78 |
79 | 80 |
81 |
82 |
83 | ); 84 | } 85 | }); 86 | -------------------------------------------------------------------------------- /components/logout.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var App = require('./app'); 6 | 7 | module.exports = React.createClass({ 8 | displayName: 'Logout', 9 | 10 | componentDidMount: function() { 11 | App.warden.logout(); 12 | }, 13 | 14 | render: function() { 15 | return ( 16 |
17 | Bye 18 |
19 | ); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /components/navigation.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var Link = Router.Link; 7 | 8 | module.exports = React.createClass({ 9 | displayName: 'Navigation', 10 | 11 | contextTypes: { 12 | router: React.PropTypes.func 13 | }, 14 | 15 | renderAuthLink: function() { 16 | var routePath = this.context.router.getCurrentPathname(); 17 | 18 | switch(routePath) { 19 | case '/bye': 20 | case '/login': 21 | return Register; 22 | case '/register': 23 | case '/logout': 24 | case '/password_reset': 25 | return Login; 26 | case '/': 27 | case '/dashboard': 28 | case '/new_card': 29 | return
30 | Account 31 | Logout 32 |
; 33 | default: 34 | return Logout; 35 | } 36 | }, 37 | 38 | render: function() { 39 | return ( 40 | 43 | ); 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /components/new_card_container.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var App = require('../components/app'); 6 | var FirebaseService = require('../services/firebase_service'); 7 | var CardForm = require('../components/card_form'); 8 | 9 | module.exports = React.createClass({ 10 | displayName: 'NewCardContainer', 11 | 12 | contextTypes: { 13 | router: React.PropTypes.func 14 | }, 15 | 16 | propTypes: { 17 | setAlert: React.PropTypes.func.isRequired, 18 | }, 19 | 20 | create: function(data) { 21 | FirebaseService.cards.create( 22 | App.firebase(), 23 | App.warden.getLocalStorageUser(), 24 | data, 25 | this.handlerError, 26 | this.handlerSuccess 27 | ); 28 | }, 29 | handlerError: function(error) { 30 | this.props.setAlert('Something failed. Developers have been informed.'); 31 | }, 32 | handlerSuccess: function(data) { 33 | this.context.router.transitionTo('/'); 34 | }, 35 | 36 | render: function() { 37 | return ( 38 | 42 | ); 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /components/no_cards.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var Link = Router.Link; 7 | 8 | var logoImage = require('../assets/images/logo-empty-state.png'); 9 | 10 | module.exports = React.createClass({ 11 | displayName: 'NoCards', 12 | 13 | render: function() { 14 | return ( 15 |
16 | 17 |
You haven't added a property yet.
18 | Add a property 19 |
20 | ); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /components/password_reset.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var App = require('../components/app'); 6 | var FirebaseService = require('../services/firebase_service'); 7 | var Router = require('react-router'); 8 | 9 | var logoImage = require('../assets/images/logo-big.png'); 10 | 11 | module.exports = React.createClass({ 12 | displayName: 'PasswordReset', 13 | 14 | contextTypes: { 15 | router: React.PropTypes.func 16 | }, 17 | 18 | getInitialState: function() { 19 | return { 20 | invalidUser: false, 21 | }; 22 | }, 23 | 24 | componentDidMount: function() { 25 | this.populateFormValuesFromQuery(); 26 | }, 27 | 28 | grabEmail: function() { 29 | return this.refs.email.getDOMNode().value.trim(); 30 | }, 31 | handleSubmit: function(e) { 32 | e.preventDefault(); 33 | this.sendToFirebase(this.grabEmail()); 34 | }, 35 | handlerError: function(error) { 36 | this.props.setAlert('Something failed. Developers have been informed.'); 37 | }, 38 | handlerInvalidUser: function() { 39 | this.setState({ invalidUser: true }); 40 | }, 41 | handlerSuccess: function() { 42 | this.context.router.transitionTo('login', {}, { email: this.grabEmail() }); 43 | }, 44 | populateFormValuesFromQuery: function() { 45 | if (this.context.router) { 46 | var passedEmail = this.context.router.getCurrentQuery().email; 47 | 48 | if (passedEmail) { 49 | this.refs.email.getDOMNode().value = passedEmail; 50 | } 51 | } 52 | }, 53 | sendToFirebase: function(email) { 54 | FirebaseService.users.resetPassword( 55 | App.firebase(), 56 | email, 57 | this.handlerInvalidUser, 58 | this.handlerError, 59 | this.handlerSuccess 60 | ); 61 | }, 62 | renderErrorElement: function() { 63 | if (this.state.invalidUser) { 64 | return
Invalid User
; 65 | } 66 | }, 67 | formElementClassNames: function() { 68 | if (this.state.invalidUser) { 69 | return 'form-field form-field-error'; 70 | } else { 71 | return 'form-field'; 72 | } 73 | }, 74 | 75 | render: function() { 76 | return ( 77 |
78 | 79 |
Password Reset
80 |
81 |
82 | 83 | 84 | {this.renderErrorElement()} 85 |
86 |
87 | 88 |
89 |
90 |
91 | ); 92 | } 93 | }); 94 | -------------------------------------------------------------------------------- /components/register.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var App = require('../components/app'); 7 | var FirebaseService = require('../services/firebase_service'); 8 | var TextInput = require('../helpers/text_input'); 9 | 10 | var logoImage = require('../assets/images/logo-big.png'); 11 | 12 | module.exports = React.createClass({ 13 | displayName: 'Register', 14 | 15 | contextTypes: { 16 | router: React.PropTypes.func 17 | }, 18 | 19 | getInitialState: function() { 20 | return { 21 | invalidEmail: false, 22 | emailTaken: false 23 | }; 24 | }, 25 | 26 | handleSubmit: function(e) { 27 | e.preventDefault(); 28 | 29 | this.sendToFirebase( 30 | this.refs.email.getDOMNode().value.trim(), 31 | this.refs.password.getDOMNode().value.trim() 32 | ); 33 | }, 34 | handlerEmailTaken: function() { 35 | this.setState({ emailTaken: true }); 36 | }, 37 | handlerInvalidEmail: function() { 38 | this.setState({ invalidEmail: true }); 39 | }, 40 | handlerError: function(error) { 41 | this.props.setAlert('Something failed. Developers have been informed.'); 42 | }, 43 | handlerLockdown: function(error) { 44 | this.props.setAlert(error); 45 | }, 46 | handlerSuccess: function(data) { 47 | App.warden.login(data.uid); 48 | this.context.router.replaceWith('dashboard'); 49 | }, 50 | sendToFirebase: function(email, password) { 51 | FirebaseService.users.create( 52 | App.firebase(), 53 | email, 54 | password, 55 | this.handlerEmailTaken, 56 | this.handlerInvalidEmail, 57 | this.handlerError, 58 | this.handlerSuccess, 59 | this.handlerLockdown 60 | ); 61 | }, 62 | renderEmailElement: function() { 63 | var error; 64 | 65 | if (this.state.emailTaken) { 66 | error = 'Email Taken'; 67 | } else if (this.state.invalidEmail) { 68 | error = 'Invalid Email Address'; 69 | } 70 | 71 | return TextInput.render('Email', 'email', { 72 | autofocus: true, 73 | required: true 74 | }, error); 75 | }, 76 | renderPasswordElement: function() { 77 | return TextInput.render('Password', 'password', { 78 | required: true 79 | }); 80 | }, 81 | 82 | render: function() { 83 | return ( 84 |
85 | 86 |
Register
87 |
88 | {this.renderEmailElement()} 89 | {this.renderPasswordElement()} 90 |
91 | 92 |
93 |
94 |
95 | ); 96 | } 97 | }); 98 | -------------------------------------------------------------------------------- /components/show_card.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | module.exports = React.createClass({ 6 | displayName: 'ShowCard', 7 | 8 | propTypes: { 9 | card: React.PropTypes.object.isRequired, 10 | destroy: React.PropTypes.func.isRequired, 11 | edit: React.PropTypes.func.isRequired, 12 | }, 13 | 14 | render: function() { 15 | return ( 16 |
17 |
18 |
19 |
{this.props.card.price}
20 | edit 21 | delete 22 |
{this.props.card.location}
23 |
    24 |
  • bedrooms {this.props.card.bedrooms}
  • 25 |
  • bathrooms {this.props.card.bathrooms}
  • 26 |
27 |
{this.props.card.description}
28 |
29 |
30 | ); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /components/show_card_container.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var CardsAction = require('../actions/cards_action'); 7 | var CardsStore = require('../stores/cards_store'); 8 | var ShowCard = require('./show_card'); 9 | 10 | function getStateFromStores() { 11 | return { 12 | card: CardsStore.card(), 13 | }; 14 | } 15 | 16 | module.exports = React.createClass({ 17 | displayName: 'ShowCardContainer', 18 | 19 | contextTypes: { 20 | router: React.PropTypes.func 21 | }, 22 | 23 | getInitialState: function() { 24 | return getStateFromStores(); 25 | }, 26 | 27 | componentWillMount: function() { 28 | CardsStore.addChangeListener(this.onChange); 29 | CardsAction.find(this.getCardId()); 30 | }, 31 | componentWillUnmount: function() { 32 | CardsStore.removeChangeListener(this.onChange); 33 | }, 34 | 35 | edit: function() { 36 | this.context.router.transitionTo( 37 | 'edit_card', 38 | { cardId: this.getCardId() }, 39 | {} 40 | ); 41 | }, 42 | getCardId: function() { 43 | return this.context.router.getCurrentParams().cardId; 44 | }, 45 | destroy: function() { 46 | this.context.router.transitionTo( 47 | 'destroy_card', 48 | { cardId: this.getCardId() }, 49 | {} 50 | ); 51 | }, 52 | onChange: function() { 53 | this.setState(getStateFromStores()); 54 | }, 55 | 56 | render: function() { 57 | return ; 62 | } 63 | }); 64 | -------------------------------------------------------------------------------- /components/subscribe.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var jQuery = require('jquery'); 5 | 6 | var App = require('../components/app'); 7 | var MailchimpService = require('../services/mailchimp_service'); 8 | var Router = require('react-router'); 9 | 10 | var logoImage = require('../assets/images/logo-big.png'); 11 | 12 | module.exports = React.createClass({ 13 | displayName: 'Subscribe', 14 | 15 | contextTypes: { 16 | router: React.PropTypes.func 17 | }, 18 | 19 | getInitialState: function() { 20 | return { 21 | submitted: false, 22 | }; 23 | }, 24 | 25 | handleSubmit: function(e) { 26 | e.preventDefault(); 27 | 28 | this.sendToMailchimp( 29 | this.refs.email.getDOMNode().value.trim() 30 | ); 31 | }, 32 | handlerError: function(error) { 33 | this.props.setAlert('Something failed. Developers have been informed.'); 34 | }, 35 | handlerSuccess: function(data) { 36 | this.setState({ submitted: true }); 37 | }, 38 | sendToMailchimp: function(email) { 39 | MailchimpService.subscribe( 40 | jQuery, 41 | email, 42 | this.handlerError, 43 | this.handlerSuccess 44 | ); 45 | }, 46 | renderContent: function() { 47 | if (this.state.submitted) { 48 | return ( 49 |
Thanks
50 | ); 51 | } else { 52 | return ( 53 |
54 |
Subscribe
55 |
56 |
57 | 58 | 59 |
60 |
61 | 62 |
63 |
64 |
65 | ); 66 | } 67 | }, 68 | 69 | render: function() { 70 | return ( 71 |
72 | 73 | {this.renderContent()} 74 |
75 | ); 76 | } 77 | }); 78 | -------------------------------------------------------------------------------- /config/routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var Account = require('../components/account'); 7 | var App = require('../components/app'); 8 | var Bye = require('../components/bye'); 9 | var Dashboard = require('../components/dashboard'); 10 | var Deregister = require('../components/deregister'); 11 | var DestroyCardContainer = require('../components/destroy_card_container'); 12 | var EditCardContainer = require('../components/edit_card_container'); 13 | var Logout = require('../components/logout'); 14 | var LoggedIn = require('../components/logged_in'); 15 | var Login = require('../components/login'); 16 | var Logout = require('../components/logout'); 17 | var NewCardContainer = require('../components/new_card_container'); 18 | var Register = require('../components/register'); 19 | var PasswordReset = require('../components/password_reset'); 20 | var Subscribe = require('../components/subscribe'); 21 | var ShowCardContainer = require('../components/show_card_container'); 22 | 23 | var Route = Router.Route; 24 | 25 | module.exports = ( 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | -------------------------------------------------------------------------------- /config/routing_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 404 5 | 6 | 7 | #!/ 8 | 9 | 10 | 11 | 12 | 403 13 | 14 | 15 | #!/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /config/security.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "$user" : { 4 | ".write": "auth.uid == $user" 5 | }, 6 | "cards" : { 7 | "$card" : { 8 | ".read": "auth != null && auth.uid == data.child('userId').val()", 9 | ".write": "auth !== null", 10 | ".validate" : "newData.hasChildren(['userId']) && auth.uid === newData.child('userId').val()" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /constants/cards_constants.js: -------------------------------------------------------------------------------- 1 | var keyMirror = require('react/lib/keyMirror'); 2 | 3 | module.exports = keyMirror({ 4 | CARDS_FIND: null, 5 | CARDS_LIST: null, 6 | }); 7 | -------------------------------------------------------------------------------- /dao/__tests__/mailchimp_dao-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../mailchimp_dao'); 4 | 5 | var MailchimpDAO = require('../mailchimp_dao'); 6 | 7 | describe('MailchimpDAO', function() { 8 | var assets = {}; 9 | var email = 'test@example.com'; 10 | 11 | var mockAdapter = { 12 | ajax: jest.genMockFunction() 13 | }; 14 | 15 | describe('#subscribe', function() { 16 | var subject = function() { 17 | return MailchimpDAO.subscribe(mockAdapter, email); 18 | }; 19 | 20 | var url = 'http://smartpickings.us9.list-manage.com/subscribe/post-json?u=' + __MAILCHIMP_API_KEY__ + '&id=' + __MAILCHIMP_LIST_ID__ + '&c=?'; 21 | 22 | it('returns agent', function() { 23 | subject(); 24 | 25 | expect(mockAdapter.ajax).toBeCalledWith({ 26 | type: 'POST', 27 | crossDomain: true, 28 | dataType: 'jsonp', 29 | timeout: 10000, 30 | url: url, 31 | data: { 'EMAIL': email } 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /dao/mailchimp_dao.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var endpoints = { 4 | subscribe: 'http://smartpickings.us9.list-manage.com/subscribe/post-json?u=' + __MAILCHIMP_API_KEY__ + '&id=' + __MAILCHIMP_LIST_ID__ + '&c=?' 5 | }; 6 | 7 | var subscribe = function(adapter, email) { 8 | return adapter.ajax({ 9 | type: 'POST', 10 | crossDomain: true, 11 | dataType: 'jsonp', 12 | timeout: 10000, 13 | url: endpoints.subscribe, 14 | data: { 'EMAIL': email } 15 | }); 16 | }; 17 | 18 | module.exports = { 19 | subscribe: subscribe, 20 | }; 21 | -------------------------------------------------------------------------------- /dist/bundle/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChuckJHardy/ReactJS-Example/9ca31313c95d916a7026588ea00798f439afd1e3/dist/bundle/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /dist/bundle/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChuckJHardy/ReactJS-Example/9ca31313c95d916a7026588ea00798f439afd1e3/dist/bundle/favicon-32x32.png -------------------------------------------------------------------------------- /dist/bundle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Smart Pickings - Intelligent Property Decisions 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /dist/bundle/robot.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /env.example.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | GOOGLE_ANALYTICS_KEY: "", 3 | FIREBASE_URL: "", 4 | KEEN_PROJECT_ID: "", 5 | KEEN_WRITE_KEY: "", 6 | AIRBRAKE_PRODUCT_ID: "", 7 | AIRBRAKE_PRODUCT_KEY: "", 8 | AWS_ACCESS_KEY_ID: "", 9 | AWS_SECRET_ACCESS_KEY: "", 10 | LOCAL_STORAGE_KEY: "smartpickingsuid", 11 | LOCKDOWN_KEY: "044b00002ab4eddd0033b0de4544bbe7", 12 | MAILCHIMP_API_KEY: "", 13 | MAILCHIMP_LIST_ID: "", 14 | }; 15 | -------------------------------------------------------------------------------- /helpers/__tests__/text_inputs-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../text_input'); 4 | 5 | var React = require('react'); 6 | var TestUtils = require('react/lib/ReactTestUtils') 7 | 8 | describe('TextInput', function () { 9 | var TextInput = require('../text_input'); 10 | 11 | var subject = function(error, options) { 12 | options = options = { 13 | required: true, 14 | autofocus: true, 15 | }; 16 | 17 | var Component = React.createClass({ 18 | render: function() { 19 | return TextInput.render('Email', 'email', options, error); 20 | } 21 | }); 22 | 23 | return TestUtils.renderIntoDocument(); 24 | }; 25 | 26 | it('renders label text', function() { 27 | expect(subject().getDOMNode().innerHTML).toContain('Email'); 28 | }); 29 | 30 | it('renders label name', function() { 31 | expect(subject().getDOMNode().innerHTML).toContain('email'); 32 | }); 33 | 34 | it('renders label type', function() { 35 | expect(subject().getDOMNode().innerHTML).toContain('text'); 36 | }); 37 | 38 | it('renders with required', function() { 39 | expect(subject().getDOMNode().innerHTML).toContain('required'); 40 | }); 41 | 42 | it('renders error message', function() { 43 | var error = 'Oops'; 44 | expect(subject(error).getDOMNode().innerHTML).toContain(error); 45 | }); 46 | 47 | it('renders error class', function() { 48 | var error = 'Oops'; 49 | expect(subject(error).getDOMNode().innerHTML).toContain('form-error-message'); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /helpers/text_input.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Assign = require('react/lib/Object.assign'); 5 | 6 | var TextInput = function(label, name, options, error) { 7 | var classNames = 'form-field'; 8 | 9 | options = Assign({ 10 | type: 'text', 11 | autofocus: false, 12 | required: false 13 | }, options) 14 | 15 | if (error) { 16 | classNames = classNames + ' form-field-error'; 17 | error =
{error}
; 18 | } 19 | 20 | return ( 21 |
22 | 23 | 30 | {error} 31 |
32 | ); 33 | }; 34 | 35 | module.exports = { 36 | render: TextInput 37 | }; 38 | -------------------------------------------------------------------------------- /main.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | var Airbrake = require('airbrake-js'); 6 | 7 | var Routes = require('./config/routes'); 8 | 9 | window.React = React; 10 | window.isProduction = process.env.NODE_ENV === 'production'; 11 | 12 | window.airbrake = new Airbrake({ 13 | projectId: __AIRBRAKE_PRODUCT_ID__, 14 | projectKey: __AIRBRAKE_PRODUCT_KEY__ 15 | }); 16 | 17 | require('normalize.css/normalize.css'); 18 | require('./styles/main.scss'); 19 | 20 | Router.run(Routes, Router.HistoryLocation, function (Handler) { 21 | React.render( 22 | , 23 | document.getElementsByTagName('root')[0] 24 | ); 25 | }); 26 | 27 | require('./support/google_analytics_tracking')(window.isProduction); 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Washington", 3 | "version": "1.0.0", 4 | "description": "Smart Pickings UI", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --progress --port 9292 --colors --content-base dist/bundle/", 8 | "build:development": "webpack --config webpack.config.js", 9 | "build:production": "webpack --config webpack.config.production.js -p", 10 | "deploy:development": "./bin/development_deploy", 11 | "deploy:production": "./bin/production_deploy", 12 | "test:live": "./bin/test_deploy", 13 | "test": "jest --coverage" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://bitbucket.org/InsertCoffee/washington" 18 | }, 19 | "author": "Chuck J Hardy", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "css-loader": "^0.10.1", 23 | "file-loader": "^0.8.1", 24 | "jest-cli": "^0.4.0", 25 | "jsx-loader": "^0.13.1", 26 | "jsxhint-loader": "^0.2.0", 27 | "react-tools": "^0.13.1", 28 | "sass-loader": "0.3.0", 29 | "style-loader": "^0.10.1", 30 | "url-loader": "^0.5.5", 31 | "webpack": "^1.8.4", 32 | "webpack-dev-server": "^1.8.0" 33 | }, 34 | "dependencies": { 35 | "airbrake-js": "^0.4.0-alpha.2", 36 | "firebase": "^2.2.4", 37 | "flux": "^2.0.3", 38 | "get-form-data": "^1.2.1", 39 | "jquery": "^2.1.3", 40 | "lodash": "^3.7.0", 41 | "normalize.css": "^3.0.3", 42 | "react": "^0.13.1", 43 | "react-router": "^0.13.2" 44 | }, 45 | "jest": { 46 | "globals": { 47 | "__DEV__": false, 48 | "__TEST__": true, 49 | "__GOOGLE_ANALYTICS_KEY__": "google-analytics-test-key", 50 | "__FIREBASE_URL__": "firebase-test-url", 51 | "__AIRBRAKE_PRODUCT_ID__": "airbrake-test-project-id", 52 | "__AIRBRAKE_PRODUCT_KEY__": "airbrake-test-project-key", 53 | "__LOCAL_STORAGE_KEY__": "localStorage-test-key", 54 | "__LOCKDOWN_KEY__": "lockdown-test-key", 55 | "__MAILCHIMP_API_KEY__": "mailchimp-test-api-key", 56 | "__MAILCHIMP_LIST_ID__": "mailchimp-test-list-id" 57 | }, 58 | "scriptPreprocessor": "/preprocessor", 59 | "unmockedModulePathPatterns": [ 60 | "/node_modules/react", 61 | "/node_modules/react-tools" 62 | ], 63 | "modulePathIgnorePatterns": [ 64 | "/utilities/logger/airbrake", 65 | "/node_modules/jquery", 66 | "/node_modules/react", 67 | "/support/" 68 | ], 69 | "moduleFileExtensions": [ 70 | "js", 71 | "jsx" 72 | ], 73 | "testFileExtensions": [ 74 | "js" 75 | ], 76 | "testPathDirs": [ 77 | "/" 78 | ] 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /preprocessor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ReactTools = require('react-tools'); 4 | 5 | module.exports = { 6 | process: function(src, filename) { 7 | if (filename.match(/\.png$/)) { 8 | return ''; 9 | } 10 | 11 | return ReactTools.transform(src); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /services/__tests__/authentication_service-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../authentication_service'); 4 | 5 | describe('AuthenticationService', function() { 6 | var AuthenticationService = require('../authentication_service'); 7 | var localStorageAssets = {}; 8 | 9 | window.localStorage = { 10 | setItem: function(key, value) { 11 | localStorageAssets[key] = value; 12 | }, 13 | removeItem: function(key) { 14 | localStorageAssets[key] = null; 15 | }, 16 | getItem: function(key) { 17 | return localStorageAssets[key]; 18 | } 19 | }; 20 | 21 | beforeEach(function() { 22 | AuthenticationService.logout(); 23 | }); 24 | 25 | describe('.getCachedUser', function() { 26 | var subject = AuthenticationService.getCachedUser(); 27 | 28 | it('returns cachedUser', function() { 29 | expect(subject).toEqual(null); 30 | }); 31 | }); 32 | 33 | describe('.getLocalStorageUser', function() { 34 | var subject = AuthenticationService.getLocalStorageUser(); 35 | 36 | it('returns local storage users', function() { 37 | expect(subject).toEqual(undefined); 38 | }); 39 | }); 40 | 41 | describe('.login', function() { 42 | var uid = 123; 43 | var assets = {}; 44 | 45 | beforeEach(function() { 46 | AuthenticationService.logout(); 47 | 48 | AuthenticationService.onChangeListener = function(uid) { 49 | assets['login'] = uid ; 50 | }; 51 | 52 | AuthenticationService.login(uid); 53 | }); 54 | 55 | it('sets the cachedUser', function() { 56 | expect(AuthenticationService.getCachedUser()).toEqual(uid); 57 | }); 58 | 59 | it('sets the localstorage', function() { 60 | expect(AuthenticationService.getLocalStorageUser()).toEqual(uid); 61 | }); 62 | 63 | it('calls the onChangeListener', function() { 64 | expect(assets.login).toEqual(uid); 65 | }); 66 | }); 67 | 68 | describe('.logout', function() { 69 | var assets = {}; 70 | 71 | beforeEach(function() { 72 | AuthenticationService.onChangeListener = function(uid) { 73 | assets['logout'] = uid ; 74 | }; 75 | 76 | AuthenticationService.logout(); 77 | }); 78 | 79 | it('resets cachedUser', function() { 80 | expect(AuthenticationService.getCachedUser()).toEqual(null); 81 | }); 82 | 83 | it('calls the onChangeListener', function() { 84 | expect(assets.logout).toEqual(null); 85 | }); 86 | }); 87 | 88 | describe('.loggedIn', function() { 89 | var assets = {}; 90 | 91 | beforeEach(function() { 92 | AuthenticationService.loggedIn(); 93 | }); 94 | 95 | describe('From Cached User', function() { 96 | it('returns false when null', function() { 97 | expect(AuthenticationService.loggedIn()).toEqual(false); 98 | }); 99 | 100 | it('returns true when assigned', function() { 101 | AuthenticationService.login(1); 102 | expect(AuthenticationService.loggedIn()).toEqual(true); 103 | }); 104 | }); 105 | 106 | describe('From Local Storage User', function() { 107 | it('returns false when null', function() { 108 | expect(AuthenticationService.loggedIn()).toEqual(false); 109 | }); 110 | 111 | it('returns true when assigned', function() { 112 | window.localStorage.setItem('localStorage-test-key', 1); 113 | expect(AuthenticationService.loggedIn()).toEqual(true); 114 | }); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /services/__tests__/firebase_service-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../firebase_service'); 4 | 5 | describe('FirebaseService', function() { 6 | var FirebaseService = require('../firebase_service'); 7 | 8 | describe('.users', function() { 9 | it('returns function', function() { 10 | expect(FirebaseService.users).toBeDefined(); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /services/__tests__/mailchimp_service-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../mailchimp_service'); 4 | 5 | var MailchimpService = require('../mailchimp_service'); 6 | var MailchimpDAO = require('../../dao/mailchimp_dao'); 7 | var Logger = require('../../utilities/logger'); 8 | 9 | describe('MailchimpService', function() { 10 | var adapter = jest.genMockFunction(); 11 | var email = 'test@example.com'; 12 | 13 | describe('#subscribe', function() { 14 | var asserts = {}; 15 | var callbacks = { 16 | errorCallback: function(error) { asserts['errorCallback'] = error }, 17 | successCallback: function(data) { asserts['successCallback'] = data }, 18 | }; 19 | 20 | var subject = function(data, error) { 21 | var mockDAO = { 22 | then: function(callback) { callback(data); return mockDAO; }, 23 | }; 24 | 25 | MailchimpDAO.subscribe = jest.genMockFunction().mockReturnValue(mockDAO); 26 | 27 | return MailchimpService.subscribe( 28 | adapter, 29 | email, 30 | callbacks.errorCallback, 31 | callbacks.successCallback 32 | ); 33 | }; 34 | 35 | describe('Success', function() { 36 | var error = 'Oops'; 37 | var data = { 38 | result: 'success', 39 | msg: error 40 | }; 41 | 42 | beforeEach(function() { 43 | Logger.notice.users.subscribe = jest.genMockFunction(); 44 | subject(data) 45 | }); 46 | 47 | it('calls callback with email', function() { 48 | expect(asserts.successCallback).toEqual(data); 49 | }); 50 | 51 | it('calls off to logger with correct args', function() { 52 | expect(Logger.notice.users.subscribe).toBeCalledWith(email, data); 53 | }); 54 | }); 55 | 56 | describe('Error', function() { 57 | var error = 'Oops'; 58 | var data = { 59 | result: 'error', 60 | msg: error 61 | }; 62 | 63 | beforeEach(function() { 64 | Logger.warn.users.subscribe = jest.genMockFunction(); 65 | subject(error); 66 | }); 67 | 68 | it('calls callback with email', function() { 69 | expect(asserts.errorCallback).toEqual(error); 70 | }); 71 | 72 | it('calls off to logger with correct args', function() { 73 | expect(Logger.warn.users.subscribe) 74 | .toBeCalledWith(email, error); 75 | }); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /services/authentication_service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var cachedUser; 4 | 5 | module.exports = { 6 | getCachedUser: function() { 7 | return cachedUser; 8 | }, 9 | 10 | getLocalStorageUser: function() { 11 | return window.localStorage.getItem(__LOCAL_STORAGE_KEY__); 12 | }, 13 | 14 | login: function(uid) { 15 | cachedUser = uid; 16 | window.localStorage.setItem(__LOCAL_STORAGE_KEY__, uid); 17 | this.onChangeListener(uid); 18 | }, 19 | 20 | logout: function() { 21 | cachedUser = null; 22 | window.localStorage.removeItem(__LOCAL_STORAGE_KEY__); 23 | this.onChangeListener(null); 24 | }, 25 | 26 | loggedIn: function() { 27 | return !!(this.getCachedUser() || this.getLocalStorageUser()); 28 | }, 29 | 30 | onChangeListener: function() {} 31 | }; 32 | -------------------------------------------------------------------------------- /services/firebase_service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Cards = require('./firebase_service/cards'); 4 | var Users = require('./firebase_service/users'); 5 | 6 | module.exports = { 7 | cards: Cards, 8 | users: Users, 9 | }; 10 | -------------------------------------------------------------------------------- /services/firebase_service/__tests__/cards-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../cards'); 4 | 5 | var Cards = require('../cards'); 6 | 7 | describe('Cards', function() { 8 | it('create is defined', function() { 9 | expect(Cards.create).toBeDefined(); 10 | }); 11 | 12 | it('destroy is defined', function() { 13 | expect(Cards.destroy).toBeDefined(); 14 | }); 15 | 16 | it('update is defined', function() { 17 | expect(Cards.update).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /services/firebase_service/__tests__/users-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../users'); 4 | 5 | var Users = require('../users'); 6 | 7 | describe('Users', function() { 8 | it('create is defined', function() { 9 | expect(Users.create).toBeDefined(); 10 | }); 11 | 12 | it('destroy is defined', function() { 13 | expect(Users.destroy).toBeDefined(); 14 | }); 15 | 16 | it('find is defined', function() { 17 | expect(Users.find).toBeDefined(); 18 | }); 19 | 20 | it('resetPassword is defined', function() { 21 | expect(Users.resetPassword).toBeDefined(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /services/firebase_service/cards.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Create = require('./cards/create'); 4 | var Destroy = require('./cards/destroy'); 5 | var Update = require('./cards/update'); 6 | 7 | module.exports = { 8 | create: Create, 9 | destroy: Destroy, 10 | update: Update, 11 | }; 12 | -------------------------------------------------------------------------------- /services/firebase_service/cards/__tests__/create-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../create'); 4 | 5 | var Create = require('../create'); 6 | var Logger = require('../../../../utilities/logger'); 7 | 8 | describe('Create', function() { 9 | var asserts = {}; 10 | var userId = 123; 11 | 12 | var mockAdapter = function(error, data) { 13 | var calls = { 14 | child: function(key) { asserts['child'] = key; return calls }, 15 | push: function(data, callback) { callback(error, data); }, 16 | }; 17 | 18 | return calls; 19 | }; 20 | 21 | var callbacks = { 22 | errorCallback: function(error) { asserts['errorCallback'] = error }, 23 | successCallback: function(data) { asserts['successCallback'] = data }, 24 | }; 25 | 26 | var subject = function(error, data) { 27 | return Create( 28 | mockAdapter(error, data), 29 | userId, 30 | data, 31 | callbacks.errorCallback, 32 | callbacks.successCallback 33 | ); 34 | }; 35 | 36 | describe('Success', function() { 37 | var data = { something: 1 }; 38 | 39 | var expectedData = { 40 | userId: userId, 41 | something: 1, 42 | }; 43 | 44 | beforeEach(function() { 45 | subject(null, data) 46 | }); 47 | 48 | it('created child node', function() { 49 | expect(asserts.child).toEqual('cards'); 50 | }); 51 | 52 | it('calls callback with email', function() { 53 | expect(asserts.successCallback).toEqual(expectedData); 54 | }); 55 | 56 | it('calls off to logger with correct args', function() { 57 | expect(Logger.notice.cards.created).toBeCalledWith(expectedData); 58 | }); 59 | }); 60 | 61 | describe('Error', function() { 62 | var error = 'Oops'; 63 | var expectedData = { 64 | userId: userId 65 | }; 66 | 67 | beforeEach(function() { 68 | subject(error, {}) 69 | }); 70 | 71 | it('created child node', function() { 72 | expect(asserts.child).toEqual('cards'); 73 | }); 74 | 75 | it('calls callback with email', function() { 76 | expect(asserts.errorCallback).toEqual(error); 77 | }); 78 | 79 | it('calls off to logger with correct args', function() { 80 | expect(Logger.warn.cards.createFail).toBeCalledWith(expectedData, error); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /services/firebase_service/cards/__tests__/destroy-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../destroy'); 4 | 5 | var Destroy = require('../destroy'); 6 | var Logger = require('../../../../utilities/logger'); 7 | 8 | describe('Destroy', function() { 9 | var asserts = { 10 | child: [] 11 | }; 12 | 13 | var cardId = 123; 14 | 15 | var mockAdapter = function(error) { 16 | var calls = { 17 | child: function(key) { asserts.child.push(key); return calls }, 18 | remove: function(callback) { callback(error); }, 19 | }; 20 | 21 | return calls; 22 | }; 23 | 24 | var callbacks = { 25 | errorCallback: function(error) { asserts['errorCallback'] = error }, 26 | successCallback: function(data) { asserts['successCallback'] = 'called' }, 27 | }; 28 | 29 | var subject = function(error) { 30 | return Destroy( 31 | mockAdapter(error), 32 | cardId, 33 | callbacks.errorCallback, 34 | callbacks.successCallback 35 | ); 36 | }; 37 | 38 | describe('Success', function() { 39 | beforeEach(function() { 40 | subject(null) 41 | }); 42 | 43 | it('created child node', function() { 44 | expect(asserts.child[0]).toEqual('cards'); 45 | expect(asserts.child[1]).toEqual(cardId); 46 | }); 47 | 48 | it('calls callback', function() { 49 | expect(asserts.successCallback).toEqual('called'); 50 | }); 51 | 52 | it('calls off to logger with correct args', function() { 53 | expect(Logger.notice.cards.destroyed).toBeCalledWith(cardId); 54 | }); 55 | }); 56 | 57 | describe('Error', function() { 58 | var error = 'Oops'; 59 | 60 | beforeEach(function() { 61 | subject(error) 62 | }); 63 | 64 | it('calls callback with email', function() { 65 | expect(asserts.errorCallback).toEqual(error); 66 | }); 67 | 68 | it('calls off to logger with correct args', function() { 69 | expect(Logger.warn.cards.destroyFail).toBeCalledWith(cardId, error); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /services/firebase_service/cards/__tests__/update-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../update'); 4 | 5 | var Update = require('../update'); 6 | var Logger = require('../../../../utilities/logger'); 7 | 8 | describe('Update', function() { 9 | var asserts = { 10 | child: [] 11 | }; 12 | 13 | var cardId = 123; 14 | 15 | var mockAdapter = function(error, data) { 16 | var calls = { 17 | child: function(key) { asserts.child.push(key); return calls }, 18 | update: function(data, callback) { 19 | asserts['updateData'] = data; 20 | callback(error); 21 | }, 22 | }; 23 | 24 | return calls; 25 | }; 26 | 27 | var callbacks = { 28 | errorCallback: function(error) { asserts['errorCallback'] = error }, 29 | successCallback: function(data) { asserts['successCallback'] = 'called' }, 30 | }; 31 | 32 | var subject = function(error, data) { 33 | return Update( 34 | mockAdapter(error, data), 35 | cardId, 36 | data, 37 | callbacks.errorCallback, 38 | callbacks.successCallback 39 | ); 40 | }; 41 | 42 | describe('Success', function() { 43 | var data = { 'something' : 'else' }; 44 | 45 | beforeEach(function() { 46 | subject(null, data) 47 | }); 48 | 49 | it('created child node', function() { 50 | expect(asserts.child[0]).toEqual('cards'); 51 | expect(asserts.child[1]).toEqual(cardId); 52 | }); 53 | 54 | it('passes data', function() { 55 | expect(asserts.updateData).toEqual(data); 56 | }); 57 | 58 | it('calls callback', function() { 59 | expect(asserts.successCallback).toEqual('called'); 60 | }); 61 | 62 | it('calls off to logger with correct args', function() { 63 | expect(Logger.notice.cards.updated).toBeCalledWith(cardId); 64 | }); 65 | }); 66 | 67 | describe('Error', function() { 68 | var error = 'Oops'; 69 | 70 | beforeEach(function() { 71 | subject(error, {}) 72 | }); 73 | 74 | it('calls callback with email', function() { 75 | expect(asserts.errorCallback).toEqual(error); 76 | }); 77 | 78 | it('calls off to logger with correct args', function() { 79 | expect(Logger.warn.cards.updateFail).toBeCalledWith(cardId, error); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /services/firebase_service/cards/create.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Assign = require('react/lib/Object.assign'); 4 | var Logger = require('../../../utilities/logger'); 5 | 6 | module.exports = function( 7 | adapter, 8 | userId, 9 | data, 10 | errorCallback, 11 | successCallback 12 | ) { 13 | var adaptedData = Assign({ userId: userId }, data); 14 | 15 | adapter.child('cards').push(adaptedData, function(error) { 16 | if (error) { 17 | Logger.warn.cards.createFail(adaptedData, error); 18 | errorCallback(error); 19 | } else { 20 | Logger.notice.cards.created(adaptedData); 21 | successCallback(adaptedData); 22 | } 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /services/firebase_service/cards/destroy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Logger = require('../../../utilities/logger'); 4 | 5 | module.exports = function( 6 | adapter, 7 | cardId, 8 | errorCallback, 9 | successCallback 10 | ) { 11 | adapter.child('cards').child(cardId).remove(function(error) { 12 | if (error) { 13 | Logger.warn.cards.destroyFail(cardId, error); 14 | errorCallback(error); 15 | } else { 16 | Logger.notice.cards.destroyed(cardId); 17 | successCallback(); 18 | } 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /services/firebase_service/cards/update.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Logger = require('../../../utilities/logger'); 4 | 5 | module.exports = function( 6 | adapter, 7 | cardId, 8 | data, 9 | errorCallback, 10 | successCallback 11 | ) { 12 | adapter.child('cards').child(cardId).update(data, function(error) { 13 | if (error) { 14 | Logger.warn.cards.updateFail(cardId, error); 15 | errorCallback(error); 16 | } else { 17 | Logger.notice.cards.updated(cardId); 18 | successCallback(); 19 | } 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /services/firebase_service/users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Create = require('./users/create'); 4 | var Destroy = require('./users/destroy'); 5 | var Find = require('./users/find'); 6 | var ResetPassword = require('./users/reset_password'); 7 | 8 | module.exports = { 9 | create: Create, 10 | destroy: Destroy, 11 | find: Find, 12 | resetPassword: ResetPassword, 13 | }; 14 | -------------------------------------------------------------------------------- /services/firebase_service/users/__tests__/create-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../create'); 4 | jest.dontMock('../../../../utilities/lockdown'); 5 | 6 | var Create = require('../create'); 7 | var Logger = require('../../../../utilities/logger'); 8 | 9 | describe('Create', function() { 10 | var asserts = {}; 11 | var email = __LOCKDOWN_KEY__ + '@example.com'; 12 | var password = 'password'; 13 | 14 | var mockAdapter = function(error, data) { 15 | return { 16 | createUser: function(params, callback) { callback(error, data); }, 17 | }; 18 | }; 19 | 20 | var callbacks = { 21 | emailTakenCallback: function(email) { asserts['emailTakenCallback'] = email }, 22 | invalidEmailCallback: function(email) { asserts['invalidEmailCallback'] = email }, 23 | errorCallback: function(error) { asserts['errorCallback'] = error }, 24 | successCallback: function(data) { asserts['successCallback'] = data }, 25 | lockdownCallback: function(error) { asserts['lockdownCallback'] = error } 26 | }; 27 | 28 | var subject = function(error, data, emailOverride) { 29 | return Create( 30 | mockAdapter(error, data), 31 | emailOverride || email, 32 | password, 33 | callbacks.emailTakenCallback, 34 | callbacks.invalidEmailCallback, 35 | callbacks.errorCallback, 36 | callbacks.successCallback, 37 | callbacks.lockdownCallback 38 | ); 39 | }; 40 | 41 | describe('Success', function() { 42 | var data = { uid: 123 }; 43 | 44 | beforeEach(function() { 45 | Logger.notice.users.created = jest.genMockFunction(); 46 | subject(null, data) 47 | }); 48 | 49 | it('calls callback with email', function() { 50 | expect(asserts.successCallback).toEqual(data); 51 | }); 52 | 53 | it('calls off to logger with correct args', function() { 54 | expect(Logger.notice.users.created).toBeCalledWith(email, password, data); 55 | }); 56 | }); 57 | 58 | describe('Email Taken', function() { 59 | var error = { code: 'EMAIL_TAKEN' }; 60 | 61 | beforeEach(function() { 62 | Logger.warn.users.emailTaken = jest.genMockFunction(); 63 | subject(error); 64 | }); 65 | 66 | it('calls callback with email', function() { 67 | expect(asserts.emailTakenCallback).toEqual(email); 68 | }); 69 | 70 | it('calls off to logger with correct args', function() { 71 | expect(Logger.warn.users.emailTaken) 72 | .toBeCalledWith(email, password, error); 73 | }); 74 | }); 75 | 76 | describe('Invalid Email', function() { 77 | var error = { code: 'INVALID_EMAIL' }; 78 | 79 | beforeEach(function() { 80 | Logger.warn.users.invalidEmail = jest.genMockFunction(); 81 | subject(error); 82 | }); 83 | 84 | it('calls callback with email', function() { 85 | expect(asserts.invalidEmailCallback).toEqual(email); 86 | }); 87 | 88 | it('calls off to logger with correct args', function() { 89 | expect(Logger.warn.users.invalidEmail) 90 | .toBeCalledWith(email, password, error); 91 | }); 92 | }); 93 | 94 | describe('Error', function() { 95 | var error = 'Oops'; 96 | 97 | beforeEach(function() { 98 | Logger.warn.users.createFail = jest.genMockFunction(); 99 | subject(error); 100 | }); 101 | 102 | it('calls callback with email', function() { 103 | expect(asserts.errorCallback).toEqual(error); 104 | }); 105 | 106 | it('calls off to logger with correct args', function() { 107 | expect(Logger.warn.users.createFail) 108 | .toBeCalledWith(email, password, error); 109 | }); 110 | }); 111 | 112 | describe('Access Denied', function() { 113 | var error = 'Still in Alpha so Access Denied.'; 114 | var localEmail = 'test@example.com'; 115 | 116 | beforeEach(function() { 117 | Logger.warn.users.accessDenied = jest.genMockFunction(); 118 | subject(error, null, localEmail); 119 | }); 120 | 121 | it('calls callback with email', function() { 122 | expect(asserts.lockdownCallback).toEqual(error); 123 | }); 124 | 125 | it('calls off to logger with correct args', function() { 126 | expect(Logger.warn.users.accessDenied) 127 | .toBeCalledWith(localEmail, password, error, __LOCKDOWN_KEY__); 128 | }); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /services/firebase_service/users/__tests__/destroy-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../destroy'); 4 | jest.dontMock('../../../../utilities/lockdown'); 5 | 6 | var Destroy = require('../destroy'); 7 | var Logger = require('../../../../utilities/logger'); 8 | 9 | describe('Destroy', function() { 10 | var asserts = {}; 11 | var email = __LOCKDOWN_KEY__ + '@example.com'; 12 | var password = 'password'; 13 | 14 | var mockAdapter = function(error, data) { 15 | return { 16 | removeUser: function(params, callback) { callback(error, data); }, 17 | }; 18 | }; 19 | 20 | var callbacks = { 21 | invalidUserCallback: function(email) { asserts['invalidUserCallback'] = email }, 22 | invalidPasswordCallback: function(email) { asserts['invalidPasswordCallback'] = email }, 23 | errorCallback: function(error) { asserts['errorCallback'] = error }, 24 | successCallback: function(data) { asserts['successCallback'] = data }, 25 | }; 26 | 27 | var subject = function(error, data, emailOverride) { 28 | return Destroy( 29 | mockAdapter(error, data), 30 | emailOverride || email, 31 | password, 32 | callbacks.invalidUserCallback, 33 | callbacks.invalidPasswordCallback, 34 | callbacks.errorCallback, 35 | callbacks.successCallback 36 | ); 37 | }; 38 | 39 | describe('Success', function() { 40 | var data = { uid: 123 }; 41 | 42 | beforeEach(function() { 43 | Logger.notice.users.destroyed = jest.genMockFunction(); 44 | subject(null, data) 45 | }); 46 | 47 | it('calls callback with email', function() { 48 | expect(asserts.successCallback).toEqual(data); 49 | }); 50 | 51 | it('calls off to logger with correct args', function() { 52 | expect(Logger.notice.users.destroyed).toBeCalledWith(email, password, data); 53 | }); 54 | }); 55 | 56 | describe('Invalid User', function() { 57 | var error = { code: 'INVALID_USER' }; 58 | 59 | beforeEach(function() { 60 | Logger.warn.users.invalidUser = jest.genMockFunction(); 61 | subject(error); 62 | }); 63 | 64 | it('calls callback with email', function() { 65 | expect(asserts.invalidUserCallback).toEqual(email); 66 | }); 67 | 68 | it('calls off to logger with correct args', function() { 69 | expect(Logger.warn.users.invalidUser) 70 | .toBeCalledWith(email, error); 71 | }); 72 | }); 73 | 74 | describe('Invalid Password', function() { 75 | var error = { code: 'INVALID_PASSWORD' }; 76 | 77 | beforeEach(function() { 78 | Logger.warn.users.invalidPassword = jest.genMockFunction(); 79 | subject(error); 80 | }); 81 | 82 | it('calls callback with email', function() { 83 | expect(asserts.invalidPasswordCallback).toEqual(email); 84 | }); 85 | 86 | it('calls off to logger with correct args', function() { 87 | expect(Logger.warn.users.invalidPassword) 88 | .toBeCalledWith(email, password, error); 89 | }); 90 | }); 91 | 92 | describe('Error', function() { 93 | var error = 'Oops'; 94 | 95 | beforeEach(function() { 96 | Logger.warn.users.destroyFail = jest.genMockFunction(); 97 | subject(error); 98 | }); 99 | 100 | it('calls callback with email', function() { 101 | expect(asserts.errorCallback).toEqual(error); 102 | }); 103 | 104 | it('calls off to logger with correct args', function() { 105 | expect(Logger.warn.users.destroyFail) 106 | .toBeCalledWith(email, password, error); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /services/firebase_service/users/__tests__/find.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../find'); 4 | jest.dontMock('../../../../utilities/logger'); 5 | 6 | var Find = require('../find'); 7 | var Logger = require('../../../../utilities/logger'); 8 | 9 | describe('Find', function() { 10 | var asserts = {}; 11 | var email = __LOCKDOWN_KEY__ + '@example.com'; 12 | var password = 'password'; 13 | 14 | var mockAdapter = function(error, data) { 15 | return { 16 | authWithPassword: function(params, callback) { callback(error, data); }, 17 | }; 18 | }; 19 | 20 | var callbacks = { 21 | errorCallback: function(error) { asserts['errorCallback'] = error }, 22 | successCallback: function(data) { asserts['successCallback'] = data } 23 | }; 24 | 25 | var subject = function(error, data) { 26 | return Find( 27 | mockAdapter(error, data), 28 | email, 29 | password, 30 | callbacks.errorCallback, 31 | callbacks.successCallback 32 | ); 33 | }; 34 | 35 | describe('Success', function() { 36 | var data = { uid: 123 }; 37 | 38 | beforeEach(function() { 39 | Logger.notice.users.found = jest.genMockFunction(); 40 | subject(null, data) 41 | }); 42 | 43 | it('calls callback with email', function() { 44 | expect(asserts.successCallback).toEqual(data); 45 | }); 46 | 47 | it('calls off to logger with correct args', function() { 48 | expect(Logger.notice.users.found).toBeCalledWith(email, password, data); 49 | }); 50 | }); 51 | 52 | describe('Error', function() { 53 | var error = 'Oops'; 54 | 55 | beforeEach(function() { 56 | Logger.warn.users.notFound = jest.genMockFunction(); 57 | subject(error); 58 | }); 59 | 60 | it('calls callback with email', function() { 61 | expect(asserts.errorCallback).toEqual(error); 62 | }); 63 | 64 | it('calls off to logger with correct args', function() { 65 | expect(Logger.warn.users.notFound) 66 | .toBeCalledWith(email, password, error); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /services/firebase_service/users/__tests__/reset_password.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../reset_password'); 4 | jest.dontMock('../../../../utilities/logger'); 5 | 6 | var ResetPassword = require('../reset_password'); 7 | var Logger = require('../../../../utilities/logger'); 8 | 9 | describe('ResetPassword', function() { 10 | var asserts = {}; 11 | var email = __LOCKDOWN_KEY__ + '@example.com'; 12 | var password = 'password'; 13 | 14 | var mockAdapter = function(error, data) { 15 | return { 16 | resetPassword: function(params, callback) { callback(error, data); } 17 | }; 18 | }; 19 | 20 | var callbacks = { 21 | invalidUserCallback: function(error) { asserts['invalidUserCallback'] = error }, 22 | errorCallback: function(error) { asserts['errorCallback'] = error }, 23 | successCallback: function(data) { asserts['successCallback'] = data } 24 | }; 25 | 26 | var subject = function(error, data) { 27 | return ResetPassword( 28 | mockAdapter(error, data), 29 | email, 30 | callbacks.invalidUserCallback, 31 | callbacks.errorCallback, 32 | callbacks.successCallback 33 | ); 34 | }; 35 | 36 | describe('Success', function() { 37 | var data = { uid: 123 }; 38 | 39 | beforeEach(function() { 40 | Logger.notice.users.passwordReset = jest.genMockFunction(); 41 | subject(null, data) 42 | }); 43 | 44 | it('calls callback with email', function() { 45 | expect(asserts.successCallback).toEqual(null); 46 | }); 47 | 48 | it('calls off to logger with correct args', function() { 49 | expect(Logger.notice.users.passwordReset).toBeCalledWith(email); 50 | }); 51 | }); 52 | 53 | describe('Invalid Email', function() { 54 | var error = { code: 'INVALID_USER' }; 55 | 56 | beforeEach(function() { 57 | Logger.warn.users.invalidUser = jest.genMockFunction(); 58 | subject(error); 59 | }); 60 | 61 | it('calls callback with email', function() { 62 | expect(asserts.invalidUserCallback).toEqual(email); 63 | }); 64 | 65 | it('calls off to logger with correct args', function() { 66 | expect(Logger.warn.users.invalidUser) 67 | .toBeCalledWith(email, error); 68 | }); 69 | }); 70 | 71 | describe('Error', function() { 72 | var error = 'Oops'; 73 | 74 | beforeEach(function() { 75 | Logger.warn.users.passwordResetFail = jest.genMockFunction(); 76 | subject(error); 77 | }); 78 | 79 | it('calls callback with email', function() { 80 | expect(asserts.errorCallback).toEqual(error); 81 | }); 82 | 83 | it('calls off to logger with correct args', function() { 84 | expect(Logger.warn.users.passwordResetFail) 85 | .toBeCalledWith(email, error); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /services/firebase_service/users/create.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Logger = require('../../../utilities/logger'); 4 | var Lockdown = require('../../../utilities/lockdown'); 5 | 6 | module.exports = function( 7 | adapter, 8 | email, 9 | password, 10 | emailTakenCallback, 11 | invalidEmailCallback, 12 | errorCallback, 13 | successCallback, 14 | lockdownCallback 15 | ) { 16 | if (!Lockdown.opened(email)) { 17 | var message = 'Still in Alpha so Access Denied.'; 18 | 19 | Logger.warn.users.accessDenied( 20 | email, 21 | password, 22 | message, 23 | __LOCKDOWN_KEY__ 24 | ); 25 | 26 | lockdownCallback(message); 27 | 28 | return; 29 | }; 30 | 31 | adapter.createUser({ 32 | email: email, 33 | password: password 34 | }, function(error, data) { 35 | if (error) { 36 | switch (error.code) { 37 | case 'EMAIL_TAKEN': 38 | Logger.warn.users.emailTaken(email, password, error); 39 | emailTakenCallback(email); break; 40 | case 'INVALID_EMAIL': 41 | Logger.warn.users.invalidEmail(email, password, error); 42 | invalidEmailCallback(email); break; 43 | default: 44 | Logger.warn.users.createFail(email, password, error); 45 | errorCallback(error); 46 | } 47 | } else { 48 | Logger.notice.users.created(email, password, data); 49 | successCallback(data); 50 | } 51 | }); 52 | }; 53 | 54 | -------------------------------------------------------------------------------- /services/firebase_service/users/destroy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Logger = require('../../../utilities/logger'); 4 | 5 | module.exports = function( 6 | adapter, 7 | email, 8 | password, 9 | invalidUserCallback, 10 | invalidPasswordCallback, 11 | errorCallback, 12 | successCallback 13 | ) { 14 | adapter.removeUser({ 15 | email: email, 16 | password: password 17 | }, function(error, data) { 18 | if (error) { 19 | switch (error.code) { 20 | case 'INVALID_USER': 21 | Logger.warn.users.invalidUser(email, error); 22 | invalidUserCallback(email); break; 23 | case 'INVALID_PASSWORD': 24 | Logger.warn.users.invalidPassword(email, password, error); 25 | invalidPasswordCallback(email); break; 26 | default: 27 | Logger.warn.users.destroyFail(email, password, error); 28 | errorCallback(error); 29 | } 30 | } else { 31 | Logger.notice.users.destroyed(email, password, data); 32 | successCallback(data); 33 | } 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /services/firebase_service/users/find.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Logger = require('../../../utilities/logger'); 4 | 5 | module.exports = function( 6 | adapter, 7 | email, 8 | password, 9 | errorCallback, 10 | successCallback 11 | ) { 12 | adapter.authWithPassword({ 13 | email: email, 14 | password: password 15 | }, function(error, data) { 16 | if (error) { 17 | Logger.warn.users.notFound(email, password, error); 18 | errorCallback(error); 19 | } else { 20 | Logger.notice.users.found(email, password, data); 21 | successCallback(data); 22 | } 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /services/firebase_service/users/reset_password.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Logger = require('../../../utilities/logger'); 4 | 5 | module.exports = function( 6 | adapter, 7 | email, 8 | invalidUserCallback, 9 | errorCallback, 10 | successCallback 11 | ) { 12 | adapter.resetPassword({ 13 | email: email 14 | }, function(error, data) { 15 | if (error) { 16 | switch (error.code) { 17 | case 'INVALID_USER': 18 | Logger.warn.users.invalidUser(email, error); 19 | invalidUserCallback(email); break; 20 | default: 21 | Logger.warn.users.passwordResetFail(email, error); 22 | errorCallback(error); 23 | } 24 | } else { 25 | Logger.notice.users.passwordReset(email); 26 | successCallback(); 27 | } 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /services/mailchimp_service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var MailchimpDAO = require('../dao/mailchimp_dao'); 4 | var Logger = require('../utilities/logger'); 5 | 6 | var subscribe = function( 7 | adapter, 8 | email, 9 | errorCallback, 10 | successCallback 11 | ) { 12 | MailchimpDAO.subscribe(adapter, email) 13 | .then(function(data) { 14 | if (data.result === 'success') { 15 | Logger.notice.users.subscribe(email, data); 16 | successCallback(data); 17 | } else { 18 | Logger.warn.users.subscribe(email, data); 19 | errorCallback(data); 20 | } 21 | }); 22 | }; 23 | 24 | module.exports = { 25 | subscribe: subscribe, 26 | }; 27 | -------------------------------------------------------------------------------- /stores/__tests__/cards_store-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.autoMockOff(); 4 | jest.mock('../../components/app'); 5 | 6 | var asserts = { 7 | onName: [] 8 | }; 9 | 10 | var snapshot = { 11 | key: function() { return 'key'; }, 12 | val: function() { return { 'something' : 'else' }; } 13 | }; 14 | 15 | jest.setMock('../../components/app', { 16 | firebase: function(endpoint) { 17 | asserts['endpoint'] = endpoint; 18 | 19 | var reference = { 20 | child: function(name) { 21 | asserts['childName'] = name; 22 | return reference; 23 | }, 24 | orderByChild: function(name) { 25 | asserts['orderByChild'] = name; 26 | return reference; 27 | }, 28 | startAt: function(name) { 29 | asserts['startAt'] = name; 30 | return reference; 31 | }, 32 | endAt: function(name) { 33 | asserts['endAt'] = name; 34 | return reference; 35 | }, 36 | once: function(name, callback) { 37 | asserts['onceName'] = name; 38 | callback(snapshot); 39 | }, 40 | on: function(name, callback) { 41 | asserts['onName'].push(name); 42 | callback(snapshot); 43 | }, 44 | }; 45 | 46 | return reference; 47 | } 48 | }) 49 | 50 | describe('CardsStore', function() { 51 | var CardsAction = require('../../actions/cards_action'); 52 | var CardsStore = require('../cards_store'); 53 | 54 | var called = false; 55 | var changeListener = function() { 56 | called = true; 57 | }; 58 | 59 | beforeEach(function() { 60 | CardsStore.addChangeListener(changeListener) ; 61 | }); 62 | 63 | it('sets firebase endpoint', function() { 64 | expect(asserts.endpoint).toEqual('cards'); 65 | }); 66 | 67 | describe('#find', function() { 68 | var id = '-JnrJpdjoBsiL-aRq16l'; 69 | 70 | beforeEach(function() { 71 | CardsAction.find(id); 72 | }); 73 | 74 | it('calls firebase child node with expected name', function() { 75 | expect(asserts.childName).toEqual(id); 76 | }); 77 | 78 | it('calls firebase on with expected name', function() { 79 | expect(asserts.onceName).toEqual('value'); 80 | }); 81 | 82 | it('returns card', function() { 83 | expect(CardsStore.card()).toEqual({ 'something' : 'else' }); 84 | }); 85 | }); 86 | 87 | describe('#list', function() { 88 | beforeEach(function() { 89 | CardsAction.list('123'); 90 | }); 91 | 92 | it('calls firebase with expected order key', function() { 93 | expect(asserts.orderByChild).toEqual('userId'); 94 | }); 95 | 96 | it('calls firebase with expected start at', function() { 97 | expect(asserts.startAt).toEqual('123'); 98 | }); 99 | 100 | it('calls firebase with expected end at', function() { 101 | expect(asserts.endAt).toEqual('123'); 102 | }); 103 | 104 | it('calls firebase on with expected name', function() { 105 | expect(asserts.onName[0]).toEqual('child_added'); 106 | expect(asserts.onName[1]).toEqual('child_removed'); 107 | }); 108 | 109 | it('returns list', function() { 110 | expect(CardsStore.list()).toEqual({}); 111 | }); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /stores/cards_store.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var App = require('../components/app'); 4 | var Dispatcher = require('../utilities/dispatcher'); 5 | var Constants = require('../constants/cards_constants'); 6 | var EventEmitter = require('events').EventEmitter; 7 | var Assign = require('react/lib/Object.assign'); 8 | 9 | var cardsRef = App.firebase('cards'); 10 | 11 | var _card = {}; 12 | var _cards = {}; 13 | 14 | var Store = Assign({}, EventEmitter.prototype, { 15 | card: function() { 16 | return _card; 17 | }, 18 | list: function() { 19 | return _cards; 20 | }, 21 | emitChange: function() { 22 | this.emit('change'); 23 | }, 24 | addChangeListener: function(callback) { 25 | this.on('change', callback); 26 | }, 27 | removeChangeListener: function(callback) { 28 | this.removeListener('change', callback); 29 | } 30 | }); 31 | 32 | function find(id) { 33 | cardsRef.child(id).once('value', function(snapshot) { 34 | _card = snapshot.val(); 35 | 36 | Store.emitChange(); 37 | }); 38 | } 39 | 40 | function list(userId) { 41 | cardsRef.orderByChild('userId').startAt(userId).endAt(userId).on('child_added', function(snapshot) { 42 | if (!_cards[snapshot.key()]) { 43 | _cards[snapshot.key()] = snapshot.val(); 44 | } 45 | 46 | Store.emitChange(); 47 | }); 48 | 49 | cardsRef.on('child_removed', function(oldChildSnapshot) { 50 | if (_cards[oldChildSnapshot.key()]) { 51 | delete _cards[oldChildSnapshot.key()]; 52 | } 53 | 54 | Store.emitChange(); 55 | }); 56 | } 57 | 58 | Dispatcher.register(function(action) { 59 | switch(action.actionType) { 60 | case Constants.CARDS_FIND: 61 | find(action.id); 62 | break; 63 | case Constants.CARDS_LIST: 64 | list(action.userId); 65 | break; 66 | default: 67 | } 68 | }); 69 | 70 | module.exports = Store; 71 | -------------------------------------------------------------------------------- /styles/add-card.scss: -------------------------------------------------------------------------------- 1 | .card-detail-empty-image { 2 | @include rounded-corners; 3 | background-color: $subtle-color; 4 | text-align: center; 5 | height: 300px; 6 | line-height: 300px; 7 | margin-bottom: $form-element-gap; 8 | } 9 | 10 | .add-picture { 11 | @include rounded-corners; 12 | border: $border-size solid $subtle-border-color; 13 | display: block; 14 | text-align: center; 15 | padding: 0.938rem; 16 | 17 | &:hover { 18 | background-color: $subtle-color; 19 | text-decoration: none; 20 | } 21 | } -------------------------------------------------------------------------------- /styles/alert-messages.scss: -------------------------------------------------------------------------------- 1 | .alert { 2 | padding: 0.938rem; 3 | margin-bottom: 1.875rem; 4 | } 5 | 6 | .alert-success { 7 | background-color: $success-color; 8 | color: $success-color-text; 9 | } 10 | 11 | .alert-info { 12 | background-color: $info-color; 13 | color: $info-color-text; 14 | } 15 | 16 | .alert-danger { 17 | background-color: $danger-color; 18 | color: $danger-color-text; 19 | } 20 | 21 | .alert-null { 22 | display: none; 23 | } 24 | -------------------------------------------------------------------------------- /styles/buttons.scss: -------------------------------------------------------------------------------- 1 | .btn { 2 | @include rounded-corners; 3 | transition: background-color 0.4s ease; 4 | background-color: transparent; 5 | color: inherit; 6 | display: inline-block; 7 | line-height: $form-element-height; 8 | padding-left: 1.25rem; 9 | padding-right: 1.25rem; 10 | text-decoration: none; 11 | 12 | &:hover { 13 | text-decoration: none; 14 | } 15 | 16 | &:active { 17 | box-shadow: inset 0px 1px 4px 0px rgba(0,0,0,0.25); 18 | } 19 | } 20 | 21 | .btn+.btn { 22 | margin-left: 10px; 23 | } 24 | 25 | .btn-mini { 26 | font-size: 0.813rem; 27 | line-height: $form-element-height*.75; 28 | padding-left: 0.625rem; 29 | padding-right: 0.625rem; 30 | } 31 | 32 | .btn-default { 33 | border: $border-size solid $subtle-border-color; 34 | 35 | &:hover { 36 | background-color: $subtle-color; 37 | } 38 | } 39 | 40 | .btn-primary { 41 | border: $border-size solid $main-color; 42 | 43 | &:hover { 44 | background-color: $main-color; 45 | color: $invert-color; 46 | } 47 | } 48 | 49 | @media only screen and (max-width: 480px) { 50 | .btn+.btn { 51 | margin-top: 10px; 52 | margin-left: 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /styles/card-details.scss: -------------------------------------------------------------------------------- 1 | .card-detail-gallery { 2 | background-color: $subtle-color; 3 | height: 360px; 4 | } 5 | 6 | .card-detail-info { 7 | margin-top: -3.125rem; 8 | margin-right: auto; 9 | margin-left: auto; 10 | max-width: 600px; 11 | } 12 | 13 | .card-detail-name { 14 | font-size: 1.5rem; 15 | margin-right: 3.438rem; 16 | margin-bottom: 1.25rem; 17 | } 18 | 19 | .card-detail-price { 20 | background-color: rgba(#000000, .5); 21 | color: white; 22 | font-size: 1.875rem; 23 | height: 3.125rem; 24 | line-height: 3.125rem; 25 | margin-bottom: 1.875rem; 26 | padding-left: 0.938rem; 27 | padding-right: 0.938rem; 28 | } 29 | 30 | .card-detail-features { 31 | font-size: 0; 32 | list-style-type: none; 33 | margin-bottom: 1.25rem; 34 | padding-left: 0; 35 | } 36 | 37 | .card-detail-feature { 38 | font-size: 0.875rem; 39 | display: inline-block; 40 | width: 50%; 41 | 42 | & > span { 43 | font-weight: bold; 44 | text-transform: uppercase; 45 | } 46 | } 47 | 48 | .card-detail-descrip { 49 | font-size: 0.875rem; 50 | line-height: 1.375rem; 51 | } 52 | 53 | @media only screen and (max-width: 480px) { 54 | .card-detail-info { 55 | padding-right: 30px; 56 | padding-bottom: 30px; 57 | padding-left: 30px; 58 | } 59 | } -------------------------------------------------------------------------------- /styles/cards.scss: -------------------------------------------------------------------------------- 1 | .cards-filter { 2 | text-align: center; 3 | margin-bottom: 0.938rem; 4 | 5 | .filter { 6 | @include rounded-corners; 7 | transition: background-color 0.4s ease; 8 | display: inline-block; 9 | font-size: 0.813rem; 10 | border: $border-size solid $subtle-border-color; 11 | padding: 0.313rem 0.938rem; 12 | margin-left: 0.313rem; 13 | margin-right: 0.313rem; 14 | 15 | &:hover { 16 | background-color: $subtle-color; 17 | text-decoration: none; 18 | } 19 | } 20 | } 21 | 22 | .add-card { 23 | margin-bottom: 0.625rem; 24 | text-align: center; 25 | 26 | .btn { 27 | max-width: 250px; 28 | } 29 | } 30 | 31 | .cards-empty { 32 | text-align: center; 33 | margin-top: 3.125rem; 34 | 35 | img { 36 | height: 173px; 37 | width: 125px; 38 | } 39 | } 40 | 41 | .cards-empty-message { 42 | font-size: 1.25rem; 43 | margin-top: 1.875rem; 44 | margin-bottom: 1.875rem; 45 | } 46 | 47 | .cards { 48 | padding: 0.625rem; 49 | 50 | &:after { 51 | @include clearfix; 52 | } 53 | } 54 | 55 | .card { 56 | float: left; 57 | width: 25%; 58 | } 59 | 60 | .card-inner { 61 | display: block; 62 | margin: 0.625rem; 63 | } 64 | 65 | .card-image img { 66 | @include top-rounded-corners; 67 | display: block; 68 | } 69 | 70 | .card-content { 71 | @include bottom-rounded-corners; 72 | border: $border-size solid $subtle-border-color; 73 | border-top: 0; 74 | padding: 1.875rem 1.25rem; 75 | } 76 | 77 | .card-name a { 78 | @include text-overflow; 79 | color: $link-color; 80 | font-size: 1.125rem; 81 | } 82 | 83 | .card-heading2 { 84 | font-size: 0.813rem; 85 | margin-bottom: 0.625rem; 86 | } 87 | 88 | .card-heading3 { 89 | font-size: 1.5rem; 90 | font-weight: bold; 91 | } 92 | 93 | .card-text { 94 | font-size: 0.875rem; 95 | margin-top: 0.313rem; 96 | } 97 | 98 | @media only screen and (max-width: 480px) { 99 | .add-card { 100 | padding-left: 10px; 101 | padding-right: 10px; 102 | 103 | .btn { 104 | width: 100%; 105 | } 106 | } 107 | 108 | .card { 109 | float: none; 110 | width: 100%; 111 | } 112 | } -------------------------------------------------------------------------------- /styles/footer.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChuckJHardy/ReactJS-Example/9ca31313c95d916a7026588ea00798f439afd1e3/styles/footer.scss -------------------------------------------------------------------------------- /styles/forms.scss: -------------------------------------------------------------------------------- 1 | form { 2 | text-align: left; 3 | } 4 | 5 | .form-action { 6 | clear: left; 7 | } 8 | 9 | 10 | 11 | .form-field { 12 | margin-bottom: $form-element-gap; 13 | } 14 | 15 | label { 16 | display: block; 17 | margin-bottom: 10px; 18 | } 19 | 20 | input[type="text"], input[type="password"], select, textarea { 21 | -webkit-transition: all 0.30s ease-in-out; 22 | -moz-transition: all 0.30s ease-in-out; 23 | -ms-transition: all 0.30s ease-in-out; 24 | -o-transition: all 0.30s ease-in-out; 25 | 26 | &:focus { 27 | box-shadow: 0 0 5px rgba(243, 156, 18, 1); 28 | border: 1px solid $main-color; 29 | } 30 | } 31 | 32 | input[type="text"], input[type="password"] { 33 | @include form-styles; 34 | width: 100%; 35 | padding-left: $form-element-padding; 36 | padding-right: $form-element-padding; 37 | } 38 | 39 | .checkbox, .radio { 40 | margin-bottom: 5px; 41 | } 42 | 43 | .checkbox-label, .radio-label { 44 | margin-left: 5px; 45 | } 46 | 47 | .select-menu { 48 | @include form-styles; 49 | background: transparent url(../assets/images/select-menu-arrow.png) 96% 50% no-repeat; 50 | background-size: 14px 9px; 51 | 52 | select { 53 | background: transparent; 54 | border: 0; 55 | border-radius: 0; 56 | line-height: $form-element-height; 57 | height: $form-element-height; 58 | -webkit-appearance: none; 59 | padding-left: $form-element-padding; 60 | padding-right: $form-element-padding; 61 | width: 100%; 62 | } 63 | } 64 | 65 | textarea { 66 | @include form-styles; 67 | height: auto; 68 | width: 100%; 69 | padding: $form-element-padding; 70 | } 71 | 72 | .forgot-link { 73 | color: #666666; 74 | font-size: 0.813rem; 75 | } 76 | 77 | .form-field-error { 78 | label { 79 | color: $danger-color-text; 80 | } 81 | input[type="text"] { 82 | border: $border-size solid $danger-color-text; 83 | } 84 | } 85 | 86 | .form-error-message { 87 | color: $danger-color-text; 88 | font-size: 0.75rem; 89 | margin-top: 5px; 90 | } 91 | 92 | @media only screen and (max-width: 480px) { 93 | .form-action .btn { 94 | width: 100%; 95 | } 96 | } -------------------------------------------------------------------------------- /styles/general.scss: -------------------------------------------------------------------------------- 1 | * { 2 | @include border-box; 3 | outline: none; 4 | } 5 | 6 | html { 7 | font-size: 16px; 8 | } 9 | 10 | a, a:visited { 11 | color: inherit; 12 | text-decoration: none; 13 | 14 | &:hover { 15 | text-decoration: underline; 16 | } 17 | } 18 | 19 | body { 20 | @include font-book; 21 | -webkit-font-smoothing: antialiased; 22 | text-rendering: optimizeLegibility; 23 | -moz-osx-font-smoothing: grayscale; 24 | -moz-font-feature-settings: "liga", "kern"; 25 | color: $font-color; 26 | } 27 | 28 | img { 29 | max-width: 100%; 30 | } 31 | 32 | @media only screen and (max-width: 480px) { 33 | html { 34 | font-size: 14px; 35 | } 36 | } -------------------------------------------------------------------------------- /styles/helpers.scss: -------------------------------------------------------------------------------- 1 | .pull-right { 2 | float: right !important; 3 | } -------------------------------------------------------------------------------- /styles/hero.scss: -------------------------------------------------------------------------------- 1 | .hero-container { 2 | text-align: center; 3 | } 4 | 5 | .hero-title { 6 | font-size: 2rem; 7 | letter-spacing: 1px; 8 | line-height: 2.625rem; 9 | margin: 0 auto 1.25rem auto; 10 | width: 500px; 11 | } 12 | 13 | .hero-logo { 14 | width: 91px; 15 | margin-bottom: 1.25rem; 16 | } 17 | 18 | .hero-form { 19 | width: 250px; 20 | margin: 0 auto; 21 | } 22 | 23 | @media only screen and (max-width: 480px) { 24 | .hero-container { 25 | padding: 30px; 26 | } 27 | 28 | .hero-title, .hero-form, .hero-form .btn { 29 | width: 100%; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /styles/layout.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-left: auto; 3 | margin-right: auto; 4 | padding: 0.938rem; 5 | max-width: 900px; 6 | } 7 | 8 | .col-6 { 9 | padding-left: 0.938rem; 10 | padding-right: 0.938rem; 11 | float: left; 12 | width: 50%; 13 | } 14 | 15 | @media only screen and (max-width: 480px) { 16 | .col-6 { 17 | float: none; 18 | width: 100%; 19 | } 20 | } -------------------------------------------------------------------------------- /styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "variables.scss"; 2 | @import "mixins.scss"; 3 | @import "general.scss"; 4 | 5 | /*Components*/ 6 | @import "layout.scss"; 7 | @import "navigation.scss"; 8 | @import "page-header.scss"; 9 | @import "hero.scss"; 10 | @import "cards.scss"; 11 | @import "add-card.scss"; 12 | @import "card-details.scss"; 13 | @import "forms.scss"; 14 | @import "buttons.scss"; 15 | @import "alert-messages.scss"; 16 | @import "footer.scss"; 17 | @import "helpers.scss"; -------------------------------------------------------------------------------- /styles/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin flexbox-center-all { 2 | display: -webkit-flex; 3 | display: flex; 4 | -webkit-flex-direction: row; 5 | flex-direction: row; 6 | -webkit-align-items: center; 7 | align-items: center; 8 | -webkit-justify-content: center; 9 | justify-content: center; 10 | } 11 | 12 | @mixin border-box { 13 | -webkit-box-sizing: border-box; 14 | -moz-box-sizing: border-box; 15 | -ms-box-sizing: border-box; 16 | box-sizing: border-box; 17 | } 18 | 19 | @mixin font-book { 20 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 21 | font-weight: 300; 22 | } 23 | 24 | @mixin rounded-corners { 25 | -webkit-border-radius: 3px; 26 | -moz-border-radius: 3px; 27 | border-radius: 3px; 28 | } 29 | 30 | @mixin top-rounded-corners { 31 | -webkit-border-top-left-radius: 3px; 32 | -webkit-border-top-right-radius: 3px; 33 | -moz-border-radius-topleft: 3px; 34 | -moz-border-radius-topright: 3px; 35 | border-top-left-radius: 3px; 36 | border-top-right-radius: 3px; 37 | } 38 | 39 | @mixin bottom-rounded-corners { 40 | -webkit-border-bottom-right-radius: 3px; 41 | -webkit-border-bottom-left-radius: 3px; 42 | -moz-border-radius-bottomright: 3px; 43 | -moz-border-radius-bottomleft: 3px; 44 | border-bottom-right-radius: 3px; 45 | border-bottom-left-radius: 3px; 46 | } 47 | 48 | @mixin form-styles { 49 | @include rounded-corners; 50 | border: $border-size solid $subtle-border-color; 51 | height: $form-element-height; 52 | } 53 | 54 | @mixin hide-text { 55 | text-indent: 100%; 56 | white-space: nowrap; 57 | overflow: hidden; 58 | } 59 | 60 | @mixin text-overflow { 61 | text-overflow: ellipsis; 62 | white-space: nowrap; 63 | overflow: hidden; 64 | } 65 | 66 | @mixin clearfix { 67 | content: ""; 68 | display: table; 69 | clear: both; 70 | } -------------------------------------------------------------------------------- /styles/navigation.scss: -------------------------------------------------------------------------------- 1 | .main-header { 2 | padding: 0.938rem 1.875rem; 3 | border-bottom: $border-size solid $subtle-border-color; 4 | 5 | &:after { 6 | @include clearfix; 7 | } 8 | } 9 | 10 | .main-logo { 11 | @include hide-text; 12 | background: transparent url(../assets/images/logo-main.png) no-repeat center center; 13 | background-size: 173px 48px; 14 | display: inline-block; 15 | float: left; 16 | margin: 0; 17 | width: 173px; 18 | height: 48px; 19 | } 20 | 21 | .header-text { 22 | margin-right: 1.25rem; 23 | } 24 | 25 | nav { 26 | float: right; 27 | margin-top: 3px; 28 | } -------------------------------------------------------------------------------- /styles/page-header.scss: -------------------------------------------------------------------------------- 1 | .page-header { 2 | background-color: $subtle-color; 3 | border-bottom: $border-size solid $subtle-border-color; 4 | color: $subtle-text-color; 5 | font-size: 1.75rem; 6 | letter-spacing: 1px; 7 | margin-bottom: 1.875rem; 8 | padding-top: 3.125rem; 9 | padding-bottom: 3.125rem; 10 | text-align: center; 11 | } -------------------------------------------------------------------------------- /styles/variables.scss: -------------------------------------------------------------------------------- 1 | $font-color: #333333; 2 | 3 | $main-color: #f39c12; 4 | $invert-color: #ffffff; 5 | 6 | $link-color: #27ae60; 7 | 8 | $subtle-color: #ecf0f1; 9 | $subtle-border-color: #dddddd; 10 | $subtle-text-color: #797979; 11 | 12 | $success-color: #dff0d8; 13 | $success-color-text: #3c763d; 14 | 15 | $danger-color: #f2dede; 16 | $danger-color-text: #a94442; 17 | 18 | $info-color: #d9edf7; 19 | $info-color-text: #31708f; 20 | 21 | $form-element-gap: 0.938em; 22 | $form-element-height: 2.5rem; 23 | $form-element-padding: 0.938rem; 24 | 25 | $border-size: 1px; -------------------------------------------------------------------------------- /support/google_analytics_tracking.js: -------------------------------------------------------------------------------- 1 | module.exports = function(show) { 2 | if (show) { 3 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 4 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 5 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 6 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 7 | 8 | ga('create', __GOOGLE_ANALYTICS_KEY__, 'auto'); 9 | ga('send', 'pageview'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /support/stub_router_context.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Assign = require('react/lib/Object.assign'); 5 | 6 | var RouterStub = function() {}; 7 | var Stubber = function(stubs) { 8 | Assign(RouterStub, { 9 | makePath: function() {}, 10 | makeHref: function() {}, 11 | transitionTo: function() {}, 12 | replaceWith: function() {}, 13 | goBack: function() {}, 14 | getCurrentPath: function() {}, 15 | getCurrentRoutes: function() {}, 16 | getCurrentPathname: function() {}, 17 | getCurrentParams: function() {}, 18 | getCurrentQuery: function() { return {}; }, 19 | isActive: function() {}, 20 | getRouteAtDepth: function() {}, 21 | setRouteComponentAtDepth: function() {} 22 | }, stubs); 23 | 24 | return RouterStub; 25 | }; 26 | 27 | var Wrapper = function(Component, props, stubs, callback) { 28 | callback = callback || function() {}; 29 | 30 | return React.createClass({ 31 | childContextTypes: { 32 | router: React.PropTypes.func, 33 | routeDepth: React.PropTypes.number 34 | }, 35 | 36 | getChildContext: function() { 37 | return { 38 | router: Stubber(stubs), 39 | routeDepth: 0 40 | }; 41 | }, 42 | 43 | componentDidMount: function() { 44 | callback(this.refs.component) 45 | }, 46 | 47 | render: function() { 48 | return ; 49 | } 50 | }); 51 | }; 52 | 53 | module.exports = { 54 | stubber: Stubber, 55 | wrapper: Wrapper 56 | }; 57 | -------------------------------------------------------------------------------- /utilities/__tests__/dispatcher-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.autoMockOff(); 4 | 5 | describe('Dispatcher', function() { 6 | var Dispatcher = require('../dispatcher'); 7 | 8 | it('sends actions to subscribers', function() { 9 | var listener = jest.genMockFunction(); 10 | Dispatcher.register(listener); 11 | 12 | var payload = {}; 13 | Dispatcher.dispatch(payload); 14 | expect(listener.mock.calls.length).toBe(1); 15 | expect(listener.mock.calls[0][0]).toBe(payload); 16 | }); 17 | 18 | it('waits with chained dependencies properly', function() { 19 | var payload = {}; 20 | 21 | var listener1Done = false; 22 | var listener1 = function() { 23 | Dispatcher.waitFor([index2, index4]); 24 | // Second, third, and fourth listeners should have now been called 25 | expect(listener2Done).toBe(true); 26 | expect(listener3Done).toBe(true); 27 | expect(listener4Done).toBe(true); 28 | listener1Done = true; 29 | }; 30 | 31 | Dispatcher.register(listener1); 32 | 33 | var listener2Done = false; 34 | var listener2 = function() { 35 | Dispatcher.waitFor([index3]); 36 | expect(listener3Done).toBe(true); 37 | listener2Done = true; 38 | }; 39 | var index2 = Dispatcher.register(listener2); 40 | 41 | var listener3Done = false; 42 | var listener3 = function() { 43 | listener3Done = true; 44 | }; 45 | var index3 = Dispatcher.register(listener3); 46 | 47 | var listener4Done = false; 48 | var listener4 = function() { 49 | Dispatcher.waitFor([index3]); 50 | expect(listener3Done).toBe(true); 51 | listener4Done = true; 52 | }; 53 | var index4 = Dispatcher.register(listener4); 54 | 55 | runs(function() { 56 | Dispatcher.dispatch(payload); 57 | }); 58 | 59 | waitsFor(function() { 60 | return listener1Done; 61 | }, 'Not all subscribers were properly called', 500); 62 | 63 | runs(function() { 64 | expect(listener1Done).toBe(true); 65 | expect(listener2Done).toBe(true); 66 | expect(listener3Done).toBe(true); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /utilities/__tests__/lockdown-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../lockdown'); 4 | 5 | describe('Lockdown', function() { 6 | var Lockdown = require('../lockdown'); 7 | 8 | it('with email that is allowed access', function() { 9 | var email = 'lockdown-test-key@example.com'; 10 | expect(Lockdown.opened(email)).toEqual(true); 11 | }); 12 | 13 | it('with email that is not allowed access', function() { 14 | var email = 'test@example.com'; 15 | expect(Lockdown.opened(email)).toEqual(false); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /utilities/__tests__/logger-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../logger'); 4 | 5 | describe('Logger', function() { 6 | var Logger = require('../logger'); 7 | 8 | describe('.notice', function() { 9 | it('returns function', function() { 10 | expect(Logger.notice).toBeDefined(); 11 | }); 12 | }); 13 | 14 | describe('.warn', function() { 15 | it('returns function', function() { 16 | expect(Logger.warn).toBeDefined(); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /utilities/dispatcher.js: -------------------------------------------------------------------------------- 1 | var Dispatcher = require('flux').Dispatcher; 2 | 3 | module.exports = new Dispatcher(); 4 | -------------------------------------------------------------------------------- /utilities/lockdown.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | opened: function(email) { 5 | var regex = new RegExp(__LOCKDOWN_KEY__); 6 | return !!email.match(regex); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /utilities/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Notice = require('./logger/notice'); 4 | var Warn = require('./logger/warn'); 5 | 6 | module.exports = { 7 | notice: Notice, 8 | warn: Warn 9 | }; 10 | -------------------------------------------------------------------------------- /utilities/logger/__tests__/notice-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../notice'); 4 | 5 | var Notice = require('../notice'); 6 | 7 | describe('Notice', function() { 8 | it('cards is defined', function() { 9 | expect(Notice.cards).toBeDefined(); 10 | }); 11 | 12 | it('users is defined', function() { 13 | expect(Notice.users).toBeDefined(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /utilities/logger/__tests__/warn-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../warn'); 4 | jest.dontMock('../runner'); 5 | 6 | var Warn = require('../warn'); 7 | 8 | describe('Warn', function() { 9 | var Airbrake = require('../airbrake'); 10 | 11 | var email = 'test@example.com'; 12 | var error = 'Oops'; 13 | 14 | beforeEach(function() { 15 | console.groupCollapsed = jest.genMockFunction(); 16 | console.log = jest.genMockFunction(); 17 | console.groupEnd = jest.genMockFunction(); 18 | }); 19 | 20 | it('cards is defined', function() { 21 | expect(Warn.cards).toBeDefined(); 22 | }); 23 | 24 | it('general is defined', function() { 25 | expect(Warn.general).toBeDefined(); 26 | }); 27 | 28 | it('users is defined', function() { 29 | expect(Warn.users).toBeDefined(); 30 | }); 31 | 32 | describe('#general', function() { 33 | var name = 'general-error'; 34 | var params = { 35 | email: email 36 | }; 37 | 38 | beforeEach(function() { 39 | Warn.general(name, error, params, true); 40 | }); 41 | 42 | it('pushes to airbrake', function() { 43 | expect(Airbrake).toBeCalledWith(error, name, params); 44 | }); 45 | 46 | it('outputs expected logs', function() { 47 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ General - ' + name); 48 | expect(console.log.mock.calls[0]).toEqual(['-> Params: ', params]); 49 | expect(console.log.mock.calls[1]).toEqual(['-> Error: ', error]); 50 | expect(console.groupEnd).toBeCalled(); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /utilities/logger/airbrake.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(error, component, params) { 4 | window.airbrake.push({ 5 | error: error, 6 | context: { component: component }, 7 | environment: { navigator_vendor: window.navigator.vendor }, 8 | params: params 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /utilities/logger/notice.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Cards = require('./notice/cards'); 4 | var Users = require('./notice/users'); 5 | 6 | module.exports = { 7 | cards: Cards, 8 | users: Users, 9 | }; 10 | -------------------------------------------------------------------------------- /utilities/logger/notice/__tests__/cards-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../cards'); 4 | jest.dontMock('../../runner'); 5 | 6 | var Cards = require('../cards'); 7 | 8 | describe('Cards', function() { 9 | var error = 'Oops'; 10 | var data = { 11 | uid: 123 12 | }; 13 | 14 | beforeEach(function() { 15 | console.groupCollapsed = jest.genMockFunction(); 16 | console.log = jest.genMockFunction(); 17 | console.groupEnd = jest.genMockFunction(); 18 | }); 19 | 20 | describe('#created', function() { 21 | beforeEach(function() { 22 | Cards.created(data, true); 23 | }); 24 | 25 | it('outputs expected logs', function() { 26 | expect(console.groupCollapsed).toBeCalledWith('-> ✓ Card - Created'); 27 | expect(console.log.mock.calls[0]).toEqual(['-> Data: ', data]); 28 | expect(console.groupEnd).toBeCalled(); 29 | }); 30 | }); 31 | 32 | describe('#destroyed', function() { 33 | var cardId = '-JnrJpdjoBsiL-aRq16l'; 34 | 35 | beforeEach(function() { 36 | Cards.destroyed(cardId, true); 37 | }); 38 | 39 | it('outputs expected logs', function() { 40 | expect(console.groupCollapsed).toBeCalledWith('-> ✓ Card - Destroyed'); 41 | expect(console.log.mock.calls[0]).toEqual(['-> Card ID: ', cardId]); 42 | expect(console.groupEnd).toBeCalled(); 43 | }); 44 | }); 45 | 46 | describe('#updated', function() { 47 | var cardId = '-JnrJpdjoBsiL-aRq16l'; 48 | 49 | beforeEach(function() { 50 | Cards.updated(cardId, true); 51 | }); 52 | 53 | it('outputs expected logs', function() { 54 | expect(console.groupCollapsed).toBeCalledWith('-> ✓ Card - Updated'); 55 | expect(console.log.mock.calls[0]).toEqual(['-> Card ID: ', cardId]); 56 | expect(console.groupEnd).toBeCalled(); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /utilities/logger/notice/__tests__/users-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../users'); 4 | jest.dontMock('../../runner'); 5 | 6 | var Users = require('../users'); 7 | 8 | describe('Users', function() { 9 | var email = 'test@example.com'; 10 | var password = 'password'; 11 | var error = 'Oops'; 12 | var data = { 13 | uid: 123 14 | }; 15 | 16 | beforeEach(function() { 17 | console.groupCollapsed = jest.genMockFunction(); 18 | console.log = jest.genMockFunction(); 19 | console.groupEnd = jest.genMockFunction(); 20 | }); 21 | 22 | describe('#created', function() { 23 | beforeEach(function() { 24 | Users.created(email, password, data, true); 25 | }); 26 | 27 | it('outputs expected logs', function() { 28 | expect(console.groupCollapsed).toBeCalledWith('-> ✓ User - Created'); 29 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 30 | expect(console.log.mock.calls[1]).toEqual(['-> Password: ', password]); 31 | expect(console.log.mock.calls[2]).toEqual(['-> Data: ', data]); 32 | expect(console.groupEnd).toBeCalled(); 33 | }); 34 | }); 35 | 36 | describe('#destroyed', function() { 37 | beforeEach(function() { 38 | Users.destroyed(email, password, data, true); 39 | }); 40 | 41 | it('outputs expected logs', function() { 42 | expect(console.groupCollapsed).toBeCalledWith('-> ✓ User - Destoryed'); 43 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 44 | expect(console.log.mock.calls[1]).toEqual(['-> Password: ', password]); 45 | expect(console.log.mock.calls[2]).toEqual(['-> Data: ', data]); 46 | expect(console.groupEnd).toBeCalled(); 47 | }); 48 | }); 49 | 50 | describe('#found', function() { 51 | beforeEach(function() { 52 | Users.found(email, password, data, true); 53 | }); 54 | 55 | it('outputs expected logs', function() { 56 | expect(console.groupCollapsed).toBeCalledWith('-> ✓ User - Found'); 57 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 58 | expect(console.log.mock.calls[1]).toEqual(['-> Password: ', password]); 59 | expect(console.log.mock.calls[2]).toEqual(['-> Data: ', data]); 60 | expect(console.groupEnd).toBeCalled(); 61 | }); 62 | }); 63 | 64 | describe('#userPasswordReset', function() { 65 | beforeEach(function() { 66 | Users.passwordReset(email, true); 67 | }); 68 | 69 | it('outputs expected logs', function() { 70 | expect(console.groupCollapsed).toBeCalledWith('-> ✓ User - Password Reset'); 71 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 72 | expect(console.groupEnd).toBeCalled(); 73 | }); 74 | }); 75 | 76 | describe('#userSubscribe', function() { 77 | var data = { uid: 123 }; 78 | 79 | beforeEach(function() { 80 | Users.subscribe(email, data, true); 81 | }); 82 | 83 | it('outputs expected logs', function() { 84 | expect(console.groupCollapsed).toBeCalledWith('-> ✓ User - Subscribed'); 85 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 86 | expect(console.log.mock.calls[1]).toEqual(['-> Data: ', data]); 87 | expect(console.groupEnd).toBeCalled(); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /utilities/logger/notice/cards.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Runner = require('../runner'); 4 | 5 | var created = function(data, forceRun) { 6 | new Runner(forceRun, function() { 7 | console.groupCollapsed('-> ✓ Card - Created'); 8 | console.log('-> Data: ', data); 9 | console.groupEnd(); 10 | }); 11 | }; 12 | 13 | var destroyed = function(cardId, forceRun) { 14 | new Runner(forceRun, function() { 15 | console.groupCollapsed('-> ✓ Card - Destroyed'); 16 | console.log('-> Card ID: ', cardId); 17 | console.groupEnd(); 18 | }); 19 | }; 20 | 21 | var updated = function(cardId, forceRun) { 22 | new Runner(forceRun, function() { 23 | console.groupCollapsed('-> ✓ Card - Updated'); 24 | console.log('-> Card ID: ', cardId); 25 | console.groupEnd(); 26 | }); 27 | }; 28 | 29 | module.exports = { 30 | created: created, 31 | destroyed: destroyed, 32 | updated: updated, 33 | }; 34 | -------------------------------------------------------------------------------- /utilities/logger/notice/users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Runner = require('../runner'); 4 | 5 | var userCreated = function(email, password, data, forceRun) { 6 | new Runner(forceRun, function() { 7 | console.groupCollapsed('-> ✓ User - Created'); 8 | console.log('-> Email: ', email); 9 | console.log('-> Password: ', password); 10 | console.log('-> Data: ', data); 11 | console.groupEnd(); 12 | }); 13 | }; 14 | 15 | var userDestroyed = function(email, password, data, forceRun) { 16 | new Runner(forceRun, function() { 17 | console.groupCollapsed('-> ✓ User - Destoryed'); 18 | console.log('-> Email: ', email); 19 | console.log('-> Password: ', password); 20 | console.log('-> Data: ', data); 21 | console.groupEnd(); 22 | }); 23 | }; 24 | 25 | var userFound = function(email, password, data, forceRun) { 26 | new Runner(forceRun, function() { 27 | console.groupCollapsed('-> ✓ User - Found'); 28 | console.log('-> Email: ', email); 29 | console.log('-> Password: ', password); 30 | console.log('-> Data: ', data); 31 | console.groupEnd(); 32 | }); 33 | }; 34 | 35 | var userPasswordReset = function(email, forceRun) { 36 | new Runner(forceRun, function() { 37 | console.groupCollapsed('-> ✓ User - Password Reset'); 38 | console.log('-> Email: ', email); 39 | console.groupEnd(); 40 | }); 41 | }; 42 | 43 | var userSubscribe = function(email, data, forceRun) { 44 | new Runner(forceRun, function() { 45 | console.groupCollapsed('-> ✓ User - Subscribed'); 46 | console.log('-> Email: ', email); 47 | console.log('-> Data: ', data); 48 | console.groupEnd(); 49 | }); 50 | }; 51 | 52 | module.exports = { 53 | created: userCreated, 54 | destroyed: userDestroyed, 55 | found: userFound, 56 | passwordReset: userPasswordReset, 57 | subscribe: userSubscribe, 58 | }; 59 | -------------------------------------------------------------------------------- /utilities/logger/runner.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(forceRun, block) { 4 | if (forceRun || __DEV__) { block(); } 5 | }; 6 | -------------------------------------------------------------------------------- /utilities/logger/warn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Airbrake = require('./airbrake'); 4 | var Runner = require('./runner'); 5 | 6 | var Cards = require('./warn/cards'); 7 | var Users = require('./warn/users'); 8 | 9 | var general = function(name, error, params, forceRun) { 10 | Airbrake(error, name, params); 11 | 12 | new Runner(forceRun, function() { 13 | console.groupCollapsed('-> ✗ General - ' + name); 14 | console.log('-> Params: ', params); 15 | console.log('-> Error: ', error); 16 | console.groupEnd(); 17 | }); 18 | }; 19 | 20 | module.exports = { 21 | cards: Cards, 22 | general: general, 23 | users: Users, 24 | }; 25 | -------------------------------------------------------------------------------- /utilities/logger/warn/__tests__/cards-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../cards'); 4 | jest.dontMock('../../runner'); 5 | 6 | var Cards = require('../cards'); 7 | 8 | describe('Cards', function() { 9 | var Airbrake = require('../../airbrake'); 10 | 11 | var data = {}; 12 | var error = 'Oops'; 13 | 14 | beforeEach(function() { 15 | console.groupCollapsed = jest.genMockFunction(); 16 | console.log = jest.genMockFunction(); 17 | console.groupEnd = jest.genMockFunction(); 18 | }); 19 | 20 | describe('#createFail', function() { 21 | beforeEach(function() { 22 | Cards.createFail(data, error, true); 23 | }); 24 | 25 | it('pushes to airbrake', function() { 26 | expect(Airbrake).toBeCalledWith( 27 | error, 'cardsCreateFail', { data: data } 28 | ); 29 | }); 30 | 31 | it('outputs expected logs', function() { 32 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ Card - Creation Failure'); 33 | expect(console.log.mock.calls[0]).toEqual(['-> Data: ', data]); 34 | expect(console.log.mock.calls[1]).toEqual(['-> Error: ', error]); 35 | expect(console.groupEnd).toBeCalled(); 36 | }); 37 | }); 38 | 39 | describe('#destroyFail', function() { 40 | var cardId = '-JnrJpdjoBsiL-aRq16l'; 41 | 42 | beforeEach(function() { 43 | Cards.destroyFail(cardId, error, true); 44 | }); 45 | 46 | it('pushes to airbrake', function() { 47 | expect(Airbrake).toBeCalledWith( 48 | error, 'cardsDestroyFail', { cardId: cardId } 49 | ); 50 | }); 51 | 52 | it('outputs expected logs', function() { 53 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ Card - Destory Failure'); 54 | expect(console.log.mock.calls[0]).toEqual(['-> Card ID: ', cardId]); 55 | expect(console.log.mock.calls[1]).toEqual(['-> Error: ', error]); 56 | expect(console.groupEnd).toBeCalled(); 57 | }); 58 | }); 59 | 60 | describe('#updateFail', function() { 61 | var cardId = '-JnrJpdjoBsiL-aRq16l'; 62 | 63 | beforeEach(function() { 64 | Cards.updateFail(cardId, error, true); 65 | }); 66 | 67 | it('pushes to airbrake', function() { 68 | expect(Airbrake).toBeCalledWith( 69 | error, 'cardsUpdateFail', { cardId: cardId } 70 | ); 71 | }); 72 | 73 | it('outputs expected logs', function() { 74 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ Card - Update Failure'); 75 | expect(console.log.mock.calls[0]).toEqual(['-> Card ID: ', cardId]); 76 | expect(console.log.mock.calls[1]).toEqual(['-> Error: ', error]); 77 | expect(console.groupEnd).toBeCalled(); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /utilities/logger/warn/__tests__/users-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.dontMock('../users'); 4 | jest.dontMock('../../runner'); 5 | 6 | var Users = require('../users'); 7 | 8 | describe('Users', function() { 9 | var Airbrake = require('../../airbrake'); 10 | 11 | var email = 'test@example.com'; 12 | var password = 'password'; 13 | var error = 'Oops'; 14 | 15 | beforeEach(function() { 16 | console.groupCollapsed = jest.genMockFunction(); 17 | console.log = jest.genMockFunction(); 18 | console.groupEnd = jest.genMockFunction(); 19 | }); 20 | 21 | describe('#emailTaken', function() { 22 | beforeEach(function() { 23 | Users.emailTaken(email, password, error, true); 24 | }); 25 | 26 | it('outputs expected logs', function() { 27 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ User - Email Taken'); 28 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 29 | expect(console.log.mock.calls[1]).toEqual(['-> Password: ', password]); 30 | expect(console.log.mock.calls[2]).toEqual(['-> Error: ', error]); 31 | expect(console.groupEnd).toBeCalled(); 32 | }); 33 | }); 34 | 35 | describe('#invalidEmail', function() { 36 | beforeEach(function() { 37 | Users.invalidEmail(email, password, error, true); 38 | }); 39 | 40 | it('outputs expected logs', function() { 41 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ User - Invalid Email'); 42 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 43 | expect(console.log.mock.calls[1]).toEqual(['-> Password: ', password]); 44 | expect(console.log.mock.calls[2]).toEqual(['-> Error: ', error]); 45 | expect(console.groupEnd).toBeCalled(); 46 | }); 47 | }); 48 | 49 | describe('#userCreateFail', function() { 50 | beforeEach(function() { 51 | Users.createFail(email, password, error, true); 52 | }); 53 | 54 | it('pushes to airbrake', function() { 55 | expect(Airbrake).toBeCalledWith( 56 | error, 'userCreateFail', { email: email } 57 | ); 58 | }); 59 | 60 | it('outputs expected logs', function() { 61 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ User - Creation Failure'); 62 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 63 | expect(console.log.mock.calls[1]).toEqual(['-> Password: ', password]); 64 | expect(console.log.mock.calls[2]).toEqual(['-> Error: ', error]); 65 | expect(console.groupEnd).toBeCalled(); 66 | }); 67 | }); 68 | 69 | describe('#userDestoryFail', function() { 70 | beforeEach(function() { 71 | Users.destroyFail(email, password, error, true); 72 | }); 73 | 74 | it('pushes to airbrake', function() { 75 | expect(Airbrake).toBeCalledWith( 76 | error, 'userDestoryFail', { email: email } 77 | ); 78 | }); 79 | 80 | it('outputs expected logs', function() { 81 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ User - Destory Failure'); 82 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 83 | expect(console.log.mock.calls[1]).toEqual(['-> Password: ', password]); 84 | expect(console.log.mock.calls[2]).toEqual(['-> Error: ', error]); 85 | expect(console.groupEnd).toBeCalled(); 86 | }); 87 | }); 88 | 89 | describe('#userNotFound', function() { 90 | beforeEach(function() { 91 | Users.notFound(email, password, error, true); 92 | }); 93 | 94 | it('pushes to airbrake', function() { 95 | expect(Airbrake).toBeCalledWith( 96 | error, 'userNotFound', { email: email } 97 | ); 98 | }); 99 | 100 | it('outputs expected logs', function() { 101 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ User - Not Found'); 102 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 103 | expect(console.log.mock.calls[1]).toEqual(['-> Password: ', password]); 104 | expect(console.log.mock.calls[2]).toEqual(['-> Error: ', error]); 105 | expect(console.groupEnd).toBeCalled(); 106 | }); 107 | }); 108 | 109 | describe('#userAccessDenied', function() { 110 | var accessKey = 'access-key'; 111 | 112 | beforeEach(function() { 113 | Users.accessDenied(email, password, error, accessKey, true); 114 | }); 115 | 116 | it('pushes to airbrake', function() { 117 | expect(Airbrake).toBeCalledWith( 118 | error, 'userAccessDenied', { email: email, accessKey: accessKey } 119 | ); 120 | }); 121 | 122 | it('outputs expected logs', function() { 123 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ User - Access Denied'); 124 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 125 | expect(console.log.mock.calls[1]).toEqual(['-> Password: ', password]); 126 | expect(console.log.mock.calls[2]).toEqual(['-> Access Key: ', accessKey]); 127 | expect(console.log.mock.calls[3]).toEqual(['-> Error: ', error]); 128 | expect(console.groupEnd).toBeCalled(); 129 | }); 130 | }); 131 | 132 | describe('#userInvalid', function() { 133 | beforeEach(function() { 134 | Users.invalidUser(email, error, true); 135 | }); 136 | 137 | it('pushes to airbrake', function() { 138 | expect(Airbrake).toBeCalledWith( 139 | error, 'userInvalid', { email: email } 140 | ); 141 | }); 142 | 143 | it('outputs expected logs', function() { 144 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ User - Invalid'); 145 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 146 | expect(console.log.mock.calls[1]).toEqual(['-> Error: ', error]); 147 | expect(console.groupEnd).toBeCalled(); 148 | }); 149 | }); 150 | 151 | describe('#invalidPassword', function() { 152 | beforeEach(function() { 153 | Users.invalidPassword(email, password, error, true); 154 | }); 155 | 156 | it('pushes to airbrake', function() { 157 | expect(Airbrake).toBeCalledWith( 158 | error, 'userInvalidPassword', { email: email } 159 | ); 160 | }); 161 | 162 | it('outputs expected logs', function() { 163 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ User - Invalid Password'); 164 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 165 | expect(console.log.mock.calls[1]).toEqual(['-> Password: ', password]); 166 | expect(console.log.mock.calls[2]).toEqual(['-> Error: ', error]); 167 | expect(console.groupEnd).toBeCalled(); 168 | }); 169 | }); 170 | 171 | describe('#userPasswordResetFail', function() { 172 | beforeEach(function() { 173 | Users.passwordResetFail(email, error, true); 174 | }); 175 | 176 | it('pushes to airbrake', function() { 177 | expect(Airbrake).toBeCalledWith( 178 | error, 'userPasswordResetFail', { email: email } 179 | ); 180 | }); 181 | 182 | it('outputs expected logs', function() { 183 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ User - Password Reset Failure'); 184 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 185 | expect(console.log.mock.calls[1]).toEqual(['-> Error: ', error]); 186 | expect(console.groupEnd).toBeCalled(); 187 | }); 188 | }); 189 | 190 | describe('#userSubscribe', function() { 191 | var listId = __MAILCHIMP_LIST_ID__; 192 | 193 | beforeEach(function() { 194 | Users.subscribe(email, listId, error, true); 195 | }); 196 | 197 | it('pushes to airbrake', function() { 198 | expect(Airbrake).toBeCalledWith( 199 | error, 'userSubscribe', { email: email, listId: listId } 200 | ); 201 | }); 202 | 203 | it('outputs expected logs', function() { 204 | expect(console.groupCollapsed).toBeCalledWith('-> ✗ User - Subscription Failure'); 205 | expect(console.log.mock.calls[0]).toEqual(['-> Email: ', email]); 206 | expect(console.log.mock.calls[1]).toEqual(['-> Error: ', error]); 207 | expect(console.groupEnd).toBeCalled(); 208 | }); 209 | }); 210 | }); 211 | -------------------------------------------------------------------------------- /utilities/logger/warn/cards.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Airbrake = require('../airbrake'); 4 | var Runner = require('../runner'); 5 | 6 | var createFail = function(data, error, forceRun) { 7 | Airbrake(error, 'cardsCreateFail', { 8 | data: data 9 | }); 10 | 11 | new Runner(forceRun, function() { 12 | console.groupCollapsed('-> ✗ Card - Creation Failure'); 13 | console.log('-> Data: ', data); 14 | console.log('-> Error: ', error); 15 | console.groupEnd(); 16 | }); 17 | }; 18 | 19 | var destroyFail = function(cardId, error, forceRun) { 20 | Airbrake(error, 'cardsDestroyFail', { 21 | cardId: cardId 22 | }); 23 | 24 | new Runner(forceRun, function() { 25 | console.groupCollapsed('-> ✗ Card - Destory Failure'); 26 | console.log('-> Card ID: ', cardId); 27 | console.log('-> Error: ', error); 28 | console.groupEnd(); 29 | }); 30 | }; 31 | 32 | var updateFail = function(cardId, error, forceRun) { 33 | Airbrake(error, 'cardsUpdateFail', { 34 | cardId: cardId 35 | }); 36 | 37 | new Runner(forceRun, function() { 38 | console.groupCollapsed('-> ✗ Card - Update Failure'); 39 | console.log('-> Card ID: ', cardId); 40 | console.log('-> Error: ', error); 41 | console.groupEnd(); 42 | }); 43 | }; 44 | 45 | module.exports = { 46 | createFail: createFail, 47 | destroyFail: destroyFail, 48 | updateFail: updateFail, 49 | }; 50 | -------------------------------------------------------------------------------- /utilities/logger/warn/users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Airbrake = require('../airbrake'); 4 | var Runner = require('../runner'); 5 | 6 | var userEmailTaken = function(email, password, error, forceRun) { 7 | new Runner(forceRun, function() { 8 | console.groupCollapsed('-> ✗ User - Email Taken'); 9 | console.log('-> Email: ', email); 10 | console.log('-> Password: ', password); 11 | console.log('-> Error: ', error); 12 | console.groupEnd(); 13 | }); 14 | }; 15 | 16 | var userInvalidEmail = function(email, password, error, forceRun) { 17 | new Runner(forceRun, function() { 18 | console.groupCollapsed('-> ✗ User - Invalid Email'); 19 | console.log('-> Email: ', email); 20 | console.log('-> Password: ', password); 21 | console.log('-> Error: ', error); 22 | console.groupEnd(); 23 | }); 24 | }; 25 | 26 | var userCreateFail = function(email, password, error, forceRun) { 27 | Airbrake(error, 'userCreateFail', { 28 | email: email, 29 | }); 30 | 31 | new Runner(forceRun, function() { 32 | console.groupCollapsed('-> ✗ User - Creation Failure'); 33 | console.log('-> Email: ', email); 34 | console.log('-> Password: ', password); 35 | console.log('-> Error: ', error); 36 | console.groupEnd(); 37 | }); 38 | }; 39 | 40 | var userDestoryFail = function(email, password, error, forceRun) { 41 | Airbrake(error, 'userDestoryFail', { 42 | email: email, 43 | }); 44 | 45 | new Runner(forceRun, function() { 46 | console.groupCollapsed('-> ✗ User - Destory Failure'); 47 | console.log('-> Email: ', email); 48 | console.log('-> Password: ', password); 49 | console.log('-> Error: ', error); 50 | console.groupEnd(); 51 | }); 52 | }; 53 | 54 | var userNotFound = function(email, password, error, forceRun) { 55 | Airbrake(error, 'userNotFound', { 56 | email: email, 57 | }); 58 | 59 | new Runner(forceRun, function() { 60 | console.groupCollapsed('-> ✗ User - Not Found'); 61 | console.log('-> Email: ', email); 62 | console.log('-> Password: ', password); 63 | console.log('-> Error: ', error); 64 | console.groupEnd(); 65 | }); 66 | }; 67 | 68 | var userAccessDenied = function(email, password, error, accessKey, forceRun) { 69 | Airbrake(error, 'userAccessDenied', { 70 | email: email, 71 | accessKey: accessKey, 72 | }); 73 | 74 | new Runner(forceRun, function() { 75 | console.groupCollapsed('-> ✗ User - Access Denied'); 76 | console.log('-> Email: ', email); 77 | console.log('-> Password: ', password); 78 | console.log('-> Access Key: ', accessKey); 79 | console.log('-> Error: ', error); 80 | console.groupEnd(); 81 | }); 82 | }; 83 | 84 | var userInvalid = function(email, error, forceRun) { 85 | Airbrake(error, 'userInvalid', { 86 | email: email, 87 | }); 88 | 89 | new Runner(forceRun, function() { 90 | console.groupCollapsed('-> ✗ User - Invalid'); 91 | console.log('-> Email: ', email); 92 | console.log('-> Error: ', error); 93 | console.groupEnd(); 94 | }); 95 | }; 96 | 97 | var userInvalidPassword = function(email, password, error, forceRun) { 98 | Airbrake(error, 'userInvalidPassword', { 99 | email: email, 100 | }); 101 | 102 | new Runner(forceRun, function() { 103 | console.groupCollapsed('-> ✗ User - Invalid Password'); 104 | console.log('-> Email: ', email); 105 | console.log('-> Password: ', password); 106 | console.log('-> Error: ', error); 107 | console.groupEnd(); 108 | }); 109 | }; 110 | 111 | var userPasswordResetFail = function(email, error, forceRun) { 112 | Airbrake(error, 'userPasswordResetFail', { 113 | email: email, 114 | }); 115 | 116 | new Runner(forceRun, function() { 117 | console.groupCollapsed('-> ✗ User - Password Reset Failure'); 118 | console.log('-> Email: ', email); 119 | console.log('-> Error: ', error); 120 | console.groupEnd(); 121 | }); 122 | }; 123 | 124 | var userSubscribe = function(email, listId, error, forceRun) { 125 | Airbrake(error, 'userSubscribe', { 126 | email: email, 127 | listId: listId 128 | }); 129 | 130 | new Runner(forceRun, function() { 131 | console.groupCollapsed('-> ✗ User - Subscription Failure'); 132 | console.log('-> Email: ', email); 133 | console.log('-> Error: ', error); 134 | console.groupEnd(); 135 | }); 136 | }; 137 | 138 | module.exports = { 139 | createFail: userCreateFail, 140 | destroyFail: userDestoryFail, 141 | emailTaken: userEmailTaken, 142 | invalidEmail: userInvalidEmail, 143 | notFound: userNotFound, 144 | accessDenied: userAccessDenied, 145 | invalidUser: userInvalid, 146 | invalidPassword: userInvalidPassword, 147 | passwordResetFail: userPasswordResetFail, 148 | subscribe: userSubscribe, 149 | }; 150 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var env = require('./env'); 3 | 4 | module.exports = { 5 | devtool: 'eval-source-map', 6 | debug: true, 7 | context: __dirname + '/', 8 | entry: [ 9 | 'webpack/hot/dev-server', 10 | './main.jsx' 11 | ], 12 | output: { 13 | path: __dirname + '/dist/bundle', 14 | filename: 'bundle.js' 15 | }, 16 | resolve: { 17 | extensions: ['', '.js', '.jsx'] 18 | }, 19 | module: { 20 | preLoaders: [ 21 | { 22 | test: /\.jsx$/, 23 | loader: 'jsxhint-loader' 24 | } 25 | ], 26 | loaders: [ 27 | { 28 | test: /\.(jsx|js)?$/, 29 | loaders: ['jsx?harmony'] 30 | }, 31 | { 32 | test: /\.(scss|css)?$/, 33 | loader: 'style!css!sass' 34 | }, 35 | { 36 | test: /\.(png|jpg)$/, 37 | loader: 'url-loader?limit=16384&mimetype=image/png' 38 | } 39 | ] 40 | }, 41 | plugins: [ 42 | new webpack.HotModuleReplacementPlugin(), 43 | new webpack.DefinePlugin({ 44 | VERSION: JSON.stringify('0.0.1'), 45 | __GOOGLE_ANALYTICS_KEY__: JSON.stringify(env.GOOGLE_ANALYTICS_KEY), 46 | __FIREBASE_URL__: JSON.stringify(env.FIREBASE_URL), 47 | __AIRBRAKE_PRODUCT_ID__: JSON.stringify(env.AIRBRAKE_PRODUCT_ID), 48 | __AIRBRAKE_PRODUCT_KEY__: JSON.stringify(env.AIRBRAKE_PRODUCT_KEY), 49 | __LOCAL_STORAGE_KEY__: JSON.stringify(env.LOCAL_STORAGE_KEY), 50 | __LOCKDOWN_KEY__: JSON.stringify(env.LOCKDOWN_KEY), 51 | __MAILCHIMP_API_KEY__: JSON.stringify(env.MAILCHIMP_API_KEY), 52 | __MAILCHIMP_LIST_ID__: JSON.stringify(env.MAILCHIMP_LIST_ID), 53 | __TEST__: false, 54 | __DEV__: true, 55 | 'process.env': { 56 | NODE_ENV: JSON.stringify('development') 57 | } 58 | }) 59 | ] 60 | }; 61 | 62 | -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var env = require('./env'); 3 | 4 | module.exports = { 5 | debug: false, 6 | context: __dirname + '/', 7 | entry: [ './main.jsx' ], 8 | output: { 9 | path: __dirname + '/dist/bundle', 10 | filename: 'bundle.js' 11 | }, 12 | resolve: { 13 | extensions: ['', '.js', '.jsx'] 14 | }, 15 | module: { 16 | preLoaders: [ 17 | { 18 | test: /\.jsx$/, 19 | loader: 'jsxhint-loader' 20 | } 21 | ], 22 | loaders: [ 23 | { 24 | test: /\.(jsx|js)?$/, 25 | loaders: ['jsx?harmony'] 26 | }, 27 | { 28 | test: /\.(scss|css)?$/, 29 | loader: 'style!css!sass' 30 | }, 31 | { 32 | test: /\.(png|jpg)$/, 33 | loader: 'url-loader?limit=16384&mimetype=image/png' 34 | } 35 | ] 36 | }, 37 | plugins: [ 38 | new webpack.HotModuleReplacementPlugin(), 39 | new webpack.DefinePlugin({ 40 | VERSION: JSON.stringify('0.0.1'), 41 | __GOOGLE_ANALYTICS_KEY__: JSON.stringify(env.GOOGLE_ANALYTICS_KEY), 42 | __FIREBASE_URL__: JSON.stringify(env.FIREBASE_URL), 43 | __AIRBRAKE_PRODUCT_ID__: JSON.stringify(env.AIRBRAKE_PRODUCT_ID), 44 | __AIRBRAKE_PRODUCT_KEY__: JSON.stringify(env.AIRBRAKE_PRODUCT_KEY), 45 | __LOCAL_STORAGE_KEY__: JSON.stringify(env.LOCAL_STORAGE_KEY), 46 | __LOCKDOWN_KEY__: JSON.stringify(env.LOCKDOWN_KEY), 47 | __MAILCHIMP_API_KEY__: JSON.stringify(env.MAILCHIMP_API_KEY), 48 | __MAILCHIMP_LIST_ID__: JSON.stringify(env.MAILCHIMP_LIST_ID), 49 | __TEST__: false, 50 | __DEV__: false, 51 | 'process.env': { 52 | NODE_ENV: JSON.stringify('production') 53 | } 54 | }) 55 | ] 56 | }; 57 | 58 | --------------------------------------------------------------------------------