force rename all UI folders to it's alway lowercase

This commit is contained in:
41666 2019-03-11 02:56:26 -05:00
parent df2a27663b
commit dc3a65cfc4
26 changed files with 0 additions and 0 deletions

8
ui/.babelrc Normal file
View file

@ -0,0 +1,8 @@
{
"presets": [
"next/babel", "@babel/preset-flow"
],
"plugins": [
[ "styled-components", { "ssr": true } ]
]
}

19
ui/.gitignore vendored Normal file
View 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*

View 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
View 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

View 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

View 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

View 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
View 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
View 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

View 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
View 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
View 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
}
}

View 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
View 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) => {
}

View 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
View 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
View 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
View 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()
}
}
}

View 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>
)
}
}

View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

8
ui/types.js Normal file
View file

@ -0,0 +1,8 @@
// @flow
export type PageProps = {
router: {
query: {
[key: string]: string
}
}
}