From a2236785d19deb37a3c7623dd0e1f97f1661e60b Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 28 Oct 2018 16:42:38 -0400 Subject: [PATCH 01/17] add webpack build pipeline for react --- .babelrc | 4 ++ .gitignore | 114 +----------------------------------- .sailsrc | 3 + assets/js/login.js | 11 ++++ assets/templates/login.html | 10 ++++ package.json | 27 +++++++-- views/pages/login.ejs | 1 + webpack.config.js | 29 +++++++++ 8 files changed, 82 insertions(+), 117 deletions(-) create mode 100644 .babelrc create mode 100644 assets/js/login.js create mode 100644 assets/templates/login.html create mode 100644 views/pages/login.ejs create mode 100644 webpack.config.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..653952e --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@babel/preset-env", "@babel/preset-react"], + "plugins": ["@babel/plugin-proposal-object-rest-spread"] +} diff --git a/.gitignore b/.gitignore index e69af24..ed072b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,119 +1,7 @@ -################################################ -# ┌─┐┬┌┬┐╦╔═╗╔╗╔╔═╗╦═╗╔═╗ -# │ ┬│ │ ║║ ╦║║║║ ║╠╦╝║╣ -# o└─┘┴ ┴ ╩╚═╝╝╚╝╚═╝╩╚═╚═╝ -# -# > Files to exclude from your app's repo. -# -# This file (`.gitignore`) is only relevant if -# you are using git. -# -# It exists to signify to git that certain files -# and/or directories should be ignored for the -# purposes of version control. -# -# This keeps tmp files and sensitive credentials -# from being uploaded to your repository. And -# it allows you to configure your app for your -# machine without accidentally committing settings -# which will smash the local settings of other -# developers on your team. -# -# Some reasonable defaults are included below, -# but, of course, you should modify/extend/prune -# to fit your needs! -# -################################################ - - -################################################ -# Local Configuration -# -# Explicitly ignore files which contain: -# -# 1. Sensitive information you'd rather not push to -# your git repository. -# e.g., your personal API keys or passwords. -# -# 2. Developer-specific configuration -# Basically, anything that would be annoying -# to have to change every time you do a -# `git pull` on your laptop. -# e.g. your local development database, or -# the S3 bucket you're using for file uploads -# during development. -# -################################################ - config/local.js - - -################################################ -# Dependencies -# -# -# When releasing a production app, you _could_ -# hypothetically include your node_modules folder -# in your git repo, but during development, it -# is always best to exclude it, since different -# developers may be working on different kernels, -# where dependencies would need to be recompiled -# anyway. -# -# Most of the time, the node_modules folder can -# be excluded from your code repository, even -# in production, thanks to features like the -# package-lock.json file / NPM shrinkwrap. -# -# But no matter what, since this is a Sails app, -# you should always push up the package-lock.json -# or shrinkwrap file to your repository, to avoid -# accidentally pulling in upgraded dependencies -# and breaking your code. -# -# That said, if you are having trouble with -# dependencies, (particularly when using -# `npm link`) this can be pretty discouraging. -# But rather than just adding the lockfile to -# your .gitignore, try this first: -# ``` -# rm -rf node_modules -# rm package-lock.json -# npm install -# ``` -# -# [?] For more tips/advice, come by and say hi -# over at https://sailsjs.com/support -# -################################################ - node_modules - - -################################################ -# -# > Do you use bower? -# > re: the bower_components dir, see this: -# > http://addyosmani.com/blog/checking-in-front-end-dependencies/ -# > (credit Addy Osmani, @addyosmani) -# -################################################ - - -################################################ -# Temporary files generated by Sails/Waterline. -################################################ - .tmp - -################################################ -# Miscellaneous -# -# Common files generated by text editors, -# operating systems, file systems, dbs, etc. -################################################ - *~ *# .DS_STORE @@ -123,7 +11,7 @@ nbproject .node_history dump.rdb -npm-debug.log +npm-debug.log.* lib-cov *.seed *.log diff --git a/.sailsrc b/.sailsrc index d684793..1a67e89 100644 --- a/.sailsrc +++ b/.sailsrc @@ -5,5 +5,8 @@ "_generatedWith": { "sails": "1.0.2", "sails-generate": "1.15.28" + }, + "hooks": { + "grunt": false } } diff --git a/assets/js/login.js b/assets/js/login.js new file mode 100644 index 0000000..ac8b1ca --- /dev/null +++ b/assets/js/login.js @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +const App = () => { + return ( +
+ login.js +
+ ); +}; + +ReactDOM.render(, document.getElementById('root')); diff --git a/assets/templates/login.html b/assets/templates/login.html new file mode 100644 index 0000000..356ad22 --- /dev/null +++ b/assets/templates/login.html @@ -0,0 +1,10 @@ + + + + + Sails React + + +
+ + diff --git a/package.json b/package.json index f3b6819..3465816 100644 --- a/package.json +++ b/package.json @@ -12,20 +12,39 @@ "express-rate-limit": "^3.2.1", "forever": "^0.15.3", "grunt": "^1.0.3", + "react": "^16.6.0", + "react-dom": "^16.6.0", "sails": "^1.0.2", "sails-hook-grunt": "^3.0.2", "sails-hook-orm": "^2.1.1", "sails-hook-sockets": "^1.4.0" }, "devDependencies": { - "@sailshq/eslint": "^4.19.3" + "@babel/core": "^7.1.2", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@babel/preset-react": "^7.0.0", + "@sailshq/eslint": "^4.19.3", + "babel-loader": "^8.0.4", + "html-webpack-plugin": "^3.2.0", + "npm-run-all": "^4.1.3", + "rimraf": "^2.6.2", + "webpack": "^4.23.1", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.10" }, "scripts": { - "start": "sudo NODE_ENV='production' ./node_modules/.bin/forever start app.js", - "stop": "./node_modules/.bin/forever stopall", + "start": "npm-run-all --parallel open:client lift", + "start:debug": "npm-run-all --parallel open:client debug", + "open:client": "webpack-dev-server --mode development", + "build": "npm run build:prod", + "build:dev": "webpack --mode development", + "build:prod": "webpack --mode production", + "clean": "rimraf .tmp && mkdirp .tmp/public", + "lift": "sails lift", "test": "npm run lint && npm run custom-tests && echo 'Done.'", "lint": "eslint . --max-warnings=0 --report-unused-disable-directives && echo '✔ Your .js files look good.'", - "custom-tests": "echo \"(No other custom tests yet.)\" && echo" + "debug": "node --inspect app.js" }, "main": "app.js", "repository": { diff --git a/views/pages/login.ejs b/views/pages/login.ejs new file mode 100644 index 0000000..94e98d0 --- /dev/null +++ b/views/pages/login.ejs @@ -0,0 +1 @@ +<%- partial('../../.tmp/public/login.html') %> diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..4b79938 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,29 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + entry: { + login: './assets/js/login.js' + }, + output: { + path: __dirname + '/.tmp/public', + filename: '[name].bundle.js' + }, + module: { + rules: [ + { + use: 'babel-loader', + test: /\.jsx?$/, + exclude: /node_modules/ + }, + { + use: ['style-loader', 'css-loader'], + test: /\.css$/ + } + ] + }, + plugins: [ + new HtmlWebpackPlugin({ + template: 'assets/templates/login.html' + }) + ] +}; From cd60b8b290f690348d4be4a023bb598b80f0bc63 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 28 Oct 2018 18:12:13 -0400 Subject: [PATCH 02/17] add baseline js and scss --- assets/js/components/Progress.js | 9 ++++ assets/js/containers/Carousel.js | 22 +++++++++ assets/js/login.js | 83 ++++++++++++++++++++++++++++---- assets/styles/importer.less | 24 --------- assets/styles/lib/colors.scss | 2 + assets/styles/lib/default.scss | 7 +++ assets/styles/login.scss | 2 + assets/templates/login.html | 8 ++- 8 files changed, 122 insertions(+), 35 deletions(-) create mode 100644 assets/js/components/Progress.js create mode 100644 assets/js/containers/Carousel.js delete mode 100644 assets/styles/importer.less create mode 100644 assets/styles/lib/colors.scss create mode 100644 assets/styles/lib/default.scss create mode 100644 assets/styles/login.scss diff --git a/assets/js/components/Progress.js b/assets/js/components/Progress.js new file mode 100644 index 0000000..8a9efff --- /dev/null +++ b/assets/js/components/Progress.js @@ -0,0 +1,9 @@ +import React from 'react' + +const Progress = props => ( +
+
+
+) + +export default Progress diff --git a/assets/js/containers/Carousel.js b/assets/js/containers/Carousel.js new file mode 100644 index 0000000..6bbf352 --- /dev/null +++ b/assets/js/containers/Carousel.js @@ -0,0 +1,22 @@ +import React from 'react' + +class Carousel extends React.Component { + render () { + return ( +
+
+ {this.props.children} +
+
+ ) + } +} + +const CarouselItem = props => ( +
{props.children}
+) + +export default Carousel +export { + CarouselItem +} \ No newline at end of file diff --git a/assets/js/login.js b/assets/js/login.js index ac8b1ca..77e79c6 100644 --- a/assets/js/login.js +++ b/assets/js/login.js @@ -1,11 +1,74 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -const App = () => { - return ( -
- login.js -
- ); -}; +import React from 'react' +import ReactDOM from 'react-dom' +import Progress from './components/Progress' +import Carousel, {CarouselItem} from './containers/Carousel' -ReactDOM.render(, document.getElementById('root')); +const App = () => { + // I will tidy this later + return ( +
+
+ + + +
+

Sign in

+
+
+ +
+ +
+
+
+
+
+ +
+ Create account + +
+ +
+ +
+

Welcome

+
+ user profile image +
+ firstname lastname + x@y.z +
+ Not you? +
+
+
+ +
+ +
+
+
+
+
+ +
+ Forgot password? + +
+
+
+
+
+ ) +} + +ReactDOM.render(, document.getElementById('root')) diff --git a/assets/styles/importer.less b/assets/styles/importer.less deleted file mode 100644 index cfe8203..0000000 --- a/assets/styles/importer.less +++ /dev/null @@ -1,24 +0,0 @@ -/** - * importer.less - * - * By default, new Sails projects are configured to compile this file - * from LESS to CSS. Unlike CSS files, LESS files are not compiled and - * included automatically unless they are imported below. - * - * For more information see: - * https://sailsjs.com/anatomy/assets/styles/importer-less - */ - - -// For example: -// -// @import 'variables/colors.less'; -// @import 'mixins/foo.less'; -// @import 'mixins/bar.less'; -// @import 'mixins/baz.less'; -// -// @import 'styleguide.less'; -// @import 'pages/login.less'; -// @import 'pages/signup.less'; -// -// etc. diff --git a/assets/styles/lib/colors.scss b/assets/styles/lib/colors.scss new file mode 100644 index 0000000..9cb7c5f --- /dev/null +++ b/assets/styles/lib/colors.scss @@ -0,0 +1,2 @@ +$text: rgba(0,0,0,.87); + diff --git a/assets/styles/lib/default.scss b/assets/styles/lib/default.scss new file mode 100644 index 0000000..da45d93 --- /dev/null +++ b/assets/styles/lib/default.scss @@ -0,0 +1,7 @@ +html, +body { + height: 100%; + width: 100%; + margin: 0; + padding: 0; +} diff --git a/assets/styles/login.scss b/assets/styles/login.scss new file mode 100644 index 0000000..d8f3ddf --- /dev/null +++ b/assets/styles/login.scss @@ -0,0 +1,2 @@ +@import 'lib/default'; +@import 'lib/colors'; diff --git a/assets/templates/login.html b/assets/templates/login.html index 356ad22..94568bd 100644 --- a/assets/templates/login.html +++ b/assets/templates/login.html @@ -1,8 +1,14 @@ +<% var key, item %> +<% htmlWebpackPlugin.options.links = htmlWebpackPlugin.options.links || [] %> - Sails React + RoE - Login + <% for (item of htmlWebpackPlugin.options.links) { + if (typeof item === 'string' || item instanceof String) { item = { href: item, rel: 'stylesheet' } } %> + <%= key %>="<%= item[key] %>"<% } %> /><% + } %>
From 25b191d04cf3c570600c72ff069960c91c0ef770 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 28 Oct 2018 18:12:33 -0400 Subject: [PATCH 03/17] add new packages and finish webpack config --- package.json | 4 +++- webpack.config.js | 14 ++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3465816..ade1c88 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,15 @@ "html-webpack-plugin": "^3.2.0", "npm-run-all": "^4.1.3", "rimraf": "^2.6.2", + "sass-webpack-plugin": "^1.0.3", "webpack": "^4.23.1", "webpack-cli": "^3.1.2", "webpack-dev-server": "^3.1.10" }, "scripts": { - "start": "npm-run-all --parallel open:client lift", + "start": "npm run open:client", "start:debug": "npm-run-all --parallel open:client debug", + "start:prod": "npm-run-all --parallel build:prod lift", "open:client": "webpack-dev-server --mode development", "build": "npm run build:prod", "build:dev": "webpack --mode development", diff --git a/webpack.config.js b/webpack.config.js index 4b79938..73048ea 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,11 +1,14 @@ -const HtmlWebpackPlugin = require('html-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin') +const SassWebpackPlugin = require('sass-webpack-plugin') +const path = require('path') module.exports = { + mode: process.env.NODE_ENV || 'development', entry: { login: './assets/js/login.js' }, output: { - path: __dirname + '/.tmp/public', + path: path.join(__dirname, '/.tmp/public'), filename: '[name].bundle.js' }, module: { @@ -23,7 +26,10 @@ module.exports = { }, plugins: [ new HtmlWebpackPlugin({ - template: 'assets/templates/login.html' - }) + template: 'assets/templates/login.html', + links: [{ rel: 'stylesheet', type: 'text/css', href: 'login.css' }], + filename: path.join(__dirname, '/.tmp/public/login.html') + }), + new SassWebpackPlugin(['assets/styles/login.scss'], process.env.NODE_ENV), ] }; From a0a174541e889e05de98a517b5c81f47c7f741c4 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 28 Oct 2018 18:12:48 -0400 Subject: [PATCH 04/17] add view and route --- config/routes.js | 3 ++ views/layouts/layout.ejs | 111 +-------------------------------------- 2 files changed, 4 insertions(+), 110 deletions(-) diff --git a/config/routes.js b/config/routes.js index 6dd624b..ec3b8b6 100644 --- a/config/routes.js +++ b/config/routes.js @@ -25,6 +25,9 @@ module.exports.routes = { '/': { view: 'pages/homepage' }, + '/login': { + view: 'pages/login' + }, /*************************************************************************** * * diff --git a/views/layouts/layout.ejs b/views/layouts/layout.ejs index f9b3405..45165c7 100644 --- a/views/layouts/layout.ejs +++ b/views/layouts/layout.ejs @@ -1,110 +1 @@ - - - - New Sails App - - - - - - - - - - - - - - <%- body %> - - - - - - - - - - - - - - - - - - - - +<%- body %> From f6fcaa0fb07763f6764c8bbf5cc1abc4702e0ed8 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 13:46:14 -0400 Subject: [PATCH 05/17] standardjs --- assets/js/login.js | 70 +++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/assets/js/login.js b/assets/js/login.js index 77e79c6..011f910 100644 --- a/assets/js/login.js +++ b/assets/js/login.js @@ -6,62 +6,62 @@ import Carousel, {CarouselItem} from './containers/Carousel' const App = () => { // I will tidy this later return ( -
-
+
+
- + -
+

Sign in

-
- -
- -
-
+
+ +
+ +
+
- -
- Create account -
-
- Sign in with your Google account + -
+

Welcome

-
- user profile image -
- firstname lastname - x@y.z +
+ user profile image +
+ firstname lastname + x@y.z
- Not you? + Not you?
-
- -
- -
-
+
+ +
+ +
+
- -
- Forgot password? -
From 9dfd47ef11fff31d736148da32a7cfd71cabbd55 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 15:30:44 -0400 Subject: [PATCH 06/17] add stylesheets for carousel and login page --- assets/js/components/UnderlineInput.js | 15 +++ assets/js/containers/Carousel.js | 37 +++++- assets/js/login.js | 139 ++++++++++++----------- assets/styles/lib/colors.scss | 2 - assets/styles/lib/default.scss | 92 ++++++++++++++- assets/styles/lib/vars.scss | 26 +++++ assets/styles/login.scss | 2 +- assets/styles/shared/auth.scss | 24 ++++ assets/styles/shared/carousel.scss | 121 ++++++++++++++++++++ assets/styles/shared/underlineinput.scss | 87 ++++++++++++++ 10 files changed, 472 insertions(+), 73 deletions(-) create mode 100644 assets/js/components/UnderlineInput.js delete mode 100644 assets/styles/lib/colors.scss create mode 100644 assets/styles/lib/vars.scss create mode 100644 assets/styles/shared/auth.scss create mode 100644 assets/styles/shared/carousel.scss create mode 100644 assets/styles/shared/underlineinput.scss diff --git a/assets/js/components/UnderlineInput.js b/assets/js/components/UnderlineInput.js new file mode 100644 index 0000000..289218f --- /dev/null +++ b/assets/js/components/UnderlineInput.js @@ -0,0 +1,15 @@ +import React from 'react' + +import STYLE from '../../styles/shared/underlineinput.scss' + +const UnderlineInput = props => ( +
+ +
+ +
+
+
+) + +export default UnderlineInput diff --git a/assets/js/containers/Carousel.js b/assets/js/containers/Carousel.js index 6bbf352..741afba 100644 --- a/assets/js/containers/Carousel.js +++ b/assets/js/containers/Carousel.js @@ -1,10 +1,22 @@ import React from 'react' +import STYLE from '../../styles/shared/carousel.scss' class Carousel extends React.Component { + constructor () { + super() + this.getWidth = this.getWidth.bind(this) + this.getOffset = this.getOffset.bind(this) + } + getWidth () { + return this.props.children.length * 450 + } + getOffset () { + return -this.props.position * 450 + } render () { return ( -
-
+
+
{this.props.children}
@@ -13,10 +25,27 @@ class Carousel extends React.Component { } const CarouselItem = props => ( -
{props.children}
+
+
+

{props.header}

+ {props.headerExtraContent} +
+ {props.inputs} + {props.error} +
+ {props.smallButton} + +
+ {props.footer && + } +
) export default Carousel export { CarouselItem -} \ No newline at end of file +} diff --git a/assets/js/login.js b/assets/js/login.js index 011f910..a1e9d74 100644 --- a/assets/js/login.js +++ b/assets/js/login.js @@ -2,73 +2,82 @@ import React from 'react' import ReactDOM from 'react-dom' import Progress from './components/Progress' import Carousel, {CarouselItem} from './containers/Carousel' +import UnderlineInput from './components/UnderlineInput' -const App = () => { - // I will tidy this later - return ( -
-
- - - -
-

Sign in

-
-
- -
- -
-
-
-
-
- -
- Create account - -
- - - -
-

Welcome

-
- user profile image -
- firstname lastname - x@y.z -
- Not you? -
-
-
- -
- -
-
-
-
-
- -
- Forgot password? - -
- - +import STYLE from '../styles/login.scss' + +class App extends React.Component { + constructor () { + super() + this.state = { + carouselPosition: 0, + emailError: null, + passwordError: null, + user: { + email: 'x@y.z', + image: 'https://placehold.it/50x50' + } + } + + this.getEmailInputs = this.getEmailInputs.bind(this) + this.getPasswordInputs = this.getPasswordInputs.bind(this) + this.getPasswordHeader = this.getPasswordHeader.bind(this) + } + getEmailInputs () { + return [ + + ] + } + getPasswordInputs () { + return ( + + ) + } + getPasswordHeader () { + return ( +
+ user profile image +
+ {/* firstname lastname */} + {this.state.user.email} +
+ Not you?
-
- ) + ) + } + render () { + return ( +
+
+ + + + + + +
+
+ ) + } } ReactDOM.render(, document.getElementById('root')) diff --git a/assets/styles/lib/colors.scss b/assets/styles/lib/colors.scss deleted file mode 100644 index 9cb7c5f..0000000 --- a/assets/styles/lib/colors.scss +++ /dev/null @@ -1,2 +0,0 @@ -$text: rgba(0,0,0,.87); - diff --git a/assets/styles/lib/default.scss b/assets/styles/lib/default.scss index da45d93..4a1276f 100644 --- a/assets/styles/lib/default.scss +++ b/assets/styles/lib/default.scss @@ -1,7 +1,97 @@ +@import 'vars'; + html, -body { +body, +#root, +.root-container { height: 100%; width: 100%; margin: 0; padding: 0; + background: $background-1; + font-family: sans-serif; +} + +* { + box-sizing: border-box; +} + +.flex-container { + display: flex; + + &.flex-center { + align-items: center; + justify-content: center; + flex-direction: column; + } + &.flex-horizintal { + flex-direction: row; + } + &.flex-vertical { + flex-direction: column; + } + .flex { + flex: 1; + } +} + +.btn { + position: relative; + height: 36px; + padding: 0 20px; + background: $accent-2; + color: white; + border: none; + border-radius: 3px; + outline: none; + font-size: 0.9rem; + text-transform: uppercase; + box-shadow: $shadow-1; + transition: box-shadow .15s ease-in-out; + cursor: pointer; + user-select: none; + + &:focus{ + outline: initial; + } + &:hover { + box-shadow: $shadow-3; + } + &:active { + box-shadow: $shadow-2; + } + &.btn-with-icon { + padding: 0 20px 0 0; + + i { + font-size: 1.5em; + height: 30px; + width: 30px; + vertical-align: middle; + margin: 0 10px; + } + } + &.btn-social-signin { + text-transform: none; + + &.btn-google { + background: #dd4b39; + } + } + &.btn-clear { + background: transparent; + color: $text-dark-1; + box-shadow: none; + border: 1px solid $accent-2; + + &:hover { + box-shadow: $shadow-2; + } + } + & + .btn { + margin-left: 6px; + } +} +a:hover { + text-decoration: underline !important; } diff --git a/assets/styles/lib/vars.scss b/assets/styles/lib/vars.scss new file mode 100644 index 0000000..eeb2e6c --- /dev/null +++ b/assets/styles/lib/vars.scss @@ -0,0 +1,26 @@ +$shadow-0: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2); +$shadow-1: 0 1.5px 4px rgba(0, 0, 0, 0.24), 0 1.5px 6px rgba(0, 0, 0, 0.12); +$shadow-2: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); +$shadow-3: 0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19); +$shadow-4: 0 10px 20px rgba(0, 0, 0, 0.22), 0 14px 56px rgba(0, 0, 0, 0.25); +$shadow-5: 0 15px 24px rgba(0, 0, 0, 0.22), 0 19px 76px rgba(0, 0, 0, 0.3); + +$transition: cubic-bezier(0.23, 0.54, 0.19, 0.99); +$transition-2: cubic-bezier(0.08, 0.54, 0.45, 0.91); + +$black-1: rgba(0,0,0,.87); +$black-2: rgba(0,0,0,.54); +$black-3: rgba(0,0,0,.38); +$black-4: rgba(0,0,0,.12); +$black-5: rgba(0,0,0,.07); + +$auth-width: 450px; + +$background-1: #f2f2f2; +$background-2: white; + +$text-dark-1: $black-1; +$text-dark-2: $black-2; + +$accent-1: #4423c4; +$accent-2: #4460c4; diff --git a/assets/styles/login.scss b/assets/styles/login.scss index d8f3ddf..3a7522b 100644 --- a/assets/styles/login.scss +++ b/assets/styles/login.scss @@ -1,2 +1,2 @@ @import 'lib/default'; -@import 'lib/colors'; +@import 'shared/auth'; diff --git a/assets/styles/shared/auth.scss b/assets/styles/shared/auth.scss new file mode 100644 index 0000000..1f61248 --- /dev/null +++ b/assets/styles/shared/auth.scss @@ -0,0 +1,24 @@ +@import '../lib/vars'; + +#root:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 45%; + background: $accent-1; + z-index: 0; +} + +.window { + position: relative; + height: 500px; + width: $auth-width; + background: $background-2; + box-shadow: $shadow-0; + z-index: 1; + padding: 40px 0; + overflow: hidden; + border-radius: 3px; +} diff --git a/assets/styles/shared/carousel.scss b/assets/styles/shared/carousel.scss new file mode 100644 index 0000000..74a86ba --- /dev/null +++ b/assets/styles/shared/carousel.scss @@ -0,0 +1,121 @@ +@import '../lib/vars'; + +.carousel-container { + position: relative; + height: 100%; + width: 100%; + overflow-x: hidden; + padding: 0 0 20px 0; + opacity: 1; + transition: opacity .2s $transition; + + .carousel { + position: absolute; + height: 100%; + display: flex; + + .carousel-item { + // width: $auth-width; + flex: 1; + height: 100%; + display: inline-block; + // float: left; + margin: 0 40px; + position: relative; + + header { + height: 128px; + + h1 { + font-weight: normal; + margin: 0; + } + .account-banner { + height: 50px; + margin-top: 10px; + line-height: 50px; + display: flex; + + img { + height: 50px; + width: 50px; + border-radius: 50%; + vertical-align: middle; + } + .info-stack { + flex: 1; + padding: 0 10px; + height: 100%; + line-height: 16px; + .name { + display: inline-block; + color: $text-dark-1; + width: 100%; + } + .email { + display: inline-block; + color: $text-dark-2; + width: 100%; + } + .hidden { + display: none; + } + } + a { + color: $accent-2; + text-decoration: none; + } + p { + line-height: initial; + } + } + } + footer { + text-align: right; + position: absolute; + bottom: 20px; + width: 100%; + margin: 0; + color: $text-dark-2; + + .btn { + margin-left: 20px; + } + a { + color: $accent-2; + text-decoration: none; + } + } + .button-row { + height: 50px; + line-height: 50px; + width: 100%; + text-align: right; + + a { + color: $accent-2; + text-decoration: none; + float: left; + } + } + &.working { + pointer-events: none; + + .input-carousel { + opacity: 0.5; + } + .progress { + height: 4px; + } + } + @media screen and (max-width: 600px) { + & { + width: 100%; + height: 100%; + // border-radius: 0; + box-shadow: none; + } + } + } + } +} diff --git a/assets/styles/shared/underlineinput.scss b/assets/styles/shared/underlineinput.scss new file mode 100644 index 0000000..aff80b8 --- /dev/null +++ b/assets/styles/shared/underlineinput.scss @@ -0,0 +1,87 @@ +@import '../lib/vars'; + +.underlined-input, +.underlined-input-readonly { + width: 100%; + height: 46px; + padding: 14px 0 0 0; + margin: 0 0 8px 0; + line-height: 26px; + position: relative; + + $transition-time: .3s; + + input { + position: absolute; + background: transparent; + border: none; + outline: none; + height: 26px; + width: 100%; + font-size: 1rem; + bottom: 0; + + & + .reacts-to { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 46px; + pointer-events: none; + + label { + position: absolute; + top: 20px; + font-size: 0.95rem; + color: $black-2; + pointer-events: none; + transition: all $transition-time $transition; + } + .underline { + position: absolute; + bottom: 0; + width: 100%; + height: 1px; + background: $black-4; + + &:before { + content: ''; + position: absolute; + left: 50%; + top: 0; + width: 0; + height: 2px; + background: $accent-2; + transition: left $transition-time $transition, + width $transition-time $transition; + } + } + } + &:focus + .reacts-to, + &:active + .reacts-to, + &.has-content + .reacts-to { + label { + top: 0; + font-size: 0.7rem; + line-height: 14px; + color: $accent-1; + } + .underline:before { + width: 100%; + left: 0; + } + } + &.invalid:focus + .reacts-to, + &.invalid:active + .reacts-to, + &.invalid.has-content + .reacts-to { + label { + color: #e53935; + } + } + &.invalid + .reacts-to { + .underline { + background: #e53935; + } + } + } +} From c3bd9e9d05981df9736c8aba7f823ea82e29c16a Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 15:31:00 -0400 Subject: [PATCH 07/17] switch to sass-loader --- package.json | 5 +++++ webpack.config.js | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ade1c88..31fa098 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,15 @@ "@babel/preset-react": "^7.0.0", "@sailshq/eslint": "^4.19.3", "babel-loader": "^8.0.4", + "css-loader": "^1.0.1", "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.4", + "node-sass": "^4.9.4", "npm-run-all": "^4.1.3", "rimraf": "^2.6.2", + "sass-loader": "^7.1.0", "sass-webpack-plugin": "^1.0.3", + "style-loader": "^0.23.1", "webpack": "^4.23.1", "webpack-cli": "^3.1.2", "webpack-dev-server": "^3.1.10" diff --git a/webpack.config.js b/webpack.config.js index 73048ea..0b84c59 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,6 @@ const HtmlWebpackPlugin = require('html-webpack-plugin') -const SassWebpackPlugin = require('sass-webpack-plugin') +// const SassWebpackPlugin = require('sass-webpack-plugin') +const MiniCssExtractPlugin = require('mini-css-extract-plugin') const path = require('path') module.exports = { @@ -19,8 +20,12 @@ module.exports = { exclude: /node_modules/ }, { - use: ['style-loader', 'css-loader'], - test: /\.css$/ + test: /\.scss$/, + use: [ + process.env.NODE_ENV !== 'production' ? 'style-loader' : MiniCssExtractPlugin.loader, + 'css-loader', + 'sass-loader' + ] } ] }, @@ -30,6 +35,9 @@ module.exports = { links: [{ rel: 'stylesheet', type: 'text/css', href: 'login.css' }], filename: path.join(__dirname, '/.tmp/public/login.html') }), - new SassWebpackPlugin(['assets/styles/login.scss'], process.env.NODE_ENV), + new MiniCssExtractPlugin({ + filename: '[name].css' + }) + // new SassWebpackPlugin(['assets/styles/login.scss'], process.env.NODE_ENV) ] -}; +} From 1ffae0e34dc72e15da24aa6c69619957a28c5fc8 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 15:31:41 -0400 Subject: [PATCH 08/17] remove sass-webpack-plugin --- package.json | 1 - webpack.config.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/package.json b/package.json index 31fa098..aa04c2f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "npm-run-all": "^4.1.3", "rimraf": "^2.6.2", "sass-loader": "^7.1.0", - "sass-webpack-plugin": "^1.0.3", "style-loader": "^0.23.1", "webpack": "^4.23.1", "webpack-cli": "^3.1.2", diff --git a/webpack.config.js b/webpack.config.js index 0b84c59..a007686 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,4 @@ const HtmlWebpackPlugin = require('html-webpack-plugin') -// const SassWebpackPlugin = require('sass-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const path = require('path') @@ -38,6 +37,5 @@ module.exports = { new MiniCssExtractPlugin({ filename: '[name].css' }) - // new SassWebpackPlugin(['assets/styles/login.scss'], process.env.NODE_ENV) ] } From 3bc4c863ab411ece82585fdd976812b9b2ac0c5c Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 17:07:12 -0400 Subject: [PATCH 09/17] add actions, reducers, more pages to login --- assets/js/actions/login.js | 22 ++++++++++ assets/js/components/UnderlineInput.js | 8 +++- assets/js/containers/Carousel.js | 4 +- assets/js/login.js | 61 ++++++++++++++++++++------ assets/js/reducers/login.js | 27 ++++++++++++ assets/styles/shared/auth.scss | 3 +- assets/styles/shared/carousel.scss | 2 +- 7 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 assets/js/actions/login.js create mode 100644 assets/js/reducers/login.js diff --git a/assets/js/actions/login.js b/assets/js/actions/login.js new file mode 100644 index 0000000..461501a --- /dev/null +++ b/assets/js/actions/login.js @@ -0,0 +1,22 @@ +const ACTIONS = { + set_user: 'set_user', + set_password: 'set_password', + set_carousel: 'set_carousel' +} + +export default ACTIONS + +export const setEmail = email => ({ + type: ACTIONS.set_user, + data: email +}) + +export const setPassword = pass => ({ + type: ACTIONS.set_password, + data: pass +}) + +export const setCarousel = pos => ({ + type: ACTIONS.set_carousel, + data: pos +}) diff --git a/assets/js/components/UnderlineInput.js b/assets/js/components/UnderlineInput.js index 289218f..daec394 100644 --- a/assets/js/components/UnderlineInput.js +++ b/assets/js/components/UnderlineInput.js @@ -4,7 +4,13 @@ import STYLE from '../../styles/shared/underlineinput.scss' const UnderlineInput = props => (
- +
diff --git a/assets/js/containers/Carousel.js b/assets/js/containers/Carousel.js index 741afba..4115685 100644 --- a/assets/js/containers/Carousel.js +++ b/assets/js/containers/Carousel.js @@ -33,8 +33,8 @@ const CarouselItem = props => ( {props.inputs} {props.error}
- {props.smallButton} -
diff --git a/assets/js/login.js b/assets/js/login.js index a1e9d74..2045afb 100644 --- a/assets/js/login.js +++ b/assets/js/login.js @@ -3,6 +3,9 @@ import ReactDOM from 'react-dom' import Progress from './components/Progress' import Carousel, {CarouselItem} from './containers/Carousel' import UnderlineInput from './components/UnderlineInput' +import reducer from './reducers/login' + +import {setEmail, setPassword, setCarousel} from './actions/login' import STYLE from '../styles/login.scss' @@ -10,45 +13,61 @@ class App extends React.Component { constructor () { super() this.state = { - carouselPosition: 0, + carouselPosition: 1, emailError: null, passwordError: null, user: { - email: 'x@y.z', - image: 'https://placehold.it/50x50' + email: '', + password: '' } } + this.dispatch = this.dispatch.bind(this) this.getEmailInputs = this.getEmailInputs.bind(this) this.getPasswordInputs = this.getPasswordInputs.bind(this) this.getPasswordHeader = this.getPasswordHeader.bind(this) } + dispatch (action) { + if (!action) throw new Error('dispatch: missing action') + if (action instanceof Function) { + action(this.dispatch, () => this.state) + } else { + const changes = reducer(this.state, action) + if (!changes || !Object.keys(changes).length) return + this.setState({ + ...changes + }) + } + } getEmailInputs () { return [ + type='text' + name='email' + placeholder='Email' + onChange={e => this.dispatch(setEmail(e.target.value))} + value={this.state.user.email} /> ] } getPasswordInputs () { - return ( + return [ - ) + onChange={e => this.dispatch(setPassword(e.target.value))} + value={this.state.user.password} /> + ] } getPasswordHeader () { return ( -
- user profile image -
- {/* firstname lastname */} +
+
{this.state.user.email}
- Not you? + this.dispatch(setCarousel(1))}>Not you?
) } @@ -58,12 +77,24 @@ class App extends React.Component {
+ null} + smallButton='Have an account?' + onSmallButtonClick={() => this.dispatch(setCarousel(1))} + footer='Sign up with your Google account' /> + this.dispatch(setCarousel(2))} smallButton='Create account' + onSmallButtonClick={() => this.dispatch(setCarousel(0))} footer='Sign in with your Google account' /> + onButtonClick={() => null} + smallButton='Forgot password?' + onSmallButtonClick={() => this.dispatch(setCarousel(3))} />
diff --git a/assets/js/reducers/login.js b/assets/js/reducers/login.js new file mode 100644 index 0000000..f20792b --- /dev/null +++ b/assets/js/reducers/login.js @@ -0,0 +1,27 @@ +import Actions from '../actions/login' + +const reducer = (state = {}, action) => { + const {type, data} = action + switch (type) { + case Actions.set_user: + return { + user: { + ...state.user, + email: data + } + } + case Actions.set_password: + return { + user: { + ...state.user, + password: data + } + } + case Actions.set_carousel: + return { + carouselPosition: data + } + } +} + +export default reducer diff --git a/assets/styles/shared/auth.scss b/assets/styles/shared/auth.scss index 1f61248..8c5f619 100644 --- a/assets/styles/shared/auth.scss +++ b/assets/styles/shared/auth.scss @@ -8,6 +8,7 @@ width: 100%; height: 45%; background: $accent-1; + box-shadow: $shadow-0; z-index: 0; } @@ -16,7 +17,7 @@ height: 500px; width: $auth-width; background: $background-2; - box-shadow: $shadow-0; + box-shadow: $shadow-1; z-index: 1; padding: 40px 0; overflow: hidden; diff --git a/assets/styles/shared/carousel.scss b/assets/styles/shared/carousel.scss index 74a86ba..acb2736 100644 --- a/assets/styles/shared/carousel.scss +++ b/assets/styles/shared/carousel.scss @@ -13,6 +13,7 @@ position: absolute; height: 100%; display: flex; + transition: left 0.4s $transition; .carousel-item { // width: $auth-width; @@ -112,7 +113,6 @@ & { width: 100%; height: 100%; - // border-radius: 0; box-shadow: none; } } From b0aa8682e7e4191ff7b4a59d39251dabe09effc7 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 18:39:20 -0400 Subject: [PATCH 10/17] all slides --- assets/js/actions/login.js | 40 ++++++++++++++++++++- assets/js/components/Progress.js | 4 +-- assets/js/containers/Carousel.js | 11 ++++-- assets/js/login.js | 26 +++++++++----- assets/js/reducers/login.js | 17 +++++++++ assets/styles/lib/default.scss | 44 ++++++++++++++++++++++++ assets/styles/lib/vars.scss | 3 ++ assets/styles/shared/auth.scss | 25 ++++++++++++++ assets/styles/shared/carousel.scss | 6 ++++ assets/styles/shared/underlineinput.scss | 6 ++-- 10 files changed, 165 insertions(+), 17 deletions(-) diff --git a/assets/js/actions/login.js b/assets/js/actions/login.js index 461501a..1784757 100644 --- a/assets/js/actions/login.js +++ b/assets/js/actions/login.js @@ -1,11 +1,19 @@ const ACTIONS = { + set_working: 'set_working', set_user: 'set_user', set_password: 'set_password', - set_carousel: 'set_carousel' + set_carousel: 'set_carousel', + set_error: 'set_error', + clear_error: 'clear_error' } export default ACTIONS +export const setWorking = working => ({ + type: ACTIONS.set_working, + data: working +}) + export const setEmail = email => ({ type: ACTIONS.set_user, data: email @@ -20,3 +28,33 @@ export const setCarousel = pos => ({ type: ACTIONS.set_carousel, data: pos }) + +export const setError = data => ({ + type: ACTIONS.set_error, + data: data +}) + +export const clearError = () => ({ + type: ACTIONS.clear_error +}) + +export const checkEmail = email => (dispatch, getState) => { + // dispatch(setWorking(true)) + if (/^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/.test(email)) { + dispatch(setCarousel(2)) + } else { + dispatch(setError({ + type: 'email', + error: 'Please enter a valid email address.' + })) + } + // dispatch(setWorking(false)) +} + +export const checkPassword = (email, password) => (dispatch, getState) => { + dispatch(setWorking(true)) + + // do email + password check + + dispatch(setWorking(false)) +} diff --git a/assets/js/components/Progress.js b/assets/js/components/Progress.js index 8a9efff..7f45c20 100644 --- a/assets/js/components/Progress.js +++ b/assets/js/components/Progress.js @@ -1,8 +1,8 @@ import React from 'react' const Progress = props => ( -
-
+
+
) diff --git a/assets/js/containers/Carousel.js b/assets/js/containers/Carousel.js index 4115685..e7bfdc3 100644 --- a/assets/js/containers/Carousel.js +++ b/assets/js/containers/Carousel.js @@ -24,8 +24,13 @@ class Carousel extends React.Component { } } +function handleClick (e, fn) { + e.preventDefault() + fn(e) +} + const CarouselItem = props => ( -
+
handleClick(e, props.onButtonClick)}>

