mirror of
https://github.com/roleypoly/roleypoly-v1.git
synced 2025-06-16 10:19:10 +00:00
force rename all UI folders to it's alway lowercase
This commit is contained in:
parent
df2a27663b
commit
dc3a65cfc4
26 changed files with 0 additions and 0 deletions
8
ui/.babelrc
Normal file
8
ui/.babelrc
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"presets": [
|
||||
"next/babel", "@babel/preset-flow"
|
||||
],
|
||||
"plugins": [
|
||||
[ "styled-components", { "ssr": true } ]
|
||||
]
|
||||
}
|
19
ui/.gitignore
vendored
Normal file
19
ui/.gitignore
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
/dist
|
||||
/.next
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
91
ui/components/global-colors.js
Normal file
91
ui/components/global-colors.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
// @flow
|
||||
// import * as React from 'react'
|
||||
import { createGlobalStyle } from 'styled-components'
|
||||
|
||||
export const colors = {
|
||||
white: '#efefef',
|
||||
c9: '#EBD6D4',
|
||||
c7: '#ab9b9a',
|
||||
c5: '#756867',
|
||||
c3: '#5d5352',
|
||||
c1: '#453e3d',
|
||||
dark: '#332d2d',
|
||||
green: '#46b646',
|
||||
red: '#e95353',
|
||||
discord: '#7289da'
|
||||
}
|
||||
|
||||
const getColors = () => {
|
||||
return Object.keys(colors).map(key => {
|
||||
const nk = key.replace(/c([0-9])/, '$1')
|
||||
return `--c-${nk}: ${colors[key]};`
|
||||
}).join(' \n')
|
||||
}
|
||||
|
||||
export default createGlobalStyle`
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "source-han-sans-japanese", "Source Sans Pro", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* prevent FOUC */
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.wf-active body {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// FOUC guard if we take too long
|
||||
.force-active body {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.font-sans-serif {
|
||||
font-family: sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
:root {
|
||||
${() => getColors()}
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--c-9);
|
||||
color: var(--c-1);
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--c-9);
|
||||
color: var(--c-1);
|
||||
}
|
||||
|
||||
html {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
color: var(--c-white);
|
||||
background-color: var(--c-1);
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
color: var(--c-9);
|
||||
}
|
||||
|
||||
.fade-element {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.fade {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
`
|
44
ui/components/head.js
Normal file
44
ui/components/head.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import React from 'react'
|
||||
import NextHead from 'next/head'
|
||||
import { string } from 'prop-types'
|
||||
|
||||
const defaultDescription = ''
|
||||
const defaultOGURL = ''
|
||||
const defaultOGImage = ''
|
||||
|
||||
const Head = props => (
|
||||
<NextHead>
|
||||
<meta charSet="UTF-8" />
|
||||
<title>{props.title || ''}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={props.description || defaultDescription}
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" sizes="192x192" href="/static/touch-icon.png" />
|
||||
<link rel="apple-touch-icon" href="/static/touch-icon.png" />
|
||||
<link rel="mask-icon" href="/static/favicon-mask.svg" color="#49B882" />
|
||||
<link rel="icon" href="/static/favicon.ico" />
|
||||
<meta property="og:url" content={props.url || defaultOGURL} />
|
||||
<meta property="og:title" content={props.title || ''} />
|
||||
<meta
|
||||
property="og:description"
|
||||
content={props.description || defaultDescription}
|
||||
/>
|
||||
<meta name="twitter:site" content={props.url || defaultOGURL} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:image" content={props.ogImage || defaultOGImage} />
|
||||
<meta property="og:image" content={props.ogImage || defaultOGImage} />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
</NextHead>
|
||||
)
|
||||
|
||||
Head.propTypes = {
|
||||
title: string,
|
||||
description: string,
|
||||
url: string,
|
||||
ogImage: string
|
||||
}
|
||||
|
||||
export default Head
|
14
ui/components/header/auth.js
Normal file
14
ui/components/header/auth.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import HeaderBarCommon from './common'
|
||||
import { type User } from '../../containers/user'
|
||||
|
||||
const HeaderBarAuth: React.StatelessFunctionalComponent<{ user: User }> = ({ user }) => (
|
||||
<HeaderBarCommon>
|
||||
<div>
|
||||
Hey there, {user.username}#{user.discriminator}
|
||||
</div>
|
||||
</HeaderBarCommon>
|
||||
)
|
||||
|
||||
export default HeaderBarAuth
|
24
ui/components/header/common.js
Normal file
24
ui/components/header/common.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import DebugBreakpoints from '../../kit/debug-breakpoints'
|
||||
import styled from 'styled-components'
|
||||
|
||||
export type CommonProps = {
|
||||
children: React.Element<any>
|
||||
}
|
||||
|
||||
const Header = styled.div`
|
||||
`
|
||||
|
||||
const HeaderInner = styled.div``
|
||||
|
||||
const HeaderBarCommon = ({ children }: CommonProps) => (
|
||||
<Header>
|
||||
<HeaderInner>
|
||||
{ (process.env.NODE_ENV === 'development') && <DebugBreakpoints />}
|
||||
{ children }
|
||||
</HeaderInner>
|
||||
</Header>
|
||||
)
|
||||
|
||||
export default HeaderBarCommon
|
13
ui/components/header/unauth.js
Normal file
13
ui/components/header/unauth.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import HeaderBarCommon from './common'
|
||||
|
||||
const HeaderBarUnauth: React.StatelessFunctionalComponent<{}> = () => (
|
||||
<HeaderBarCommon>
|
||||
<div>
|
||||
Hey stranger.
|
||||
</div>
|
||||
</HeaderBarCommon>
|
||||
)
|
||||
|
||||
export default HeaderBarUnauth
|
17
ui/components/layout.js
Normal file
17
ui/components/layout.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import GlobalColors from './global-colors'
|
||||
import SocialCards from './social-cards'
|
||||
import HeaderBar from '../containers/header-bar'
|
||||
import { type User } from '../containers/user'
|
||||
|
||||
const Layout = ({ children, user }: {children: React.Element<any>, user: User }) => <>
|
||||
<GlobalColors />
|
||||
<SocialCards />
|
||||
<div>
|
||||
<HeaderBar user={user} />
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
|
||||
export default Layout
|
59
ui/components/nav.js
Normal file
59
ui/components/nav.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
const links = [
|
||||
{ href: 'https://github.com/segmentio/create-next-app', label: 'Github' }
|
||||
].map(link => {
|
||||
link.key = `nav-link-${link.href}-${link.label}`
|
||||
return link
|
||||
})
|
||||
|
||||
const Nav = () => (
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<Link prefetch href="/">
|
||||
<a>Home</a>
|
||||
</Link>
|
||||
</li>
|
||||
<ul>
|
||||
{links.map(({ key, href, label }) => (
|
||||
<li key={key}>
|
||||
<Link href={href}>
|
||||
<a>{label}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<style jsx>{`
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Avenir Next, Avenir,
|
||||
Helvetica, sans-serif;
|
||||
}
|
||||
nav {
|
||||
text-align: center;
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
nav > ul {
|
||||
padding: 4px 16px;
|
||||
}
|
||||
li {
|
||||
display: flex;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
a {
|
||||
color: #067df7;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
`}</style>
|
||||
</nav>
|
||||
)
|
||||
|
||||
export default Nav
|
36
ui/components/social-cards.js
Normal file
36
ui/components/social-cards.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import NextHead from 'next/head'
|
||||
|
||||
export type SocialCardProps = {
|
||||
title?: string,
|
||||
description?: string,
|
||||
image?: string,
|
||||
imageSize?: number
|
||||
}
|
||||
|
||||
const defaultProps: SocialCardProps = {
|
||||
title: 'Roleypoly',
|
||||
description: 'Tame your Discord roles.',
|
||||
image: 'https://rp.kat.cafe/static/social.png',
|
||||
imageSize: 200
|
||||
}
|
||||
|
||||
const SocialCards: React.StatelessFunctionalComponent<SocialCardProps> = (props) => {
|
||||
props = {
|
||||
...defaultProps,
|
||||
...props
|
||||
}
|
||||
|
||||
return <NextHead>
|
||||
<meta key='og:title' property='og:title' content={props.title} />
|
||||
<meta key='og:description' property='og:description' content={props.description} />
|
||||
<meta key='twitter:card' name='twitter:card' content='summary_large_image' />
|
||||
<meta key='twitter:image' name='twitter:image' content={props.image} />
|
||||
<meta key='og:image' property='og:image' content={props.image} />
|
||||
<meta key='og:image:width' property='og:image:width' content={props.imageSize} />
|
||||
<meta key='og:image:height' property='og:image:height' content={props.imageSize} />
|
||||
</NextHead>
|
||||
}
|
||||
|
||||
export default SocialCards
|
15
ui/config/redux.js
Normal file
15
ui/config/redux.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { createStore, applyMiddleware } from 'redux'
|
||||
import { composeWithDevTools } from 'redux-devtools-extension'
|
||||
import thunkMiddleware from 'redux-thunk'
|
||||
import withNextRedux from 'next-redux-wrapper'
|
||||
import { rootReducer } from 'fast-redux'
|
||||
|
||||
export const initStore = (initialState = {}) => {
|
||||
return createStore(
|
||||
rootReducer,
|
||||
initialState,
|
||||
composeWithDevTools(applyMiddleware(thunkMiddleware))
|
||||
)
|
||||
}
|
||||
|
||||
export const withRedux = (comp) => withNextRedux(initStore)(comp)
|
13
ui/config/rpc.js
Normal file
13
ui/config/rpc.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
// @flow
|
||||
import RPCClient from '../rpc'
|
||||
|
||||
const client = new RPCClient({ forceDev: false })
|
||||
|
||||
export default client.rpc
|
||||
export const withCookies = (ctx: any) => {
|
||||
if (ctx.req != null) {
|
||||
return client.withCookies(ctx.req.headers.cookie)
|
||||
} else {
|
||||
return client.rpc
|
||||
}
|
||||
}
|
21
ui/containers/header-bar.js
Normal file
21
ui/containers/header-bar.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { type User } from './user'
|
||||
|
||||
type Props = {
|
||||
user: ?User
|
||||
}
|
||||
|
||||
const HeaderBarAuth = dynamic(() => import('../components/header/auth'))
|
||||
const HeaderBarUnauth = dynamic(() => import('../components/header/unauth'))
|
||||
|
||||
const HeaderBar: React.StatelessFunctionalComponent<Props> = (props) => {
|
||||
if (props.user == null) {
|
||||
return <HeaderBarUnauth {...props} />
|
||||
} else {
|
||||
return <HeaderBarAuth {...props} />
|
||||
}
|
||||
}
|
||||
|
||||
export default HeaderBar
|
32
ui/containers/user.js
Normal file
32
ui/containers/user.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
// @flow
|
||||
import { namespaceConfig } from 'fast-redux'
|
||||
|
||||
export type User = {
|
||||
id: string,
|
||||
username: string,
|
||||
discriminator: string,
|
||||
avatar: string,
|
||||
nicknameCache: {
|
||||
[server: string]: string
|
||||
}
|
||||
}
|
||||
|
||||
export type UserState = {
|
||||
currentUser: User | null,
|
||||
userCache: {
|
||||
[id: string]: User
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_STATE: UserState = {
|
||||
currentUser: null,
|
||||
userCache: {}
|
||||
}
|
||||
|
||||
export const {
|
||||
action, getState: getUserStore
|
||||
} = namespaceConfig('userStore', DEFAULT_STATE)
|
||||
|
||||
export const getCurrentUser = () => async (dispatch: Function) => {
|
||||
|
||||
}
|
31
ui/kit/debug-breakpoints.js
Normal file
31
ui/kit/debug-breakpoints.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import MediaQuery, { breakpoints } from './media'
|
||||
|
||||
const BreakpointDebugFloat = styled.div`
|
||||
position: absolute;
|
||||
bottom: 0em;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
height: 1.4em;
|
||||
opacity: 0.4;
|
||||
font-family: monospace;
|
||||
`
|
||||
|
||||
const Breakpoint = styled.div`
|
||||
padding: 0.1em;
|
||||
display: none;
|
||||
width: 1.5em;
|
||||
text-align: center;
|
||||
background-color: hsl(${(props: any) => props.hue}, 50%, 50%);
|
||||
${(props: any) => MediaQuery({ [props.bp]: `display: inline-block` })}
|
||||
`
|
||||
|
||||
const DebugFloater = () => {
|
||||
return <BreakpointDebugFloat>
|
||||
{ Object.keys(breakpoints).map((x, i, a) => <Breakpoint key={x} bp={x} hue={(360 / a.length) * i}>{x}</Breakpoint>) }
|
||||
</BreakpointDebugFloat>
|
||||
}
|
||||
|
||||
export default DebugFloater
|
32
ui/kit/media.js
Normal file
32
ui/kit/media.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
// @flow
|
||||
export type MediaQuery = $Shape<{
|
||||
xs: string,
|
||||
sm: string,
|
||||
md: string,
|
||||
lg: string,
|
||||
xl: string
|
||||
}>
|
||||
|
||||
export const breakpoints = {
|
||||
xs: 0,
|
||||
sm: 544,
|
||||
md: 768,
|
||||
lg: 1012,
|
||||
xl: 1280
|
||||
}
|
||||
|
||||
export default (mq: MediaQuery) => {
|
||||
const out = []
|
||||
|
||||
for (const size in mq) {
|
||||
if (breakpoints[size] == null) {
|
||||
continue
|
||||
}
|
||||
|
||||
const inner = mq[size]
|
||||
|
||||
out.push(`@media screen and (min-width: ${breakpoints[size]}px) {\n${inner}\n};`)
|
||||
}
|
||||
|
||||
return out.join('\n')
|
||||
}
|
76
ui/pages/_app.js
Normal file
76
ui/pages/_app.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
import * as React from 'react'
|
||||
import App, { Container } from 'next/app'
|
||||
import Head from 'next/head'
|
||||
import Layout from '../components/layout'
|
||||
import { withCookies } from '../config/rpc'
|
||||
|
||||
class RoleypolyApp extends App {
|
||||
static async getInitialProps ({ Component, ctx }) {
|
||||
let pageProps = {}
|
||||
const rpc = withCookies(ctx)
|
||||
|
||||
if (Component.getInitialProps) {
|
||||
pageProps = await Component.getInitialProps(ctx)
|
||||
}
|
||||
|
||||
return { pageProps, user: await rpc.getCurrentUser() }
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.loadTypekit(document)
|
||||
this.waitForFOUC()
|
||||
}
|
||||
|
||||
loadTypekit (d) {
|
||||
var config = {
|
||||
kitId: 'bck0pci',
|
||||
scriptTimeout: 1500,
|
||||
async: true
|
||||
}
|
||||
const h = d.documentElement
|
||||
const t = setTimeout(function () { h.className = h.className.replace(/\bwf-loading\b/g, '') + ' wf-inactive' }, config.scriptTimeout)
|
||||
const tk = d.createElement('script')
|
||||
const s = d.getElementsByTagName('script')[0]
|
||||
let f = false
|
||||
let a
|
||||
h.className += ' wf-loading'
|
||||
tk.src = 'https://use.typekit.net/' + config.kitId + '.js'
|
||||
tk.async = true
|
||||
tk.onload = tk.onreadystatechange = function () {
|
||||
a = this.readyState
|
||||
if (f || (a && a !== 'complete' && a !== 'loaded')) return
|
||||
f = true
|
||||
clearTimeout(t)
|
||||
try { window.Typekit.load(config) } catch (e) {}
|
||||
}
|
||||
s.parentNode.insertBefore(tk, s)
|
||||
}
|
||||
|
||||
// wait one second, add FOUC de-protection.
|
||||
waitForFOUC () {
|
||||
setTimeout(() => {
|
||||
document.documentElement.className += ' force-active'//
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { Component, pageProps, router, user } = this.props
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<noscript>Hey there. Unfortunately, we require JS for this app to work. Please take this rose as retribution. 🌹</noscript>
|
||||
<Head>
|
||||
<meta charSet='utf-8' />
|
||||
<title key='title'>Roleypoly</title>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1' />
|
||||
</Head>
|
||||
|
||||
<Layout user={user}>
|
||||
<Component {...pageProps} router={router} />
|
||||
</Layout>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default RoleypolyApp
|
24
ui/pages/_document.js
Normal file
24
ui/pages/_document.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import Document from 'next/document'
|
||||
import { ServerStyleSheet } from 'styled-components'
|
||||
|
||||
export default class MyDocument extends Document {
|
||||
static async getInitialProps (ctx) {
|
||||
const sheet = new ServerStyleSheet()
|
||||
const originalRenderPage = ctx.renderPage
|
||||
|
||||
try {
|
||||
ctx.renderPage = () =>
|
||||
originalRenderPage({
|
||||
enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
|
||||
})
|
||||
|
||||
const initialProps = await Document.getInitialProps(ctx)
|
||||
return {
|
||||
...initialProps,
|
||||
styles: <>{initialProps.styles}{sheet.getStyleElement()}</>
|
||||
}
|
||||
} finally {
|
||||
sheet.seal()
|
||||
}
|
||||
}
|
||||
}
|
19
ui/pages/_internal/_server.js
Normal file
19
ui/pages/_internal/_server.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Head from 'next/head'
|
||||
import type { PageProps } from '../../types'
|
||||
import SocialCards from '../../components/social-cards'
|
||||
|
||||
export default class Server extends React.Component<PageProps> {
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title key='title'>server name!</title>
|
||||
</Head>
|
||||
<SocialCards title={'server test'} />
|
||||
hello {this.props.router.query.id}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
9
ui/pages/_test/landing-mock.js
Normal file
9
ui/pages/_test/landing-mock.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import type { PageProps } from '../../types'
|
||||
|
||||
export default class LandingTest extends React.Component<PageProps> {
|
||||
render () {
|
||||
return <div />
|
||||
}
|
||||
}
|
10
ui/pages/index.js
Normal file
10
ui/pages/index.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
// import Link from 'next/link'
|
||||
// import Head from '../components/head'
|
||||
// import Nav from '../components/nav'
|
||||
|
||||
const Home = () => (
|
||||
<h1>Hi there.</h1>
|
||||
)
|
||||
|
||||
export default Home
|
31
ui/pages/testrpc.js
Normal file
31
ui/pages/testrpc.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import * as React from 'react'
|
||||
import RPC, { withCookies } from '../config/rpc'
|
||||
|
||||
export default class TestRPC extends React.Component {
|
||||
static async getInitialProps (ctx) {
|
||||
const user = await withCookies(ctx).getCurrentUser()
|
||||
console.log(user)
|
||||
return {
|
||||
user
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount () {
|
||||
window.$RPC = RPC
|
||||
}
|
||||
|
||||
componentDidCatch (error, errorInfo) {
|
||||
if (error) {
|
||||
console.log(error, errorInfo)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
if (this.props.user == null) {
|
||||
return <div>hello stranger OwO</div>
|
||||
}
|
||||
|
||||
const { username, avatar, discriminator } = this.props.user
|
||||
return <div>hello, {username}#{discriminator} <img src={avatar} width={50} height={50} /></div>
|
||||
}
|
||||
}
|
116
ui/rpc/index.js
Normal file
116
ui/rpc/index.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
// @flow
|
||||
import superagent from 'superagent'
|
||||
import RPCError from '../../rpc/_error'
|
||||
|
||||
export type RPCResponse = {
|
||||
response?: mixed,
|
||||
hash?: string,
|
||||
|
||||
// error stuff
|
||||
error?: boolean,
|
||||
msg?: string,
|
||||
trace?: string
|
||||
}
|
||||
|
||||
export type RPCRequest = {
|
||||
fn: string,
|
||||
args: mixed[]
|
||||
}
|
||||
|
||||
export default class RPCClient {
|
||||
dev: boolean = false
|
||||
baseUrl: string
|
||||
firstKnownHash: string
|
||||
recentHash: string
|
||||
cookieHeader: string
|
||||
|
||||
rpc: {
|
||||
[fn: string]: (...args: any[]) => Promise<mixed> | string
|
||||
} = {}
|
||||
|
||||
__rpcAvailable: Array<{
|
||||
name: string,
|
||||
args: number
|
||||
}> = []
|
||||
|
||||
constructor ({ forceDev, baseUrl = '/api/_rpc' }: { forceDev?: boolean, baseUrl?: string } = {}) {
|
||||
this.baseUrl = (process.env.APP_URL || '') + baseUrl
|
||||
|
||||
if (forceDev != null) {
|
||||
this.dev = forceDev
|
||||
} else {
|
||||
this.dev = process.env.NODE_ENV === 'development'
|
||||
}
|
||||
|
||||
this.rpc = new Proxy({
|
||||
toJSON () {
|
||||
return '{}'
|
||||
}
|
||||
}, { get: this.__rpcCall, has: this.__checkCall, ownKeys: this.__listCalls, delete: () => {} })
|
||||
|
||||
if (this.dev) {
|
||||
this.updateCalls()
|
||||
}
|
||||
}
|
||||
|
||||
withCookies = (h: string) => {
|
||||
this.cookieHeader = h
|
||||
return this.rpc
|
||||
}
|
||||
|
||||
async updateCalls () {
|
||||
// this is for development only. doing in prod is probably dumb.
|
||||
const rsp = await superagent.get(this.baseUrl)
|
||||
if (rsp.status !== 200) {
|
||||
console.error(rsp)
|
||||
return
|
||||
}
|
||||
|
||||
const { hash, available } = rsp.body
|
||||
|
||||
this.__rpcAvailable = available
|
||||
if (this.firstKnownHash == null) {
|
||||
this.firstKnownHash = hash
|
||||
}
|
||||
|
||||
this.recentHash = hash
|
||||
|
||||
// just kinda prefill. none of these get called anyway.
|
||||
// and don't matter in prod either.
|
||||
for (let { name } of available) {
|
||||
this.rpc[name] = async () => {}
|
||||
}
|
||||
}
|
||||
|
||||
async call (fn: string, ...args: any[]): mixed {
|
||||
const req: RPCRequest = { fn, args }
|
||||
const rq = superagent.post(this.baseUrl)
|
||||
if (this.cookieHeader != null) {
|
||||
rq.cookies = this.cookieHeader
|
||||
}
|
||||
const rsp = await rq.send(req).ok(() => true)
|
||||
const body: RPCResponse = rsp.body
|
||||
if (body.error === true) {
|
||||
throw RPCError.fromResponse(body, rsp.status)
|
||||
}
|
||||
|
||||
if (body.hash != null) {
|
||||
if (this.firstKnownHash == null) {
|
||||
this.firstKnownHash = body.hash
|
||||
}
|
||||
|
||||
this.recentHash = body.hash
|
||||
|
||||
if (this.firstKnownHash !== this.recentHash) {
|
||||
this.updateCalls()
|
||||
}
|
||||
}
|
||||
|
||||
return body.response
|
||||
}
|
||||
|
||||
// PROXY HANDLERS
|
||||
__rpcCall = (_: {}, fn: string) => this.call.bind(this, fn)
|
||||
__checkCall = (_: {}, fn: string) => this.dev ? this.__listCalls(_).includes(fn) : true
|
||||
__listCalls = (_: {}): string[] => this.__rpcAvailable.map(x => x.name)
|
||||
}
|
35
ui/src/pages/Landing.js
Normal file
35
ui/src/pages/Landing.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import React, { Component, Fragment } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import Scrollbars from 'react-custom-scrollbars'
|
||||
import Typist from 'react-typist'
|
||||
import moment from 'moment'
|
||||
import './landing.sass'
|
||||
import discordLogo from './images/discord-logo.svg'
|
||||
import RoleypolyDemo from '../components/demos/roleypoly'
|
||||
import TypingDemo from '../components/demos/typing'
|
||||
|
||||
const Landing = ({ root = false }) =>
|
||||
<div className="landing uk-width-1-1 uk-text-center">
|
||||
<div className="uk-container">
|
||||
<section>
|
||||
<h1>Self-assignable Discord roles for humans.</h1>
|
||||
<h4>Ditch bot commands once and for all.</h4>
|
||||
</section>
|
||||
<section>
|
||||
<Link to="/oauth/flow" className="uk-button rp-button discord"><img src={discordLogo} className="rp-button-logo"/> Sign in with Discord</Link>
|
||||
</section>
|
||||
<section uk-grid="">
|
||||
{/* Typist */}
|
||||
<div className="uk-width-1-2">
|
||||
<TypingDemo />
|
||||
<p className="subtext">Why are we still using antiques?</p>
|
||||
</div>
|
||||
{/* role side */}
|
||||
<div className="uk-width-1-2">
|
||||
<RoleypolyDemo />
|
||||
<p className="subtext">It's 2018. We can do better.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
export default Landing
|
BIN
ui/static/favicon.ico
Normal file
BIN
ui/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
8
ui/types.js
Normal file
8
ui/types.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
// @flow
|
||||
export type PageProps = {
|
||||
router: {
|
||||
query: {
|
||||
[key: string]: string
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue