commit a3e6ce3fb52b60cae76a5e08f26b956823febb09 Author: Dan Head Date: Fri Feb 10 00:33:25 2017 +0000 đŸ”Ĩ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..959db8a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# http://editorconfig.org + +root = true + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# editorconfig-tools is unable to ignore longs strings or urls +max_line_length = null diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..fadd349 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,7 @@ +[ignore] +.*/build +.*/config +.*/node_modules +.*/gulpfile.js + +[include] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5ca4655 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,20 @@ +# Automatically normalize line endings for all text-based files +# http://git-scm.com/docs/gitattributes#_end_of_line_conversion +* text=auto + +# For the following file types, normalize line endings to LF on +# checkin and prevent conversion to CRLF when they are checked out +# (this is required in order to prevent newline related issues like, +# for example, after the build script is run) +.* text eol=lf +*.html text eol=lf +*.css text eol=lf +*.less text eol=lf +*.scss text eol=lf +*.sss text eol=lf +*.js text eol=lf +*.json text eol=lf +*.md text eol=lf +*.sh text eol=lf +*.txt text eol=lf +*.xml text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33717fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Include your project-specific ignores in this file +# Read about how to use .gitignore: https://help.github.com/articles/ignoring-files + +build +database.sqlite +node_modules +ncp-debug.log +npm-debug.log +wedding.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..530f5c8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:7.2.1-alpine + +# Copy application files +COPY ./build /usr/src/app +WORKDIR /usr/src/app + +ENV DATABASE_PATH db/wedding.db +ENV PORT 80 + +EXPOSE 80 + +# Install Node.js dependencies +RUN npm install --production --silent + +CMD [ "node", "server.js" ] diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..16f8127 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2014-2016 Konstantin Tarkus, KriaSoft LLC. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a08bc75 --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# Wedding RSVP site +Forked from [React Starter Kit](https://www.reactstarterkit.com) +development built on top of [Node.js](https://nodejs.org/), +[Express](http://expressjs.com/), [GraphQL](http://graphql.org/) and +[React](https://facebook.github.io/react/), containing modern web development +tools such as [Webpack](http://webpack.github.io/), [Babel](http://babeljs.io/) +and [Browsersync](http://www.browsersync.io/). Helping you to stay productive +following the best practices. A solid starting point for both professionals +and newcomers to the industry. + +## Getting Started + +### Requirements + + * Mac OS X, Windows, or Linux + * [Yarn](https://yarnpkg.com/) package + [Node.js](https://nodejs.org/) v6.5 or newer + * `node-gyp` prerequisites mentioned [here](https://github.com/nodejs/node-gyp) + * Text editor or IDE pre-configured with React/JSX/Flow/ESlint ([learn more](./how-to-configure-text-editors.md)) + +### Quick Start + +#### 1. Run `yarn install` + +This will install both run-time project dependencies and developer tools listed +in [package.json](../package.json) file. + +#### 2. Run `yarn start` + +This command will build the app from the source files (`/src`) into the output +`/build` folder. As soon as the initial build completes, it will start the +Node.js server (`node build/server.js`) and [Browsersync](https://browsersync.io/) +with [HMR](https://webpack.github.io/docs/hot-module-replacement) on top of it. + +> [http://localhost:3000/](http://localhost:3000/) — Node.js server (`build/server.js`)
+> [http://localhost:3000/graphql](http://localhost:3000/graphql) — GraphQL server and IDE
+> [http://localhost:3001/](http://localhost:3001/) — BrowserSync proxy with HMR, React Hot Transform
+> [http://localhost:3002/](http://localhost:3002/) — BrowserSync control panel (UI) + +Now you can open your web app in a browser, on mobile devices and start +hacking. Whenever you modify any of the source files inside the `/src` folder, +the module bundler ([Webpack](http://webpack.github.io/)) will recompile the +app on the fly and refresh all the connected browsers. + +![browsersync](https://dl.dropboxusercontent.com/u/16006521/react-starter-kit/brwosersync.jpg) + +Note that the `yarn start` command launches the app in `development` mode, +the compiled output files are not optimized and minimized in this case. +You can use `--release` command line argument to check how your app works +in release (production) mode: + +```shell +$ yarn start -- --release +``` +*NOTE: double dashes are required* + + +### How to Build, Test, Deploy + +If you need just to build the app (without running a dev server), simply run: + +```shell +$ yarn run build +``` + +or, for a production build: + +```shell +$ yarn run build -- --release +``` + +or, for a production docker build: + +```shell +$ yarn run build -- --release --docker +``` + +*NOTE: double dashes are required* + +After running this command, the `/build` folder will contain the compiled +version of the app. For example, you can launch Node.js server normally by +running `node build/server.js`. + +To check the source code for syntax errors and potential issues run: + +```shell +$ yarn run lint +``` + +To launch unit tests: + +```shell +$ yarn run test # Run unit tests with Mocha +$ yarn run test:watch # Launch unit test runner and start watching for changes +``` + +By default, [Mocha](https://mochajs.org/) test runner is looking for test files +matching the `src/**/*.test.js` pattern. Take a look at `src/components/Layout/Layout.test.js` +as an example. + +To deploy the app, run: + +```shell +$ yarn run deploy +``` + +The deployment script `tools/deploy.js` is configured to push the contents of +the `/build` folder to a remote server via Git. You can easily deploy your app +to [Azure Web Apps](https://azure.microsoft.com/en-us/services/app-service/web/), +or [Heroku](https://www.heroku.com/) this way. Both will execute `yarn install --production` +upon receiving new files from you. Note, you should only deploy the contents +of the `/build` folder to a remote server. + diff --git a/package.json b/package.json new file mode 100644 index 0000000..e3da3d4 --- /dev/null +++ b/package.json @@ -0,0 +1,220 @@ +{ + "name": "wedding", + "version": "0.0.1", + "private": true, + "engines": { + "node": ">=6.5", + "npm": ">=3.10" + }, + "dependencies": { + "babel-polyfill": "^6.20.0", + "babel-runtime": "^6.20.0", + "bluebird": "^3.4.6", + "body-parser": "^1.15.2", + "classnames": "^2.2.5", + "cookie-parser": "^1.4.3", + "core-js": "^2.4.1", + "express": "^4.14.0", + "express-graphql": "^0.6.1", + "express-jwt": "^5.1.0", + "fastclick": "^1.0.6", + "fbjs": "^0.8.6", + "graphql": "^0.8.2", + "history": "^4.5.0", + "isomorphic-style-loader": "^1.1.0", + "jsonwebtoken": "^7.2.1", + "node-fetch": "^1.6.3", + "normalize.css": "^5.0.0", + "passport": "^0.3.2", + "passport-facebook": "^2.1.1", + "pretty-error": "^2.0.2", + "query-string": "^4.2.3", + "react": "^15.4.1", + "react-dom": "^15.4.1", + "react-icon-base": "^2.0.4", + "react-icons": "^2.2.3", + "rollbar": "^0.6.3", + "sequelize": "^3.28.0", + "source-map-support": "^0.4.6", + "sqlite3": "^3.1.8", + "universal-router": "^2.0.0", + "whatwg-fetch": "^2.0.1" + }, + "devDependencies": { + "assets-webpack-plugin": "^3.5.0", + "autoprefixer": "^6.5.3", + "babel-cli": "^6.18.0", + "babel-core": "^6.18.2", + "babel-eslint": "^7.1.1", + "babel-loader": "^6.2.8", + "babel-plugin-rewire": "^1.0.0", + "babel-plugin-transform-runtime": "^6.15.0", + "babel-preset-latest": "^6.16.0", + "babel-preset-react": "^6.16.0", + "babel-preset-react-optimize": "^1.0.1", + "babel-preset-stage-0": "^6.16.0", + "babel-register": "^6.18.0", + "babel-template": "^6.16.0", + "babel-types": "^6.19.0", + "browser-sync": "^2.18.2", + "chai": "^3.5.0", + "chokidar": "^1.6.1", + "css-loader": "^0.26.0", + "editorconfig-tools": "^0.1.1", + "enzyme": "^2.6.0", + "eslint": "^3.10.2", + "eslint-config-airbnb": "^13.0.0", + "eslint-loader": "^1.6.1", + "eslint-plugin-css-modules": "^1.0.9", + "eslint-plugin-import": "^2.2.0", + "eslint-plugin-jsx-a11y": "^2.2.3", + "eslint-plugin-react": "^6.7.1", + "extend": "^3.0.0", + "file-loader": "^0.9.0", + "front-matter": "^2.1.1", + "git-repository": "^0.1.4", + "glob": "^7.1.1", + "json-loader": "^0.5.4", + "lint-staged": "^3.2.2", + "markdown-it": "^8.2.2", + "mkdirp": "^0.5.1", + "mocha": "^3.1.2", + "pixrem": "^3.0.2", + "pleeease-filters": "^3.0.0", + "postcss": "^5.2.5", + "postcss-calc": "^5.3.1", + "postcss-color-function": "^2.0.1", + "postcss-custom-media": "^5.0.1", + "postcss-custom-properties": "^5.0.1", + "postcss-custom-selectors": "^3.0.0", + "postcss-flexbugs-fixes": "^2.0.0", + "postcss-loader": "^1.1.1", + "postcss-media-minmax": "^2.1.2", + "postcss-nested": "^1.0.0", + "postcss-nesting": "^2.3.1", + "postcss-partial-import": "^2.1.0", + "postcss-pseudoelements": "^3.0.0", + "postcss-selector-matches": "^2.0.5", + "postcss-selector-not": "^2.0.0", + "postcss-url": "^5.1.2", + "pre-commit": "^1.2.2", + "raw-loader": "^0.5.1", + "react-addons-test-utils": "15.4.1", + "react-deep-force-update": "^2.0.1", + "react-hot-loader": "^3.0.0-beta.6", + "redbox-react": "^1.3.3", + "rimraf": "^2.5.4", + "sinon": "^2.0.0-pre.3", + "stylefmt": "^5.0.2", + "stylelint": "^7.6.0", + "stylelint-config-standard": "^15.0.0", + "url-loader": "^0.5.7", + "webpack": "^1.13.3", + "webpack-hot-middleware": "^2.13.2", + "webpack-middleware": "^1.5.1" + }, + "babel": { + "presets": [ + "react", + "stage-0" + ], + "plugins": [ + "syntax-trailing-function-commas", + "transform-async-to-generator", + "transform-es2015-destructuring", + "transform-es2015-parameters", + "transform-es2015-duplicate-keys", + "transform-es2015-modules-commonjs", + "transform-exponentiation-operator", + "transform-runtime" + ], + "env": { + "test": { + "plugins": [ + "rewire" + ] + } + } + }, + "eslintConfig": { + "parser": "babel-eslint", + "extends": [ + "airbnb", + "plugin:css-modules/recommended" + ], + "plugins": [ + "css-modules" + ], + "globals": { + "__DEV__": true + }, + "env": { + "browser": true + }, + "rules": { + "arrow-parens": "off", + "generator-star-spacing": "off", + "import/extensions": "off", + "import/no-extraneous-dependencies": "off", + "react/forbid-prop-types": "off", + "react/jsx-filename-extension": "off", + "react/no-danger": "off", + "react/no-unused-prop-types": "off", + "react/prefer-stateless-function": "off" + } + }, + "stylelint": { + "extends": "stylelint-config-standard", + "rules": { + "string-quotes": "single", + "property-no-unknown": [ + true, + { + "ignoreProperties": [ + "composes" + ] + } + ], + "selector-pseudo-class-no-unknown": [ + true, + { + "ignorePseudoClasses": [ + "global" + ] + } + ] + } + }, + "pre-commit": "lint:staged", + "lint-staged": { + "*.{md,sh,txt,xml,html,json}": [ + "editorconfig-tools fix", + "git add" + ], + "*.{js,jsx}": [ + "eslint --fix", + "git add" + ], + "*.{css,less,scss,sss}": [ + "stylefmt", + "stylelint", + "git add" + ] + }, + "scripts": { + "lint:js": "eslint src tools", + "lint:css": "stylelint \"src/**/*.{css,less,scss,sss}\"", + "lint:staged": "lint-staged", + "lint": "npm run lint:js && npm run lint:css", + "test": "mocha \"src/**/*.test.js\" --require babel-register --require test/setup.js", + "test:watch": "npm run test -- --reporter min --watch", + "clean": "babel-node tools/run clean", + "copy": "babel-node tools/run copy", + "bundle": "babel-node tools/run bundle", + "build": "babel-node tools/run build", + "deploy": "babel-node tools/run deployToAzureWebApps", + "render": "babel-node tools/run render", + "serve": "babel-node tools/run runServer", + "start": "babel-node tools/run start" + } +} diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..2ac9d6e Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/browserconfig.xml b/public/browserconfig.xml new file mode 100644 index 0000000..46de5d3 --- /dev/null +++ b/public/browserconfig.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/crossdomain.xml b/public/crossdomain.xml new file mode 100644 index 0000000..818b822 --- /dev/null +++ b/public/crossdomain.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..8cd781a Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/humans.txt b/public/humans.txt new file mode 100644 index 0000000..ed65c14 --- /dev/null +++ b/public/humans.txt @@ -0,0 +1,15 @@ +# humanstxt.org/ +# The humans responsible & technology colophon + +# TEAM + + Daniel Head -- Developer/Groom -- @danhead + +# THANKS + + Hana Becka, for saying yes + +# TECHNOLOGY COLOPHON + + CSS3, HTML5, JavaScript + React, Flux, SuperAgent diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..d0e5f1b --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,5 @@ +# www.robotstxt.org/ + +# Allow crawling of all content +User-agent: * +Disallow: diff --git a/public/tile-wide.png b/public/tile-wide.png new file mode 100644 index 0000000..d470bce Binary files /dev/null and b/public/tile-wide.png differ diff --git a/public/tile.png b/public/tile.png new file mode 100644 index 0000000..858d0fe Binary files /dev/null and b/public/tile.png differ diff --git a/src/client.js b/src/client.js new file mode 100644 index 0000000..0575fdb --- /dev/null +++ b/src/client.js @@ -0,0 +1,189 @@ +import 'babel-polyfill'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import FastClick from 'fastclick'; +import UniversalRouter from 'universal-router'; +import queryString from 'query-string'; +import { createPath } from 'history/PathUtils'; +import history from './core/history'; +import App from './components/App'; +import { ErrorReporter, deepForceUpdate } from './core/devUtils'; + +// Global (context) variables that can be easily accessed from any React component +// https://facebook.github.io/react/docs/context.html +const context = { + // Enables critical path CSS rendering + // https://github.com/kriasoft/isomorphic-style-loader + insertCss: (...styles) => { + // eslint-disable-next-line no-underscore-dangle + const removeCss = styles.map(x => x._insertCss()); + return () => { removeCss.forEach(f => f()); }; + }, +}; + +function updateTag(tagName, keyName, keyValue, attrName, attrValue) { + const node = document.head.querySelector(`${tagName}[${keyName}="${keyValue}"]`); + if (node && node.getAttribute(attrName) === attrValue) return; + + // Remove and create a new tag in order to make it work with bookmarks in Safari + if (node) { + node.parentNode.removeChild(node); + } + if (typeof attrValue === 'string') { + const nextNode = document.createElement(tagName); + nextNode.setAttribute(keyName, keyValue); + nextNode.setAttribute(attrName, attrValue); + document.head.appendChild(nextNode); + } +} +function updateMeta(name, content) { + updateTag('meta', 'name', name, 'content', content); +} +function updateCustomMeta(property, content) { // eslint-disable-line no-unused-vars + updateTag('meta', 'property', property, 'content', content); +} +function updateLink(rel, href) { // eslint-disable-line no-unused-vars + updateTag('link', 'rel', rel, 'href', href); +} + +// Switch off the native scroll restoration behavior and handle it manually +// https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration +const scrollPositionsHistory = {}; +if (window.history && 'scrollRestoration' in window.history) { + window.history.scrollRestoration = 'manual'; +} + +let onRenderComplete = function initialRenderComplete() { + const elem = document.getElementById('css'); + if (elem) elem.parentNode.removeChild(elem); + onRenderComplete = function renderComplete(route, location) { + document.title = route.title; + + updateMeta('description', route.description); + // Update necessary tags in at runtime here, ie: + // updateMeta('keywords', route.keywords); + // updateCustomMeta('og:url', route.canonicalUrl); + // updateCustomMeta('og:image', route.imageUrl); + // updateLink('canonical', route.canonicalUrl); + // etc. + + let scrollX = 0; + let scrollY = 0; + const pos = scrollPositionsHistory[location.key]; + if (pos) { + scrollX = pos.scrollX; + scrollY = pos.scrollY; + } else { + const targetHash = location.hash.substr(1); + if (targetHash) { + const target = document.getElementById(targetHash); + if (target) { + scrollY = window.pageYOffset + target.getBoundingClientRect().top; + } + } + } + + // Restore the scroll position if it was saved into the state + // or scroll to the given #hash anchor + // or scroll to top of the page + window.scrollTo(scrollX, scrollY); + + // Google Analytics tracking. Don't send 'pageview' event after + // the initial rendering, as it was already sent + if (window.ga) { + window.ga('send', 'pageview', createPath(location)); + } + }; +}; + +// Make taps on links and buttons work fast on mobiles +FastClick.attach(document.body); + +const container = document.getElementById('app'); +let appInstance; +let currentLocation = history.location; +let routes = require('./routes').default; + +// Re-render the app when window.location changes +async function onLocationChange(location) { + // Remember the latest scroll position for the previous location + scrollPositionsHistory[currentLocation.key] = { + scrollX: window.pageXOffset, + scrollY: window.pageYOffset, + }; + // Delete stored scroll position for next page if any + if (history.action === 'PUSH') { + delete scrollPositionsHistory[location.key]; + } + currentLocation = location; + + try { + // Traverses the list of routes in the order they are defined until + // it finds the first route that matches provided URL path string + // and whose action method returns anything other than `undefined`. + const route = await UniversalRouter.resolve(routes, { + path: location.pathname, + query: queryString.parse(location.search), + }); + + // Prevent multiple page renders during the routing process + if (currentLocation.key !== location.key) { + return; + } + + if (route.redirect) { + history.replace(route.redirect); + return; + } + + appInstance = ReactDOM.render( + {route.component}, + container, + () => onRenderComplete(route, location), + ); + } catch (error) { + console.error(error); // eslint-disable-line no-console + + // Current url has been changed during navigation process, do nothing + if (currentLocation.key !== location.key) { + return; + } + + // Display the error in full-screen for development mode + if (process.env.NODE_ENV !== 'production') { + appInstance = null; + document.title = `Error: ${error.message}`; + ReactDOM.render(, container); + return; + } + + // Avoid broken navigation in production mode by a full page reload on error + window.location.reload(); + } +} + +// Handle client-side navigation by using HTML5 History API +// For more information visit https://github.com/mjackson/history#readme +history.listen(onLocationChange); +onLocationChange(currentLocation); + +// Enable Hot Module Replacement (HMR) +if (module.hot) { + module.hot.accept('./routes', () => { + routes = require('./routes').default; // eslint-disable-line global-require + + if (appInstance) { + try { + // Force-update the whole tree, including components that refuse to update + deepForceUpdate(appInstance); + } catch (error) { + appInstance = null; + document.title = `Hot Update Error: ${error.message}`; + ReactDOM.render(, container); + return; + } + } + + onLocationChange(currentLocation); + }); +} diff --git a/src/components/App.js b/src/components/App.js new file mode 100644 index 0000000..28ce58f --- /dev/null +++ b/src/components/App.js @@ -0,0 +1,52 @@ +import React, { PropTypes } from 'react'; + +const ContextType = { + // Enables critical path CSS rendering + // https://github.com/kriasoft/isomorphic-style-loader + insertCss: PropTypes.func.isRequired, +}; + +/** + * The top-level React component setting context (global) variables + * that can be accessed from all the child components. + * + * https://facebook.github.io/react/docs/context.html + * + * Usage example: + * + * const context = { + * history: createBrowserHistory(), + * store: createStore(), + * }; + * + * ReactDOM.render( + * + * + * + * + * , + * container, + * ); + */ +class App extends React.PureComponent { + + static propTypes = { + context: PropTypes.shape(ContextType).isRequired, + children: PropTypes.element.isRequired, + }; + + static childContextTypes = ContextType; + + getChildContext() { + return this.props.context; + } + + render() { + // NOTE: If you need to add or modify header, footer etc. of the app, + // please do that inside the Layout component. + return React.Children.only(this.props.children); + } + +} + +export default App; diff --git a/src/components/Html.js b/src/components/Html.js new file mode 100644 index 0000000..8ce9301 --- /dev/null +++ b/src/components/Html.js @@ -0,0 +1,45 @@ +import React, { PropTypes } from 'react'; +import { analytics } from '../config'; + +class Html extends React.Component { + static propTypes = { + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + style: PropTypes.string, + scripts: PropTypes.arrayOf(PropTypes.string.isRequired), + children: PropTypes.string, + }; + + render() { + const { title, description, style, scripts, children } = this.props; + return ( + + + + + {title} + + + + {style &&