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) => (
+
+
+ {children}
+
+)
+
+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 @@
+
+
+
+
+
+
+
+
+
+