From a3e6ce3fb52b60cae76a5e08f26b956823febb09 Mon Sep 17 00:00:00 2001 From: Dan Head Date: Fri, 10 Feb 2017 00:33:25 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 20 + .flowconfig | 7 + .gitattributes | 20 + .gitignore | 9 + Dockerfile | 15 + LICENSE.txt | 21 + README.md | 112 + package.json | 220 + public/apple-touch-icon.png | Bin 0 -> 4124 bytes public/browserconfig.xml | 12 + public/crossdomain.xml | 15 + public/favicon.ico | Bin 0 -> 766 bytes public/humans.txt | 15 + public/robots.txt | 5 + public/tile-wide.png | Bin 0 -> 4856 bytes public/tile.png | Bin 0 -> 9854 bytes src/client.js | 189 + src/components/App.js | 52 + src/components/Html.js | 45 + src/components/Layout/Layout.css | 170 + src/components/Layout/Layout.js | 19 + src/components/Layout/Layout.test.js | 23 + src/components/Layout/package.json | 6 + src/components/Link/Link.js | 42 + src/components/Link/package.json | 6 + src/components/Page/Page.css | 12 + src/components/Page/Page.js | 24 + src/components/Page/package.json | 6 + src/components/RsvpCard/RsvpCard.css | 98 + src/components/RsvpCard/RsvpCard.js | 261 + src/components/RsvpCard/package.json | 6 + src/components/variables.css | 34 + src/core/admin.js | 6 + src/core/devUtils.js | 13 + src/core/fetch/fetch.client.js | 6 + src/core/fetch/fetch.server.js | 24 + src/core/fetch/package.json | 6 + src/core/fetchQL.js | 17 + src/core/history.js | 5 + src/core/passport.js | 121 + src/data/models/.eslintrc | 5 + src/data/models/Person.js | 73 + src/data/models/User.js | 30 + src/data/models/UserClaim.js | 16 + src/data/models/UserLogin.js | 18 + src/data/models/UserProfile.js | 33 + src/data/models/index.js | 34 + src/data/queries/getAllPersons.js | 25 + src/data/queries/getPersonByKey.js | 22 + src/data/queries/getPersonsByPassword.js | 22 + src/data/queries/me.js | 13 + src/data/schema.js | 23 + src/data/sequelize.js | 12 + src/data/types/PersonItemType.js | 25 + src/data/types/UserType.js | 16 + src/routes/admin/Admin.css | 13 + src/routes/admin/Admin.js | 36 + src/routes/admin/index.js | 41 + src/routes/adminPeople/AdminPeople.css | 77 + src/routes/adminPeople/AdminPeople.js | 112 + src/routes/adminPeople/index.js | 32 + src/routes/adminPerson/AdminPerson.css | 77 + src/routes/adminPerson/AdminPerson.js | 101 + src/routes/adminPerson/index.js | 38 + src/routes/error/ErrorPage.css | 47 + src/routes/error/ErrorPage.js | 32 + src/routes/error/index.js | 17 + src/routes/home/Home.css | 73 + src/routes/home/Home.js | 35 + src/routes/home/index.js | 15 + src/routes/index.js | 32 + src/routes/login/Login.css | 51 + src/routes/login/Login.js | 38 + src/routes/login/index.js | 17 + src/routes/notFound/NotFound.css | 12 + src/routes/notFound/NotFound.js | 22 + src/routes/notFound/index.js | 19 + src/routes/rsvp/Rsvp.css | 24 + src/routes/rsvp/Rsvp.js | 33 + src/routes/rsvp/index.js | 39 + src/server.js | 295 + test/.eslintrc | 9 + test/setup.js | 25 + tools/.eslintrc | 6 + tools/README.md | 48 + tools/build.js | 27 + tools/bundle.js | 20 + tools/clean.js | 22 + tools/copy.js | 57 + tools/deployToAzureWebApps.js | 51 + tools/deployToGitHubPages.js | 37 + tools/lib/fs.js | 69 + tools/lib/markdown-loader.js | 25 + tools/render.js | 53 + tools/run.js | 27 + tools/runServer.js | 63 + tools/start.js | 71 + tools/webpack.config.js | 358 ++ yarn.lock | 6992 ++++++++++++++++++++++ 99 files changed, 11317 insertions(+) create mode 100644 .editorconfig create mode 100644 .flowconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 package.json create mode 100644 public/apple-touch-icon.png create mode 100644 public/browserconfig.xml create mode 100644 public/crossdomain.xml create mode 100644 public/favicon.ico create mode 100644 public/humans.txt create mode 100644 public/robots.txt create mode 100644 public/tile-wide.png create mode 100644 public/tile.png create mode 100644 src/client.js create mode 100644 src/components/App.js create mode 100644 src/components/Html.js create mode 100644 src/components/Layout/Layout.css create mode 100644 src/components/Layout/Layout.js create mode 100644 src/components/Layout/Layout.test.js create mode 100644 src/components/Layout/package.json create mode 100644 src/components/Link/Link.js create mode 100644 src/components/Link/package.json create mode 100644 src/components/Page/Page.css create mode 100644 src/components/Page/Page.js create mode 100644 src/components/Page/package.json create mode 100644 src/components/RsvpCard/RsvpCard.css create mode 100644 src/components/RsvpCard/RsvpCard.js create mode 100644 src/components/RsvpCard/package.json create mode 100644 src/components/variables.css create mode 100644 src/core/admin.js create mode 100644 src/core/devUtils.js create mode 100644 src/core/fetch/fetch.client.js create mode 100644 src/core/fetch/fetch.server.js create mode 100644 src/core/fetch/package.json create mode 100644 src/core/fetchQL.js create mode 100644 src/core/history.js create mode 100644 src/core/passport.js create mode 100644 src/data/models/.eslintrc create mode 100644 src/data/models/Person.js create mode 100644 src/data/models/User.js create mode 100644 src/data/models/UserClaim.js create mode 100644 src/data/models/UserLogin.js create mode 100644 src/data/models/UserProfile.js create mode 100644 src/data/models/index.js create mode 100644 src/data/queries/getAllPersons.js create mode 100644 src/data/queries/getPersonByKey.js create mode 100644 src/data/queries/getPersonsByPassword.js create mode 100644 src/data/queries/me.js create mode 100644 src/data/schema.js create mode 100644 src/data/sequelize.js create mode 100644 src/data/types/PersonItemType.js create mode 100644 src/data/types/UserType.js create mode 100644 src/routes/admin/Admin.css create mode 100644 src/routes/admin/Admin.js create mode 100644 src/routes/admin/index.js create mode 100644 src/routes/adminPeople/AdminPeople.css create mode 100644 src/routes/adminPeople/AdminPeople.js create mode 100644 src/routes/adminPeople/index.js create mode 100644 src/routes/adminPerson/AdminPerson.css create mode 100644 src/routes/adminPerson/AdminPerson.js create mode 100644 src/routes/adminPerson/index.js create mode 100644 src/routes/error/ErrorPage.css create mode 100644 src/routes/error/ErrorPage.js create mode 100644 src/routes/error/index.js create mode 100644 src/routes/home/Home.css create mode 100644 src/routes/home/Home.js create mode 100644 src/routes/home/index.js create mode 100644 src/routes/index.js create mode 100644 src/routes/login/Login.css create mode 100644 src/routes/login/Login.js create mode 100644 src/routes/login/index.js create mode 100644 src/routes/notFound/NotFound.css create mode 100644 src/routes/notFound/NotFound.js create mode 100644 src/routes/notFound/index.js create mode 100644 src/routes/rsvp/Rsvp.css create mode 100644 src/routes/rsvp/Rsvp.js create mode 100644 src/routes/rsvp/index.js create mode 100644 src/server.js create mode 100644 test/.eslintrc create mode 100644 test/setup.js create mode 100644 tools/.eslintrc create mode 100644 tools/README.md create mode 100644 tools/build.js create mode 100644 tools/bundle.js create mode 100644 tools/clean.js create mode 100644 tools/copy.js create mode 100644 tools/deployToAzureWebApps.js create mode 100644 tools/deployToGitHubPages.js create mode 100644 tools/lib/fs.js create mode 100644 tools/lib/markdown-loader.js create mode 100644 tools/render.js create mode 100644 tools/run.js create mode 100644 tools/runServer.js create mode 100644 tools/start.js create mode 100644 tools/webpack.config.js create mode 100644 yarn.lock 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 0000000000000000000000000000000000000000..2ac9d6edaf9b668604f5ccae7345f65162a7a4b5 GIT binary patch literal 4124 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 aC%^!+X_I=0b?htv0000 + + + + + + + + + + + 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 0000000000000000000000000000000000000000..8cd781ac91a19d4e2a5561de83bfd4dd5a1367cd GIT binary patch literal 766 zcmc&wJxe1|6g@8)^RdG4UJ+5U#S{xhY{o*1oggD@p%6sbu&~(pQHd5Qth9+17N(I! zM8zVAEJ`AnMB;AVHcM>$tgM280UP%>Y!UC9afJ8>yu*8U?!E7vbMFNheTKp5*6j_dzT#T1cnUSX&xKZpFsV`WO8CF$#G& zj=RcN{JWmQ|Hh1qZnavN;FGLFFfKUfLa-Qz4miHbt9K|1x)|v?aK)Kh7?cPJs<3sO!kzaI-o6k>oAGkEnhhyltAP60k@3RTp0+Jh}-_{KxSO zh<{h{16rXmRu>|GfYir|Hd4^F3AONl@9pK1*eNyw1*tq!B30=19wZV39CF@_+)h|2BIQvq{lrv9`zW5Ih5VhlAPx literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d470bce7eb86e30eccb690361dc051a624ab5b6d GIT binary patch literal 4856 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 KZuK2h+d_fE9Fo(4H76Da@yAlK4VH3R_g!T;$b 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 CvOIPG literal 0 HcmV?d00001 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 &&