From bbb34f17714164a767656599b6f0b1b313b17922 Mon Sep 17 00:00:00 2001 From: Kata Date: Tue, 19 Mar 2019 20:26:47 -0500 Subject: [PATCH] UI: add auth flow and related errors --- ui/components/discord-button.js | 62 ++++++++++++++++ ui/lib/redirect.js | 2 +- ui/pages/_error.js | 87 ++++------------------ ui/pages/auth/expired.js | 3 + ui/pages/auth/login.js | 125 ++++++++++++++++++++++++++++++++ ui/static/discord-logo.svg | 21 ++++++ 6 files changed, 226 insertions(+), 74 deletions(-) create mode 100644 ui/components/discord-button.js create mode 100644 ui/pages/auth/expired.js create mode 100644 ui/pages/auth/login.js create mode 100644 ui/static/discord-logo.svg diff --git a/ui/components/discord-button.js b/ui/components/discord-button.js new file mode 100644 index 0000000..a5b4b3b --- /dev/null +++ b/ui/components/discord-button.js @@ -0,0 +1,62 @@ +// @flow +import * as React from 'react' +import styled from 'styled-components' + +export type ButtonProps = { + children: React.Node +} + +const Button = styled.a` + background-color: var(--c-discord); + color: var(--c-white); + padding: 0.4em 1em; + border-radius: 3px; + border: 1px solid rgba(0,0,0,0.25); + text-decoration: none; + display: flex; + align-items: center; + justify-content: center; + transform: translateY(0px); + transition: all 0.3s ease-in-out; + position: relative; + + &::after { + content: ''; + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + transition: all 0.35s ease-in-out; + background-color: hsla(0,0%,100%,0.1); + pointer-events: none; + opacity: 0; + z-index: 2; + } + + &:hover { + box-shadow: 0 1px 2px rgba(0,0,0,0.75); + transform: translateY(-1px); + &::after { + opacity: 1; + } + } + + &:active { + transform: translateY(0px); + box-shadow: none; + } +` + +const ButtonIcon = styled.img` + height: 1.5em; +` + +const DiscordButton = ({ children, ...props }: ButtonProps) => ( + +) + +export default DiscordButton diff --git a/ui/lib/redirect.js b/ui/lib/redirect.js index 1f7d533..a885919 100644 --- a/ui/lib/redirect.js +++ b/ui/lib/redirect.js @@ -1,7 +1,7 @@ import Router from 'next/router' export default (context, target) => { - if (context.res) { + if (context && context.res) { // server // 303: "See other" context.res.writeHead(303, { Location: target }) diff --git a/ui/pages/_error.js b/ui/pages/_error.js index d9310ef..6a70158 100644 --- a/ui/pages/_error.js +++ b/ui/pages/_error.js @@ -10,12 +10,13 @@ export const Overlay = styled.div` bottom: 0; left: 0; right: 0; - z-index: -1; + z-index: -10; background-image: radial-gradient(circle, var(--c-dark), var(--c-dark) 1px, transparent 1px, transparent); background-size: 27px 27px; ` const ResponsiveSplitter = styled.div` + z-index: -1; display: flex; align-items: center; justify-content: center; @@ -59,86 +60,25 @@ export default class CustomErrorPage extends React.Component { return { statusCode } } - render404 () { - return
- - -
- 404 -
-
-
- This page is in another castle. -
- お探しのページは見つかりませんでした -
-
-
- } + render403 = () => this.out('403', `You weren't allowed to access this.`, 'あなたはこの点に合格しないかもしれません') + render404 = () => this.out('404', 'This page is in another castle.', 'お探しのページは見つかりませんでした') + render500 = () => this.out('500', `The server doesn't like you right now. Feed it a cookie.`, 'クッキーを送ってください〜 クッキーを送ってください〜') + renderDefault = () => this.out('Oops', 'Something went bad. How could this happen?', 'おねがい?') + renderServer = () => this.out('Oops.', 'Server was unhappy about this render. Try reloading or changing page.', 'クッキーを送ってください〜') + renderAuthExpired = () => this.out('Woah.', 'That magic login link was expired.', 'What are you trying to do?') - render403 () { + out (code, description, flair) { return
- 403 + {code}
- You weren't allowed to access this. + {description}
- あなたはこの点に合格しないかもしれません -
-
-
- } - - render500 () { - return
- - -
- 500 -
-
-
- The server doesn't like you right now. Feed it a cookie. -
- クッキーを送ってください〜 クッキーを送ってください〜 -
-
-
- } - - renderDefault () { - return
- - -
- Oops. -
-
-
- Something went bad. How could this happen? -
- おねがい? -
-
-
- } - - renderServer () { - return
- - -
- Oops. -
-
-
- Server was unhappy about this render. Try reloading or changing page. -
- クッキーを送ってください〜 + {flair}
@@ -147,7 +87,8 @@ export default class CustomErrorPage extends React.Component { handlers = { 403: this.render403, 404: this.render404, - 500: this.render500 + 500: this.render500, + 1001: this.renderAuthExpired } render () { diff --git a/ui/pages/auth/expired.js b/ui/pages/auth/expired.js new file mode 100644 index 0000000..0255d0e --- /dev/null +++ b/ui/pages/auth/expired.js @@ -0,0 +1,3 @@ +import ErrorPage from '../_error' + +export default (props) => diff --git a/ui/pages/auth/login.js b/ui/pages/auth/login.js new file mode 100644 index 0000000..7913aa0 --- /dev/null +++ b/ui/pages/auth/login.js @@ -0,0 +1,125 @@ +// @flow +import * as React from 'react' +import styled from 'styled-components' +import MediaQuery from '../../kit/media' +import DiscordButton from '../../components/discord-button' +import RPC from '../../config/rpc' +import redirect from '../../lib/redirect' + +type AuthLoginState = { + humanCode: string, + waiting: boolean +} + +const Wrapper = styled.div` + display: flex; + justify-content: center; + padding-top: 3em; + ${() => MediaQuery({ + md: ` + padding-top: 0; + align-items: center; + min-height: 80vh; + ` + })} +` + +const Line = styled.div` + height: 1px; + background-color: var(--c-9); + margin: 1em 0.3em; +` + +const SecretCode = styled.input` + background-color: transparent; + border: 0; + padding: 1em; + color: var(--c-9); + margin: 0.5rem 0; + width: 100%; + font-size: 0.9em; + appearance: none; + transition: all 0.3s ease-in-out; + + &:focus, &:active, &:hover { + background-color: var(--c-3); + } + + &:focus, &:active { + & ::placeholder { + color: transparent; + } + } + + & ::placeholder { + transition: all 0.3s ease-in-out; + color: var(--c-7); + text-align: center; + } +` + +const HiderButton = styled.button` + appearance: none; + display: block; + cursor: pointer; + width: 100%; + background-color: var(--c-3); + color: var(--c-white); + border: none; + padding: 1em; + font-size: 0.9em; + transition: all 0.3s ease-in-out; + + &[disabled] { + cursor: default; + opacity: 0; + pointer-events: none; + } +` + +export default class AuthLogin extends React.Component<{}, AuthLoginState> { + state = { + humanCode: '', + waiting: false + } + + static async getInitialProps (ctx, rpc) { + if (ctx.user != null) { + redirect(ctx, '/') + } + } + + onChange = (event: any) => { + this.setState({ humanCode: event.target.value }) + } + + onSubmit = async () => { + this.setState({ waiting: true }) + try { + const result = await RPC.checkAuthChallenge(this.state.humanCode) + if (result === true) { + redirect(null, '/') + } + } finally { + this.setState({ waiting: false }) + } + } + + render () { + return +
+ Sign in with Discord + +
+ Or, send a DM to roleypoly#3712 saying: login +
+
+ + { + (this.state.waiting) ? 'One sec...' : 'Submit Code →' + } +
+
+
+ } +} diff --git a/ui/static/discord-logo.svg b/ui/static/discord-logo.svg new file mode 100644 index 0000000..87513d9 --- /dev/null +++ b/ui/static/discord-logo.svg @@ -0,0 +1,21 @@ + + + + + + + + + +