This commit is contained in:
keyan 2021-04-13 19:57:32 -05:00
parent 9acde2df1c
commit bc5d4d4808
23 changed files with 695 additions and 308 deletions

View File

@ -1,43 +1,71 @@
import { UserInputError, AuthenticationError } from 'apollo-server-micro'
const createItem = async (parent, { title, text, url, parentId }, { me, models }) => {
if (!me) {
throw new AuthenticationError('You must be logged in')
}
const data = {
title,
url,
text,
user: {
connect: {
name: me.name
}
}
}
if (parentId) {
data.parent = {
connect: {
id: parseInt(parentId)
}
}
}
return await models.item.create({ data })
}
export default {
Query: {
items: async (parent, args, { models }) => {
return await models.$queryRaw(`
SELECT id, text, "userId", ltree2text("path") AS "path"
SELECT id, "created_at" as "createdAt", title, url, text, "userId", ltree2text("path") AS "path"
FROM "Item"
ORDER BY "path"`)
}
},
Mutation: {
createItem: async (parent, { text, parentId }, { me, models }) => {
if (!me) {
throw new AuthenticationError('You must be logged in')
createLink: async (parent, { title, url }, { me, models }) => {
if (!title) {
throw new UserInputError('Link must have title', { argumentName: 'title' })
}
if (!url) {
throw new UserInputError('Link must have url', { argumentName: 'url' })
}
return await createItem(parent, { title, url }, { me, models })
},
createDiscussion: async (parent, { title, text }, { me, models }) => {
if (!title) {
throw new UserInputError('Link must have title', { argumentName: 'title' })
}
return await createItem(parent, { title, text }, { me, models })
},
createComment: async (parent, { text, parentId }, { me, models }) => {
if (!text) {
throw new UserInputError('Item must have text', { argumentName: 'text' })
throw new UserInputError('Comment must have text', { argumentName: 'text' })
}
const data = {
text,
user: {
connect: {
name: me.name
}
}
if (!parentId) {
throw new UserInputError('Comment must have text', { argumentName: 'text' })
}
if (parentId) {
data.parent = {
connect: {
id: parseInt(parentId)
}
}
}
return await models.item.create({ data })
return await createItem(parent, { text, parentId }, { me, models })
}
},
@ -56,6 +84,8 @@ export default {
WHERE id = ${item.id}`
return path.split('.').length - 1
}
},
comments: () => 0,
sats: () => 0
}
}

View File

@ -6,13 +6,20 @@ export default gql`
}
extend type Mutation {
createItem(text: String!, parentId: ID): Item!
createLink(title: String!, url: String): Item!
createDiscussion(title: String!, text: String): Item!
createComment(text: String!, parentId: ID!): Item!
}
type Item {
id: ID!
text: String!
createdAt: String!
title: String
url: String
text: String
user: User!
depth: Int!
sats: Int!
comments: Int!
}
`

74
components/form.js Normal file
View File

@ -0,0 +1,74 @@
import Button from 'react-bootstrap/Button'
import InputGroup from 'react-bootstrap/InputGroup'
import BootstrapForm from 'react-bootstrap/Form'
import Alert from 'react-bootstrap/Alert'
import { Formik, Form as FormikForm, useFormikContext, useField } from 'formik'
import { useState } from 'react'
export function SubmitButton ({ children, variant, ...props }) {
const { isSubmitting } = useFormikContext()
return (
<Button
variant={variant || 'main'}
type='submit'
disabled={isSubmitting}
{...props}
>
{children}
</Button>
)
}
export function Input ({ label, prepend, append, hint, ...props }) {
const [field, meta] = useField(props)
return (
<BootstrapForm.Group>
{label && <BootstrapForm.Label>{label}</BootstrapForm.Label>}
<InputGroup hasValidation>
{prepend && (
<InputGroup.Prepend>
{prepend}
</InputGroup.Prepend>
)}
<BootstrapForm.Control
{...field} {...props}
isInvalid={meta.touched && meta.error}
/>
{append && (
<InputGroup.Append>
{append}
</InputGroup.Append>
)}
<BootstrapForm.Control.Feedback type='invalid'>
{meta.touched && meta.error}
</BootstrapForm.Control.Feedback>
{hint && (
<BootstrapForm.Text>
{hint}
</BootstrapForm.Text>
)}
</InputGroup>
</BootstrapForm.Group>
)
}
export function Form ({
initial, schema, onSubmit, children, ...props
}) {
const [error, setError] = useState()
return (
<Formik
initialValues={initial}
validationSchema={schema}
onSubmit={(...args) =>
onSubmit(...args).catch(e => setError(e.message))}
>
<FormikForm {...props} noValidate>
{error && <Alert variant='danger' onClose={() => setError(undefined)} dismissible>{error}</Alert>}
{children}
</FormikForm>
</Formik>
)
}

View File

@ -4,6 +4,7 @@ import Nav from 'react-bootstrap/Nav'
import Link from 'next/link'
import styles from './header.module.css'
import { useRouter } from 'next/router'
import { Container } from 'react-bootstrap'
export default function Header () {
const [session, loading] = useSession()
@ -30,25 +31,27 @@ export default function Header () {
return (
<>
<Navbar bg='brand' className={styles.navbar}>
<Link href='/' passHref>
<Navbar.Brand className={styles.brand}>STACKER NEWS</Navbar.Brand>
</Link>
<Nav className='mr-auto align-items-center' activeKey={router.pathname}>
<Nav.Item>
<Link href='/recent' passHref>
<Nav.Link>recent</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href='/post' passHref>
<Nav.Link>post</Nav.Link>
</Link>
</Nav.Item>
</Nav>
<Nav className='ml-auto align-items-center'>
<Corner />
</Nav>
<Navbar bg='primary' className={styles.navbar}>
<Container>
<Link href='/' passHref>
<Navbar.Brand className={styles.brand}>STACKER NEWS</Navbar.Brand>
</Link>
<Nav className='mr-auto align-items-center' activeKey={router.pathname}>
<Nav.Item>
<Link href='/recent' passHref>
<Nav.Link>recent</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href='/post' passHref>
<Nav.Link>post</Nav.Link>
</Link>
</Nav.Item>
</Nav>
<Nav className='ml-auto align-items-center'>
<Corner />
</Nav>
</Container>
</Navbar>
</>
)

47
components/item.js Normal file
View File

@ -0,0 +1,47 @@
import UpVote from '../svgs/lightning-arrow.svg'
import styles from './item.module.css'
function timeSince (timeStamp) {
const now = new Date()
const secondsPast = (now.getTime() - timeStamp) / 1000
if (secondsPast < 60) {
return parseInt(secondsPast) + 's'
}
if (secondsPast < 3600) {
return parseInt(secondsPast / 60) + 'm'
}
if (secondsPast <= 86400) {
return parseInt(secondsPast / 3600) + 'h'
}
if (secondsPast > 86400) {
const day = timeStamp.getDate()
const month = timeStamp.toDateString().match(/ [a-zA-Z]*/)[0].replace(' ', '')
const year = timeStamp.getFullYear() === now.getFullYear() ? '' : ' ' + timeStamp.getFullYear()
return day + ' ' + month + year
}
}
export default function Item ({ item }) {
return (
<div className='d-flex justify-content-start align-items-center'>
<UpVote width={32} height={32} className={styles.upvote} />
<div>
<div>
<span>
<span className={styles.title}>{item.title}</span>
<a className={styles.link} href={item.url}>{item.url.replace(/(^\w+:|^)\/\//, '')}</a>
</span>
</div>
<div className={styles.other}>
<span>{item.sats} sats</span>
<span> \ </span>
<span>{item.comments} comments</span>
<span> </span>
<a href='/satoshi'>@{item.user.name}</a>
<span> </span>
<span>{timeSince(new Date(item.createdAt))}</span>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,23 @@
.upvote {
fill: grey;
margin-right: .25rem;
}
.upvote:hover {
fill: darkgray;
cursor: pointer;
}
.title {
font-weight: 500;
}
.link {
font-size: 80%;
margin-left: .5rem;
}
.other {
font-size: 70%;
color: grey;
}

View File

@ -12,13 +12,16 @@
"@prisma/client": "^2.19.0",
"apollo-server-micro": "^2.21.2",
"bootstrap": "^4.6.0",
"formik": "^2.2.6",
"graphql": "^15.5.0",
"next": "10.0.9",
"next-auth": "^3.13.3",
"react": "17.0.1",
"react-bootstrap": "^1.5.2",
"react-dom": "17.0.1",
"swr": "^0.5.4"
"sass": "^1.32.8",
"swr": "^0.5.4",
"yup": "^0.32.9"
},
"standard": {
"parser": "babel-eslint",

View File

@ -1,5 +1,4 @@
import 'bootstrap/dist/css/bootstrap.min.css'
import '../styles/globals.css'
import '../styles/globals.scss'
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client'
import { Provider } from 'next-auth/client'

View File

@ -1,103 +1,106 @@
import { useQuery, gql, useMutation } from '@apollo/client'
import { useState } from 'react'
import { useSession } from 'next-auth/client'
import styles from '../styles/index.module.css'
import { useQuery, gql } from '@apollo/client'
// import styles from '../styles/index.module.css'
import Layout from '../components/layout'
import Item from '../components/item'
import Container from 'react-bootstrap/Container'
function Users () {
const { loading, error, data } = useQuery(gql`{ users { id, name } }`)
if (error) return <div>Failed to load</div>
if (loading) return <div>Loading...</div>
const { users } = data
return (
<div>
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
)
}
// function Users () {
// const { loading, error, data } = useQuery(gql`{ users { id, name } }`)
// if (error) return <div>Failed to load</div>
// if (loading) return <div>Loading...</div>
// const { users } = data
// return (
// <div>
// {users.map(user => (
// <div key={user.id}>{user.name}</div>
// ))}
// </div>
// )
// }
function NewItem ({ parentId }) {
const [session] = useSession()
const [createItem] = useMutation(
gql`
mutation CreateItem($text: String!, $parentId: ID) {
createItem(text: $text, parentId: $parentId) {
id
}
}`, {
update (cache, { data: { createItem } }) {
cache.modify({
fields: {
items (existingItems = [], { readField }) {
const newItemRef = cache.writeFragment({
data: createItem,
fragment: gql`
fragment NewItem on Item {
id
user {
name
}
text
depth
}
`
})
for (let i = 0; i < existingItems.length; i++) {
if (readField('id', existingItems[i]) === parentId) {
return [...existingItems.slice(0, i), newItemRef, ...existingItems.slice(i)]
}
}
return [newItemRef, ...existingItems]
}
}
})
}
})
const [open, setOpen] = useState(false)
// function NewItem ({ parentId }) {
// const [session] = useSession()
// const [createItem] = useMutation(
// gql`
// mutation CreateItem($text: String!, $parentId: ID) {
// createItem(text: $text, parentId: $parentId) {
// id
// }
// }`, {
// update (cache, { data: { createItem } }) {
// cache.modify({
// fields: {
// items (existingItems = [], { readField }) {
// const newItemRef = cache.writeFragment({
// data: createItem,
// fragment: gql`
// fragment NewItem on Item {
// id
// user {
// name
// }
// text
// depth
// }
// `
// })
// for (let i = 0; i < existingItems.length; i++) {
// if (readField('id', existingItems[i]) === parentId) {
// return [...existingItems.slice(0, i), newItemRef, ...existingItems.slice(i)]
// }
// }
// return [newItemRef, ...existingItems]
// }
// }
// })
// }
// })
// const [open, setOpen] = useState(false)
if (!session) return null
// if (!session) return null
if (!open) {
return (
<div onClick={() => setOpen(true)}>
{parentId ? 'reply' : 'submit'}
</div>
)
}
// if (!open) {
// return (
// <div onClick={() => setOpen(true)}>
// {parentId ? 'reply' : 'submit'}
// </div>
// )
// }
let text
return (
<form
style={{ marginLeft: '5px' }}
onSubmit={e => {
e.preventDefault()
createItem({ variables: { text: text.value, parentId } })
setOpen(false)
text.value = ''
}}
>
<textarea
ref={node => {
text = node
}}
/>
<button type='submit'>Submit</button>
</form>
)
}
// let text
// return (
// <form
// style={{ marginLeft: '5px' }}
// onSubmit={e => {
// e.preventDefault()
// createItem({ variables: { text: text.value, parentId } })
// setOpen(false)
// text.value = ''
// }}
// >
// <textarea
// ref={node => {
// text = node
// }}
// />
// <button type='submit'>Submit</button>
// </form>
// )
// }
function Items () {
const { loading, error, data } = useQuery(
gql`
{ items {
id
createdAt
title
url
user {
name
}
text
depth
sats
comments
} }`
)
if (error) return <div>Failed to load</div>
@ -105,15 +108,18 @@ function Items () {
const { items } = data
return (
<>
<NewItem />
<div>
{items.map(item => (
<div key={item.id} style={{ marginLeft: `${5 * item.depth}px` }}>
<div>{item.user.name} : {item.text}</div>
<NewItem parentId={item.id} />
</div>
))}
</div>
<Container className='my-1 py-2 px-sm-0'>
<table>
<tbody>
{items.map((item, i) => (
<tr className='align-items-center' key={item.id}>
<td align='right' className='font-weight-bold text-muted mr-1'>{i + 1}</td>
<td><Item item={item} /></td>
</tr>
))}
</tbody>
</table>
</Container>
</>
)
}
@ -121,8 +127,6 @@ function Items () {
export default function Index () {
return (
<Layout>
<div className={`${styles.box} flashit`} />
<Users />
<Items />
</Layout>
)

View File

@ -0,0 +1,13 @@
import Container from "react-bootstrap/Container";
import Item from "../../../components/item";
import Layout from "../../../components/layout";
export default function AnItem () {
return (
<Layout>
<Container className='my-4'>
<Item />
</Container>
</Layout>
)
}

View File

@ -1,38 +1,102 @@
import Button from 'react-bootstrap/Button'
import Form from 'react-bootstrap/Form'
import Layout from '../components/layout'
import Button from 'react-bootstrap/Button'
import { Form, Input, SubmitButton } from '../components/form'
import { useRouter } from 'next/router'
import Link from 'next/link'
import styles from '../styles/post.module.css'
import * as Yup from 'yup'
import { gql, useMutation } from '@apollo/client'
export const DiscussionSchema = Yup.object({
title: Yup.string().required('required').trim()
})
export function DiscussionForm () {
const router = useRouter()
const [createDiscussion] = useMutation(
gql`
mutation createDiscussion($title: String!, $text: String) {
createDiscussion(title: $title, text: $text) {
id
}
}`
)
return (
<Form>
<Form.Group>
<Form.Label>title</Form.Label>
<Form.Control type='text' />
</Form.Group>
<Form.Group>
<Form.Label>text <small className='text-muted ml-2'>optional</small></Form.Label>
<Form.Control as='textarea' rows={4} />
</Form.Group>
<Button className='mt-2' variant='main' size='lg' type='submit'>post</Button>
<Form
initial={{
title: '',
text: ''
}}
schema={DiscussionSchema}
onSubmit={async (values) => {
const { data: { createDiscussion: { id } }, error } = await createDiscussion({ variables: values })
if (error) {
throw new Error({ message: error.toString() })
}
router.push(`items/${id}`)
}}
>
<Input
label='title'
name='title'
required
autoFocus
/>
<Input
label={<>text <small className='text-muted ml-2'>optional</small></>}
name='text'
as='textarea'
rows={4}
/>
<SubmitButton variant='secondary' className='mt-2'>post</SubmitButton>
</Form>
)
}
export const LinkSchema = Yup.object({
title: Yup.string().required('required').trim(),
url: Yup.string().url('invalid url')
})
export function LinkForm () {
const router = useRouter()
const [createLink] = useMutation(
gql`
mutation createLink($title: String!, $url: String!) {
createLink(title: $title, url: $url) {
id
}
}`
)
return (
<Form>
<Form.Group>
<Form.Label>title</Form.Label>
<Form.Control type='text' />
</Form.Group>
<Form.Group>
<Form.Label>url</Form.Label>
<Form.Control type='url' />
</Form.Group>
<Button className='mt-2' variant='main' size='lg' type='submit'>post</Button>
<Form
initial={{
title: '',
url: ''
}}
schema={LinkSchema}
onSubmit={async (values) => {
const { data: { createLink: { id } }, error } = await createLink({ variables: values })
if (error) {
throw new Error({ message: error.toString() })
}
router.push(`items/${id}`)
}}
>
<Input
label='title'
name='title'
required
autoFocus
/>
<Input
label='url'
name='url'
required
/>
<SubmitButton variant='secondary' className='mt-2'>post</SubmitButton>
</Form>
)
}
@ -44,11 +108,11 @@ export function PostForm () {
return (
<div className='align-items-center'>
<Link href='/post?type=link'>
<Button variant='main' size='lg'>link</Button>
<Button variant='secondary'>link</Button>
</Link>
<span className='mx-3 font-weight-bold text-muted'>or</span>
<Link href='/post?type=discussion'>
<Button variant='main' size='lg'> discussion</Button>
<Button variant='secondary'> discussion</Button>
</Link>
</div>
)

View File

@ -28,6 +28,7 @@ CREATE TABLE "Item" (
"id" SERIAL NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"title" TEXT,
"text" TEXT,
"url" TEXT,
"userId" INTEGER NOT NULL,

View File

@ -35,7 +35,7 @@ model Item {
id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
title String
title String?
text String?
url String?
user User @relation(fields: [userId], references: [id])

View File

@ -5,29 +5,14 @@ async function main () {
where: { name: 'k00b' },
update: {},
create: {
name: 'k00b',
messages: {
create: {
text: 'Hello world'
}
}
name: 'k00b'
}
})
const satoshi = await prisma.user.upsert({
where: { name: 'satoshi' },
update: {},
create: {
name: 'satoshi',
messages: {
create: [
{
text: 'Peer to peer digital cash'
},
{
text: 'Reengineer the world'
}
]
}
name: 'satoshi'
}
})
const greg = await prisma.user.upsert({
@ -49,52 +34,39 @@ async function main () {
where: { id: 0 },
update: {},
create: {
text: 'A',
userId: satoshi.id,
children: {
create: [
{
text: 'B',
userId: k00b.id,
children: {
create: [
{
text: 'G',
userId: satoshi.id,
children: {
create: [
{
text: 'H',
userId: greg.id
}
]
}
}
]
}
},
{
text: 'C',
userId: k00b.id,
children: {
create: [
{
text: 'D',
userId: satoshi.id
},
{
text: 'E',
userId: greg.id
},
{
text: 'F',
userId: stan.id
}
]
}
}
]
}
title: 'System76 Developing “Cosmic” Desktop Environment',
url: 'https://blog.system76.com/post/648371526931038208/cosmic-to-arrive-in-june-release-of-popos-2104',
userId: satoshi.id
}
})
await prisma.item.upsert({
where: { id: 1 },
update: {},
create: {
title: 'Deno 1.9',
url: 'https://deno.com/blog/v1.9',
userId: k00b.id
}
})
await prisma.item.upsert({
where: { id: 2 },
update: {},
create: {
title: '1Password Secrets Automation',
url: 'https://blog.1password.com/introducing-secrets-automation/',
userId: greg.id
}
})
await prisma.item.upsert({
where: { id: 3 },
update: {},
create: {
title: 'Counter Strike Bug Allows Hackers to Take over a PC with a Steam Invite',
url: 'https://www.vice.com/en/article/dyvgej/counter-strike-bug-allows-hackers-to-take-over-a-pc-with-a-steam-invite',
userId: stan.id
}
})
}

View File

@ -1,74 +0,0 @@
@font-face {
font-family: lightning;
src: url(/Lightningvolt-xoqm.ttf);
}
html {
background-color: #fafafa !important;
}
body {
background-color: #fafafa !important;
}
.form-label {
font-size: 0.938rem;
font-weight: bold;
}
.form-control {
border-radius: .36rem;
height: calc(1.5em + 1rem + 2px);
}
.btn {
transition: none;
font-weight: 600;
font-size: 16px;
border-radius: .4rem;
}
.btn:focus {
box-shadow: none !important;
}
button:focus {
outline: none !important;
}
.btn-main {
background-color: #F6911D;
color: #ffffff;
}
.btn-main:hover {
background-color: #F6911D;
color: #ffffff;
}
.btn-lg {
padding: 0.5rem 1.25rem;
}
.bg-brand {
background: #FADA5E;
}
.flashit{
animation: flash ease-out 7s infinite;
animation-delay: -3s;
}
@keyframes flash {
from { opacity: 0; }
42% { opacity: 0; }
43% { opacity: 0.6; }
44% { opacity: 0.2; }
46% { opacity: 1; }
50% { opacity: 0; }
92% { opacity: 0; }
93% { opacity: 0.6; }
94% { opacity: 0.2; }
96% { opacity: 1; }
to { opacity: 0; }
}

99
styles/globals.scss Normal file
View File

@ -0,0 +1,99 @@
$theme-colors: (
"primary" : #FADA5E,
"secondary" : #F6911D,
"danger" : #c03221,
"info" : #007cbe,
"success" : #5c8001
);
$body-bg: #fafafa;
$border-radius: .4rem;
$enable-transitions: false;
$enable-gradients: false;
$enable-shadows: false;
$btn-transition: none;
$form-feedback-icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='12' height='12'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M22 15h-3V3h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-5.293 1.293l-6.4 6.4a.5.5 0 0 1-.654.047L8.8 22.1a1.5 1.5 0 0 1-.553-1.57L9.4 16H3a2 2 0 0 1-2-2v-2.104a2 2 0 0 1 .15-.762L4.246 3.62A1 1 0 0 1 5.17 3H16a1 1 0 0 1 1 1v11.586a1 1 0 0 1-.293.707z' fill='rgba(192,50,33,1)'/%3E%3C/svg%3E");
$line-height-base: 1.75;
$input-btn-padding-y: .42rem;
$input-btn-padding-x: .84rem;
$btn-padding-y: .5rem;
$btn-padding-x: 1.1rem;
$btn-font-weight: bold;
$btn-focus-width: 0;
$btn-border-width: 0;
$btn-focus-box-shadow: none;
$alert-border-width: 0;
$close-text-shadow: none;
$close-color: inherit;
$alert-border-radius: #{33% 2%} / #{11% 74%};
$link-color: #007cbe;
$font-size-base: .9rem;
$enable-responsive-font-sizes: true;
$link-hover-decoration: none;
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 900px,
xl: 900px
) !default;
@import "~bootstrap/scss/bootstrap";
@font-face {
font-family: lightning;
src: url(/Lightningvolt-xoqm.ttf);
}
.form-label {
font-size: 92%;
font-weight: bold;
}
.form-control:focus {
border-color: #fada5e;
}
.btn-secondary,
.btn-secondary:hover,
.btn-secondary:disabled {
color: #ffffff;
}
.flashit{
animation: flash ease-out 7s infinite;
animation-delay: -3s;
}
@keyframes flash {
from { opacity: 0; }
42% { opacity: 0; }
43% { opacity: 0.6; }
44% { opacity: 0.2; }
46% { opacity: 1; }
50% { opacity: 0; }
92% { opacity: 0; }
93% { opacity: 0.6; }
94% { opacity: 0.2; }
96% { opacity: 1; }
to { opacity: 0; }
}
.morphit{
animation: flash ease-out 7s infinite;
animation-delay: -3s;
}
@keyframes flash {
from { opacity: 0; }
42% { opacity: 0; }
43% { opacity: 0.6; }
44% { opacity: 0.2; }
46% { opacity: 1; }
50% { opacity: 0; }
92% { opacity: 0; }
93% { opacity: 0.6; }
94% { opacity: 0.2; }
96% { opacity: 1; }
to { opacity: 0; }
}

View File

@ -2,4 +2,10 @@
width: 100px;
height: 100px;
background-color: grey;
}
.grid {
display: grid;
grid-template-columns: auto auto auto;
width: 100%;
}

1
svgs/arrow-up-s-fill.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="32" height="32"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 8l6 6H6z"/></svg>

After

Width:  |  Height:  |  Size: 150 B

1
svgs/lightning-arrow.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 114 287"><path d="M113.62 193.44h-49.1l17.93 93.28H18.9L.5 164.46h64.02L48.76 62.02h-26.3L61.74.5l39.46 61.52H77.95z"/></svg>

After

Width:  |  Height:  |  Size: 178 B

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 286.221 286.221" style="enable-background:new 0 0 286.221 286.221;" xml:space="preserve">
<g>
<polygon points="199.667,93.283 150.567,93.283 168.5,0 104.95,0 86.554,122.26 150.567,122.26 134.813,224.703 108.506,224.703
147.795,286.221 187.247,224.703 164,224.703 "/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 837 B

5
svgs/pyramid.svg Normal file
View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" >
<path d="M256,0.289L0,379.471l256,132.24l256-132.24L256,0.289z M240.992,470.175L54.582,373.882l186.411-96.294V470.175z
M240.992,243.805L67.197,333.582L240.992,76.16V243.805z M271.008,76.16l173.795,257.423l-173.795-89.778V76.16z
M271.008,277.588l186.411,96.294l-186.411,96.293V277.588z"/>
</svg>

After

Width:  |  Height:  |  Size: 372 B

1
svgs/triangle.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72.72 72.719"><path d="M72.72,65.686H0L36.36,7.034L72.72,65.686z M5.388,62.686h61.943L36.36,12.727L5.388,62.686z"/></svg>

After

Width:  |  Height:  |  Size: 176 B

View File

@ -149,7 +149,7 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.1", "@babel/runtime@^7.13.8", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7":
"@babel/runtime@^7.10.5", "@babel/runtime@^7.12.1", "@babel/runtime@^7.13.8", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7":
version "7.13.10"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
@ -522,6 +522,11 @@
"@types/koa-compose" "*"
"@types/node" "*"
"@types/lodash@^4.14.165":
version "4.14.168"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008"
integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==
"@types/long@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
@ -1277,7 +1282,7 @@ chalk@^4.0.0, chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chokidar@3.5.1:
chokidar@3.5.1, "chokidar@>=2.0.0 <4.0.0":
version "3.5.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
@ -1572,6 +1577,11 @@ deep-is@^0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
deepmerge@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@ -2196,6 +2206,19 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
formik@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.6.tgz#378a4bafe4b95caf6acf6db01f81f3fe5147559d"
integrity sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA==
dependencies:
deepmerge "^2.1.1"
hoist-non-react-statics "^3.3.0"
lodash "^4.17.14"
lodash-es "^4.17.14"
react-fast-compare "^2.0.1"
tiny-warning "^1.0.2"
tslib "^1.10.0"
fs-capacitor@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-2.0.4.tgz#5a22e72d40ae5078b4fe64fe4d08c0d3fc88ad3c"
@ -2412,7 +2435,7 @@ hoek@6.x.x:
resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c"
integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==
hoist-non-react-statics@^3.3.2:
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -2835,7 +2858,7 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
lodash-es@^4.17.20:
lodash-es@^4.17.14, lodash-es@^4.17.15, lodash-es@^4.17.20:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
@ -3028,6 +3051,11 @@ mz@^2.4.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
nanoclone@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4"
integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==
nanoid@^3.1.16:
version "3.1.22"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844"
@ -3584,6 +3612,11 @@ prop-types@15.7.2, prop-types@^15.6.2, prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"
property-expr@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910"
integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==
public-encrypt@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
@ -3694,6 +3727,11 @@ react-dom@17.0.1:
object-assign "^4.1.1"
scheduler "^0.20.1"
react-fast-compare@^2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-is@16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -3888,6 +3926,13 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass@^1.32.8:
version "1.32.8"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.8.tgz#f16a9abd8dc530add8834e506878a2808c037bdc"
integrity sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==
dependencies:
chokidar ">=2.0.0 <4.0.0"
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@ -4360,6 +4405,11 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
tiny-warning@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
@ -4382,6 +4432,11 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
tr46@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@ -4747,6 +4802,19 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
yup@^0.32.9:
version "0.32.9"
resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.9.tgz#9367bec6b1b0e39211ecbca598702e106019d872"
integrity sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==
dependencies:
"@babel/runtime" "^7.10.5"
"@types/lodash" "^4.14.165"
lodash "^4.17.20"
lodash-es "^4.17.15"
nanoclone "^0.2.1"
property-expr "^2.0.4"
toposort "^2.0.2"
zen-observable-ts@^0.8.21:
version "0.8.21"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d"