mirror of
https://github.com/roleypoly/roleypoly-v1.git
synced 2025-04-25 12:19:10 +00:00
UI: add auth flow and related errors
This commit is contained in:
parent
ff67bc3f1b
commit
bbb34f1771
6 changed files with 226 additions and 74 deletions
62
ui/components/discord-button.js
Normal file
62
ui/components/discord-button.js
Normal file
|
@ -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) => (
|
||||
<Button {...props}>
|
||||
<ButtonIcon src='/static/discord-logo.svg' />
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
|
||||
export default DiscordButton
|
|
@ -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 })
|
||||
|
|
|
@ -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 <div>
|
||||
<Overlay />
|
||||
<ResponsiveSplitter>
|
||||
<div>
|
||||
<Code>404</Code>
|
||||
</div>
|
||||
<div>
|
||||
<section>
|
||||
This page is in another castle.
|
||||
</section>
|
||||
<JapaneseFlair>お探しのページは見つかりませんでした</JapaneseFlair>
|
||||
</div>
|
||||
</ResponsiveSplitter>
|
||||
</div>
|
||||
}
|
||||
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 <div>
|
||||
<Overlay />
|
||||
<ResponsiveSplitter>
|
||||
<div>
|
||||
<Code>403</Code>
|
||||
<Code>{code}</Code>
|
||||
</div>
|
||||
<div>
|
||||
<section>
|
||||
You weren't allowed to access this.
|
||||
{description}
|
||||
</section>
|
||||
<JapaneseFlair>あなたはこの点に合格しないかもしれません</JapaneseFlair>
|
||||
</div>
|
||||
</ResponsiveSplitter>
|
||||
</div>
|
||||
}
|
||||
|
||||
render500 () {
|
||||
return <div>
|
||||
<Overlay />
|
||||
<ResponsiveSplitter>
|
||||
<div>
|
||||
<Code>500</Code>
|
||||
</div>
|
||||
<div>
|
||||
<section>
|
||||
The server doesn't like you right now. Feed it a cookie.
|
||||
</section>
|
||||
<JapaneseFlair>クッキーを送ってください〜 クッキーを送ってください〜</JapaneseFlair>
|
||||
</div>
|
||||
</ResponsiveSplitter>
|
||||
</div>
|
||||
}
|
||||
|
||||
renderDefault () {
|
||||
return <div>
|
||||
<Overlay />
|
||||
<ResponsiveSplitter>
|
||||
<div>
|
||||
<Code>Oops.</Code>
|
||||
</div>
|
||||
<div>
|
||||
<section>
|
||||
Something went bad. How could this happen?
|
||||
</section>
|
||||
<JapaneseFlair>おねがい?</JapaneseFlair>
|
||||
</div>
|
||||
</ResponsiveSplitter>
|
||||
</div>
|
||||
}
|
||||
|
||||
renderServer () {
|
||||
return <div>
|
||||
<Overlay />
|
||||
<ResponsiveSplitter>
|
||||
<div>
|
||||
<Code>Oops.</Code>
|
||||
</div>
|
||||
<div>
|
||||
<section>
|
||||
Server was unhappy about this render. Try reloading or changing page.
|
||||
</section>
|
||||
<JapaneseFlair>クッキーを送ってください〜</JapaneseFlair>
|
||||
<JapaneseFlair>{flair}</JapaneseFlair>
|
||||
</div>
|
||||
</ResponsiveSplitter>
|
||||
</div>
|
||||
|
@ -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 () {
|
||||
|
|
3
ui/pages/auth/expired.js
Normal file
3
ui/pages/auth/expired.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import ErrorPage from '../_error'
|
||||
|
||||
export default (props) => <ErrorPage {...props} statusCode={1001} />
|
125
ui/pages/auth/login.js
Normal file
125
ui/pages/auth/login.js
Normal file
|
@ -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 <Wrapper>
|
||||
<div>
|
||||
<DiscordButton href='/api/auth/redirect'>Sign in with Discord</DiscordButton>
|
||||
<Line />
|
||||
<div>
|
||||
<i>Or, send a DM to <b>roleypoly</b>#3712 saying: login</i>
|
||||
</div>
|
||||
<div>
|
||||
<SecretCode placeholder='click to enter super secret code' onChange={this.onChange} value={this.state.humanCode} />
|
||||
<HiderButton onClick={this.onSubmit} disabled={this.state.humanCode === ''}>{
|
||||
(this.state.waiting) ? 'One sec...' : 'Submit Code →'
|
||||
}</HiderButton>
|
||||
</div>
|
||||
</div>
|
||||
</Wrapper>
|
||||
}
|
||||
}
|
21
ui/static/discord-logo.svg
Normal file
21
ui/static/discord-logo.svg
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 245 240" style="enable-background:new 0 0 245 240;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#efefef;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M104.4,103.9c-5.7,0-10.2,5-10.2,11.1c0,6.1,4.6,11.1,10.2,11.1c5.7,0,10.2-5,10.2-11.1
|
||||
C114.7,108.9,110.1,103.9,104.4,103.9z"/>
|
||||
<path class="st0" d="M140.9,103.9c-5.7,0-10.2,5-10.2,11.1c0,6.1,4.6,11.1,10.2,11.1c5.7,0,10.2-5,10.2-11.1
|
||||
C151.1,108.9,146.6,103.9,140.9,103.9z"/>
|
||||
<path class="st0" d="M189.5,20H55.5C44.2,20,35,29.2,35,40.6v135.2c0,11.4,9.2,20.6,20.5,20.6h113.4l-5.3-18.5l12.8,11.9l12.1,11.2
|
||||
L210,220v-44.2v-10V40.6C210,29.2,200.8,20,189.5,20z M150.9,150.6c0,0-3.6-4.3-6.6-8.1c13.1-3.7,18.1-11.9,18.1-11.9
|
||||
c-4.1,2.7-8,4.6-11.5,5.9c-5,2.1-9.8,3.5-14.5,4.3c-9.6,1.8-18.4,1.3-25.9-0.1c-5.7-1.1-10.6-2.7-14.7-4.3c-2.3-0.9-4.8-2-7.3-3.4
|
||||
c-0.3-0.2-0.6-0.3-0.9-0.5c-0.2-0.1-0.3-0.2-0.4-0.3c-1.8-1-2.8-1.7-2.8-1.7s4.8,8,17.5,11.8c-3,3.8-6.7,8.3-6.7,8.3
|
||||
c-22.1-0.7-30.5-15.2-30.5-15.2c0-32.2,14.4-58.3,14.4-58.3c14.4-10.8,28.1-10.5,28.1-10.5l1,1.2c-18,5.2-26.3,13.1-26.3,13.1
|
||||
s2.2-1.2,5.9-2.9c10.7-4.7,19.2-6,22.7-6.3c0.6-0.1,1.1-0.2,1.7-0.2c6.1-0.8,13-1,20.2-0.2c9.5,1.1,19.7,3.9,30.1,9.6
|
||||
c0,0-7.9-7.5-24.9-12.7l1.4-1.6c0,0,13.7-0.3,28.1,10.5c0,0,14.4,26.1,14.4,58.3C181.5,135.4,173,149.9,150.9,150.6z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
Loading…
Add table
Reference in a new issue