{props.header}

{props.headerExtraContent} @@ -34,7 +39,7 @@ const CarouselItem = props => ( {props.error}
{props.smallButton} -
@@ -42,7 +47,7 @@ const CarouselItem = props => ( } -
+ ) export default Carousel diff --git a/assets/js/login.js b/assets/js/login.js index 2045afb..b8e11d7 100644 --- a/assets/js/login.js +++ b/assets/js/login.js @@ -5,7 +5,7 @@ import Carousel, {CarouselItem} from './containers/Carousel' import UnderlineInput from './components/UnderlineInput' import reducer from './reducers/login' -import {setEmail, setPassword, setCarousel} from './actions/login' +import {setEmail, setPassword, setCarousel, checkEmail, checkPassword} from './actions/login' import STYLE from '../styles/login.scss' @@ -14,12 +14,13 @@ class App extends React.Component { super() this.state = { carouselPosition: 1, - emailError: null, - passwordError: null, + emailError: '', + passwordError: '', user: { email: '', password: '' - } + }, + working: false } this.dispatch = this.dispatch.bind(this) @@ -74,8 +75,8 @@ class App extends React.Component { render () { return (
-
- +
+ this.dispatch(setCarousel(2))} + onButtonClick={() => this.dispatch(checkEmail(this.state.user.email))} smallButton='Create account' onSmallButtonClick={() => this.dispatch(setCarousel(0))} footer='Sign in with your Google account' /> @@ -103,9 +104,18 @@ class App extends React.Component { inputs={this.getPasswordInputs()} error={this.state.passwordError} button='Sign in' - onButtonClick={() => null} + onButtonClick={() => this.dispatch(checkPassword(this.state.user.email, this.state.user.password))} smallButton='Forgot password?' onSmallButtonClick={() => this.dispatch(setCarousel(3))} /> + + null} + smallButton='Log in' + onSmallButtonClick={() => this.dispatch(setCarousel(1))} />
diff --git a/assets/js/reducers/login.js b/assets/js/reducers/login.js index f20792b..bdd6afc 100644 --- a/assets/js/reducers/login.js +++ b/assets/js/reducers/login.js @@ -21,6 +21,23 @@ const reducer = (state = {}, action) => { return { carouselPosition: data } + case Actions.set_working: + return { + working: data + } + case Actions.set_error: + switch (data.type) { + case 'email': + return { + emailError: data.error + } + default: return {} + } + case Actions.clear_error: + return { + emailError: '', + passwordError: '' + } } } diff --git a/assets/styles/lib/default.scss b/assets/styles/lib/default.scss index 4a1276f..a300eba 100644 --- a/assets/styles/lib/default.scss +++ b/assets/styles/lib/default.scss @@ -95,3 +95,47 @@ body, a:hover { text-decoration: underline !important; } + +@-webkit-keyframes indeterminate {0% {left: -35%; right: 100%; } 60% {left: 100%; right: -90%; } 100% {left: 100%; right: -90%; } } @keyframes indeterminate {0% {left: -35%; right: 100%; } 60% {left: 100%; right: -90%; } 100% {left: 100%; right: -90%; } } @-webkit-keyframes indeterminate-short {0% {left: -200%; right: 100%; } 60% {left: 107%; right: -8%; } 100% {left: 107%; right: -8%; } } @keyframes indeterminate-short {0% {left: -200%; right: 100%; } 60% {left: 107%; right: -8%; } 100% {left: 107%; right: -8%; } } + +.progress { + position: absolute; + display: inline-block; + top: 0; + height: 0; + width: 100%; + background-color: $accent-3; + background-clip: padding-box; + margin: 0; + overflow: hidden; + transition: height .2s $transition; + + .indeterminate { + background-color: $accent-2; + + &:before { + content: ''; + position: absolute; + background-color: inherit; + top: 0; + left: 0; + bottom: 0; + will-change: left, right; + -webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite; + animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite; + } + &:after { + content: ''; + position: absolute; + background-color: inherit; + top: 0; + left: 0; + bottom: 0; + will-change: left, right; + -webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite; + animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite; + -webkit-animation-delay: 1.15s; + animation-delay: 1.15s; + } + } +} diff --git a/assets/styles/lib/vars.scss b/assets/styles/lib/vars.scss index eeb2e6c..ca34ecc 100644 --- a/assets/styles/lib/vars.scss +++ b/assets/styles/lib/vars.scss @@ -24,3 +24,6 @@ $text-dark-2: $black-2; $accent-1: #4423c4; $accent-2: #4460c4; +$accent-3: #D4DBF1; + +$red: #FE4C52; \ No newline at end of file diff --git a/assets/styles/shared/auth.scss b/assets/styles/shared/auth.scss index 8c5f619..5c84ea7 100644 --- a/assets/styles/shared/auth.scss +++ b/assets/styles/shared/auth.scss @@ -22,4 +22,29 @@ padding: 40px 0; overflow: hidden; border-radius: 3px; + + &:before { + opacity: 0; + z-index: 2; + position: absolute; + content: ''; + height: 100%; + width: 100%; + top: 4px; + left: 0; + background: rgba(255,255,255,.50); + transition: .2s opacity $transition; + pointer-events: none; + } + + &.working { + & > .progress { + top: 0; + height: 4px; + } + &:before { + opacity: 1; + pointer-events: initial; + } + } } diff --git a/assets/styles/shared/carousel.scss b/assets/styles/shared/carousel.scss index acb2736..0139b8f 100644 --- a/assets/styles/shared/carousel.scss +++ b/assets/styles/shared/carousel.scss @@ -29,6 +29,7 @@ h1 { font-weight: normal; + font-size: 1.5rem; margin: 0; } .account-banner { @@ -71,6 +72,11 @@ } } } + .carousel-error { + color: $red; + display: inline-block; + font-size: 0.9rem; + } footer { text-align: right; position: absolute; diff --git a/assets/styles/shared/underlineinput.scss b/assets/styles/shared/underlineinput.scss index aff80b8..a539719 100644 --- a/assets/styles/shared/underlineinput.scss +++ b/assets/styles/shared/underlineinput.scss @@ -64,7 +64,7 @@ top: 0; font-size: 0.7rem; line-height: 14px; - color: $accent-1; + color: $accent-2; } .underline:before { width: 100%; @@ -75,12 +75,12 @@ &.invalid:active + .reacts-to, &.invalid.has-content + .reacts-to { label { - color: #e53935; + color: $red; } } &.invalid + .reacts-to { .underline { - background: #e53935; + background: $red; } } } From 872d3ca292a46de5480b9902a47816abdbdf4b1f Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 18:39:29 -0400 Subject: [PATCH 11/17] alias page to login --- config/routes.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/routes.js b/config/routes.js index ec3b8b6..d6f29a6 100644 --- a/config/routes.js +++ b/config/routes.js @@ -28,6 +28,9 @@ module.exports.routes = { '/login': { view: 'pages/login' }, + '/register': { + view: 'pages/login' + }, /*************************************************************************** * * @@ -40,7 +43,6 @@ module.exports.routes = { * * ***************************************************************************/ - // ╔═╗╔═╗╦ ╔═╗╔╗╔╔╦╗╔═╗╔═╗╦╔╗╔╔╦╗╔═╗ // ╠═╣╠═╝║ ║╣ ║║║ ║║╠═╝║ ║║║║║ ║ ╚═╗ // ╩ ╩╩ ╩ ╚═╝╝╚╝═╩╝╩ ╚═╝╩╝╚╝ ╩ ╚═╝ From bc901a9f9e77ccf11932e7867ecc84ae879819a0 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 19:23:37 -0400 Subject: [PATCH 12/17] add babel polyfill --- .babelrc | 11 +++++++++-- package.json | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.babelrc b/.babelrc index 653952e..09acf26 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,11 @@ { - "presets": ["@babel/preset-env", "@babel/preset-react"], - "plugins": ["@babel/plugin-proposal-object-rest-spread"] + "presets": [ + [ + "@babel/preset-env", { + "useBuiltIns": "usage" + } + ], + "@babel/preset-react" + ], + "plugins": ["@babel/plugin-proposal-object-rest-spread"], } diff --git a/package.json b/package.json index aa04c2f..7a775c3 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "devDependencies": { "@babel/core": "^7.1.2", "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/polyfill": "^7.0.0", "@babel/preset-env": "^7.1.0", "@babel/preset-react": "^7.0.0", "@sailshq/eslint": "^4.19.3", From 385d3ed169a4d2b1f2fbdc9695ae7e5c8312d3fd Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 19:23:46 -0400 Subject: [PATCH 13/17] add ajax class --- assets/js/actions/login.js | 33 ++++- assets/js/components/Progress.js | 2 + assets/js/components/UnderlineInput.js | 2 + assets/js/containers/Carousel.js | 2 + assets/js/lib/Ajax.js | 159 +++++++++++++++++++++++++ assets/js/login.js | 2 + assets/js/reducers/login.js | 2 + 7 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 assets/js/lib/Ajax.js diff --git a/assets/js/actions/login.js b/assets/js/actions/login.js index 1784757..f4ef2cb 100644 --- a/assets/js/actions/login.js +++ b/assets/js/actions/login.js @@ -1,3 +1,7 @@ +'use strict' + +import Ajax from '../lib/Ajax' + const ACTIONS = { set_working: 'set_working', set_user: 'set_user', @@ -38,8 +42,14 @@ export const clearError = () => ({ type: ACTIONS.clear_error }) +export const setLoggedIn = (data) => (dispatch, getState) => { + document.localStorage.setItem('roe-token', JSON.stringify(data)) + window.location.href = '/app' +} + export const checkEmail = email => (dispatch, getState) => { // dispatch(setWorking(true)) + dispatch(clearError()) if (/^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/.test(email)) { dispatch(setCarousel(2)) } else { @@ -51,10 +61,27 @@ export const checkEmail = email => (dispatch, getState) => { // dispatch(setWorking(false)) } -export const checkPassword = (email, password) => (dispatch, getState) => { +export const checkPassword = (email, password) => async (dispatch, getState) => { dispatch(setWorking(true)) // do email + password check - - dispatch(setWorking(false)) + try { + const res = await Ajax.post({ + url: '/api/token', + data: { + grant_type: 'credentials', + email, + password + } + }) + dispatch(setLoggedIn(res)) + // dispatch(setWorking(false)) + } catch (e) { + console.log(e.toString()) + dispatch(setError({ + type: 'password', + error: e.toString() + })) + dispatch(setWorking(false)) + } } diff --git a/assets/js/components/Progress.js b/assets/js/components/Progress.js index 7f45c20..e3177c4 100644 --- a/assets/js/components/Progress.js +++ b/assets/js/components/Progress.js @@ -1,3 +1,5 @@ +'use strict' + import React from 'react' const Progress = props => ( diff --git a/assets/js/components/UnderlineInput.js b/assets/js/components/UnderlineInput.js index daec394..0281a6a 100644 --- a/assets/js/components/UnderlineInput.js +++ b/assets/js/components/UnderlineInput.js @@ -1,3 +1,5 @@ +'use strict' + import React from 'react' import STYLE from '../../styles/shared/underlineinput.scss' diff --git a/assets/js/containers/Carousel.js b/assets/js/containers/Carousel.js index e7bfdc3..3a5f4cc 100644 --- a/assets/js/containers/Carousel.js +++ b/assets/js/containers/Carousel.js @@ -1,3 +1,5 @@ +'use strict' + import React from 'react' import STYLE from '../../styles/shared/carousel.scss' diff --git a/assets/js/lib/Ajax.js b/assets/js/lib/Ajax.js new file mode 100644 index 0000000..e86e232 --- /dev/null +++ b/assets/js/lib/Ajax.js @@ -0,0 +1,159 @@ +/* global XMLHttpRequest FormData */ +'use strict' + +let ajaxcfg = {} + +class AjaxError extends Error { + constructor (reason, data, xhr) { + super(reason) + this.data = data + this.xhr = xhr + } +} + +export default class Ajax { + static async get (opts) { + if (!opts) opts = {} + opts.method = 'get' + return Ajax.ajax(opts) + } + static async post (opts) { + if (!opts) opts = {} + opts.method = 'post' + return Ajax.ajax(opts) + } + static async put (opts) { + if (!opts) opts = {} + opts.method = 'put' + return Ajax.ajax(opts) + } + static async patch (opts) { + if (!opts) opts = {} + opts.method = 'patch' + return Ajax.ajax(opts) + } + static async delete (opts) { + if (!opts) opts = {} + opts.method = 'delete' + return Ajax.ajax(opts) + } + static async head (opts) { + if (!opts) opts = {} + opts.method = 'head' + return Ajax.ajax(opts) + } + static async options (opts) { + if (!opts) opts = {} + opts.method = 'options' + return Ajax.ajax(opts) + } + static ajax (opts) { + return new Promise((resolve, reject) => { + if (!opts) reject(new Error('Missing required options parameter.')) + if (opts.method) { + if (!['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(opts.method.toLowerCase())) reject(new Error('opts.method must be one of: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.')) + opts.method = opts.method.toUpperCase() + } + + var xhr = opts.xhr || new XMLHttpRequest() + + var fd = null + var qs = '' + if (opts.data && opts.method.toLowerCase() !== 'get') { + fd = new FormData() + for (let key in opts.data) { + fd.append(key, opts.data[key]) + } + } else if (opts.data) { + qs += '?' + let params = [] + for (let key in opts.data) { + params.push([key, opts.data[key]].join('=')) + } + qs += params.join('&') + } + + xhr.onload = () => { + if (xhr.status !== 200) return xhr.onerror() + var data = xhr.response + resolve({ + data, + xhr + }) + } + xhr.onerror = () => { + var data = xhr.response + + // method not allowed + if (xhr.status === 405) { + reject(new AjaxError('405 Method Not Allowed', data, xhr)) + return + } + + try { + // if the access token is invalid, try to use the refresh token + var json = JSON.parse(data) + if (json.error === 'access_denied' && json.hint.includes('token') && json.hint.includes('invalid') && ajaxcfg.refresh_token) { + return Ajax.refresh(opts) + } else if (json.error === 'access_denied' && json.hint.includes('token') && json.hint.includes('revoked')) { + reject(new AjaxError('token revoked', data, xhr)) + } + } catch (e) { + reject(new AjaxError(e.toString(), data, xhr)) + } finally { + reject(new AjaxError(xhr.status, data, xhr)) + } + } + + xhr.open(opts.method || 'GET', opts.url + qs || window.location.href) + if (opts.headers) { + for (let key in opts.headers) xhr.setRequestHeader(key, opts.headers[key]) + } + if (ajaxcfg.access_token && !(opts.headers || {}).Authorization) xhr.setRequestHeader('Authorization', 'Bearer ' + ajaxcfg.access_token) + xhr.send(fd) + }) + } + static refresh (opts) { + return new Promise((resolve, reject) => { + var xhr = new XMLHttpRequest() + + var fd = new FormData() + const OAUTH_TOKEN_REQUEST = { + grant_type: 'refresh_token', + refresh_token: ajaxcfg.refresh_token, + client_id: 'foxfile', + client_secret: 1 + } + for (var key in OAUTH_TOKEN_REQUEST) { + fd.append(key, OAUTH_TOKEN_REQUEST[key]) + } + // try original request + xhr.onload = () => { + if (xhr.status !== 200) return xhr.onerror() + if (ajaxcfg.refresh) ajaxcfg.refresh(xhr.response) + var json = JSON.parse(xhr.response) + ajaxcfg.access_token = json.access_token + ajaxcfg.refresh_token = json.refresh_token + return Ajax.ajax(opts) + } + // if this fails, dont try again + xhr.onerror = () => { + var data = xhr.response + reject(new AjaxError(xhr.status, data, xhr)) + } + xhr.open('POST', ajaxcfg.refresh_url) + xhr.send(fd) + }) + } + static setTokenData (tokens) { + if (!tokens) throw new Error('Missing tokens.') + if (!tokens.access_token && !tokens.refresh_token && !tokens.refresh_url) throw new Error('Missing at least one of: access_token, refresh_token, refresh_url.') + if (tokens.access_token) ajaxcfg.access_token = tokens.access_token + if (tokens.refresh_token) ajaxcfg.refresh_token = tokens.refresh_token + if (tokens.refresh_url) ajaxcfg.refresh_url = tokens.refresh_url + return true + } + static onRefresh (func) { + ajaxcfg.refresh = func + } +} diff --git a/assets/js/login.js b/assets/js/login.js index b8e11d7..5f2e2dd 100644 --- a/assets/js/login.js +++ b/assets/js/login.js @@ -1,3 +1,5 @@ +'use strict' + import React from 'react' import ReactDOM from 'react-dom' import Progress from './components/Progress' diff --git a/assets/js/reducers/login.js b/assets/js/reducers/login.js index bdd6afc..4825226 100644 --- a/assets/js/reducers/login.js +++ b/assets/js/reducers/login.js @@ -1,3 +1,5 @@ +'use strict' + import Actions from '../actions/login' const reducer = (state = {}, action) => { From bc5485fbbcf65de89b06f2598a73cf6979775d86 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 19:35:27 -0400 Subject: [PATCH 14/17] handle error better --- assets/js/actions/login.js | 1 - assets/js/lib/Ajax.js | 3 +++ assets/js/login.js | 1 - assets/js/reducers/login.js | 4 ++++ config/http.js | 6 ++---- webpack.config.js | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/assets/js/actions/login.js b/assets/js/actions/login.js index f4ef2cb..1ce5828 100644 --- a/assets/js/actions/login.js +++ b/assets/js/actions/login.js @@ -77,7 +77,6 @@ export const checkPassword = (email, password) => async (dispatch, getState) => dispatch(setLoggedIn(res)) // dispatch(setWorking(false)) } catch (e) { - console.log(e.toString()) dispatch(setError({ type: 'password', error: e.toString() diff --git a/assets/js/lib/Ajax.js b/assets/js/lib/Ajax.js index e86e232..b6b9439 100644 --- a/assets/js/lib/Ajax.js +++ b/assets/js/lib/Ajax.js @@ -88,6 +88,9 @@ export default class Ajax { if (xhr.status === 405) { reject(new AjaxError('405 Method Not Allowed', data, xhr)) return + } else if (xhr.status === 404) { + reject(new AjaxError('404 Not Found', data, xhr)) + return } try { diff --git a/assets/js/login.js b/assets/js/login.js index 5f2e2dd..e6e6203 100644 --- a/assets/js/login.js +++ b/assets/js/login.js @@ -6,7 +6,6 @@ import Progress from './components/Progress' import Carousel, {CarouselItem} from './containers/Carousel' import UnderlineInput from './components/UnderlineInput' import reducer from './reducers/login' - import {setEmail, setPassword, setCarousel, checkEmail, checkPassword} from './actions/login' import STYLE from '../styles/login.scss' diff --git a/assets/js/reducers/login.js b/assets/js/reducers/login.js index 4825226..6495974 100644 --- a/assets/js/reducers/login.js +++ b/assets/js/reducers/login.js @@ -33,6 +33,10 @@ const reducer = (state = {}, action) => { return { emailError: data.error } + case 'password': + return { + passwordError: data.error + } default: return {} } case Actions.clear_error: diff --git a/config/http.js b/config/http.js index e9603bc..601ed57 100644 --- a/config/http.js +++ b/config/http.js @@ -64,7 +64,5 @@ module.exports.http = { // var middlewareFn = skipper({ strict: true }); // return middlewareFn; // })(), - - }, - -}; + } +} diff --git a/webpack.config.js b/webpack.config.js index a007686..4d357b2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -31,7 +31,7 @@ module.exports = { plugins: [ new HtmlWebpackPlugin({ template: 'assets/templates/login.html', - links: [{ rel: 'stylesheet', type: 'text/css', href: 'login.css' }], + links: process.env.NODE_ENV === 'production' ? [{ rel: 'stylesheet', type: 'text/css', href: 'login.css' }] : [], filename: path.join(__dirname, '/.tmp/public/login.html') }), new MiniCssExtractPlugin({ From 7fd86ca0ccc4363366cb8ff42ca34aec05ef1a26 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 19:35:49 -0400 Subject: [PATCH 15/17] rename index page --- config/routes.js | 2 +- views/pages/{homepage.ejs => index.ejs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename views/pages/{homepage.ejs => index.ejs} (100%) diff --git a/config/routes.js b/config/routes.js index d6f29a6..55635aa 100644 --- a/config/routes.js +++ b/config/routes.js @@ -23,7 +23,7 @@ module.exports.routes = { ***************************************************************************/ '/': { - view: 'pages/homepage' + view: 'pages/index' }, '/login': { view: 'pages/login' diff --git a/views/pages/homepage.ejs b/views/pages/index.ejs similarity index 100% rename from views/pages/homepage.ejs rename to views/pages/index.ejs From 53bafbea9b71e2817e8e5b6084967bf0bd398540 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 19:56:07 -0400 Subject: [PATCH 16/17] add link to work --- views/pages/index.ejs | 1 + 1 file changed, 1 insertion(+) diff --git a/views/pages/index.ejs b/views/pages/index.ejs index 68c9fa7..f43d01b 100644 --- a/views/pages/index.ejs +++ b/views/pages/index.ejs @@ -19,6 +19,7 @@ setTimeout(function sunrise () {

<%= __('A brand new app.') %>

You're looking at: <%= view.pathFromApp + '.' +view.ext %>

+

Go to Login: Here

    From af125ba5358f23f626ed52977e8a56aba5986dee Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2018 19:56:18 -0400 Subject: [PATCH 17/17] add better starts --- package.json | 2 ++ webpack.config.js | 78 ++++++++++++++++++++++++++--------------------- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 7a775c3..210043a 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,8 @@ "build:prod": "webpack --mode production", "clean": "rimraf .tmp && mkdirp .tmp/public", "lift": "sails lift", + "forever": "sudo NODE_ENV='production' ./node_modules/.bin/forever start app.js", + "stop": "./node_modules/.bin/forever stopall", "test": "npm run lint && npm run custom-tests && echo 'Done.'", "lint": "eslint . --max-warnings=0 --report-unused-disable-directives && echo '✔ Your .js files look good.'", "debug": "node --inspect app.js" diff --git a/webpack.config.js b/webpack.config.js index 4d357b2..232a891 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,41 +1,49 @@ const HtmlWebpackPlugin = require('html-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') +const webpack = require('webpack') const path = require('path') -module.exports = { - mode: process.env.NODE_ENV || 'development', - entry: { - login: './assets/js/login.js' - }, - output: { - path: path.join(__dirname, '/.tmp/public'), - filename: '[name].bundle.js' - }, - module: { - rules: [ - { - use: 'babel-loader', - test: /\.jsx?$/, - exclude: /node_modules/ - }, - { - test: /\.scss$/, - use: [ - process.env.NODE_ENV !== 'production' ? 'style-loader' : MiniCssExtractPlugin.loader, - 'css-loader', - 'sass-loader' - ] - } +module.exports = (env, argv) => { + const mode = argv.mode || 'development' + + return { + mode: mode || 'development', + entry: { + login: './assets/js/login.js' + }, + output: { + path: path.join(__dirname, '/.tmp/public'), + filename: '[name].bundle.js' + }, + module: { + rules: [ + { + use: 'babel-loader', + test: /\.jsx?$/, + exclude: /node_modules/ + }, + { + test: /\.scss$/, + use: [ + mode !== 'production' ? 'style-loader' : MiniCssExtractPlugin.loader, + 'css-loader', + 'sass-loader' + ] + } + ] + }, + plugins: [ + new HtmlWebpackPlugin({ + template: 'assets/templates/login.html', + links: mode === 'production' ? [{ rel: 'stylesheet', type: 'text/css', href: 'login.css' }] : [], + filename: path.join(__dirname, '/.tmp/public/login.html') + }), + new MiniCssExtractPlugin({ + filename: '[name].css' + }), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(mode) + }) ] - }, - plugins: [ - new HtmlWebpackPlugin({ - template: 'assets/templates/login.html', - links: process.env.NODE_ENV === 'production' ? [{ rel: 'stylesheet', type: 'text/css', href: 'login.css' }] : [], - filename: path.join(__dirname, '/.tmp/public/login.html') - }), - new MiniCssExtractPlugin({ - filename: '[name].css' - }) - ] + } }