finish mvp

This commit is contained in:
keyan 2021-05-24 19:08:56 -05:00
parent a9ea341a7b
commit 80ff13abd6
17 changed files with 7068 additions and 52 deletions

View File

@ -174,12 +174,10 @@ const createItem = async (parent, { title, url, text, parentId }, { me, models }
throw new AuthenticationError('you must be logged in')
}
console.log('before')
const [item] = await serialize(models, models.$queryRaw(
`${SELECT} FROM create_item($1, $2, $3, $4, $5) AS "Item"`,
title, url, text, Number(parentId), me.name))
item.comments = []
console.log('after')
return item
}

View File

@ -86,7 +86,12 @@ export default {
}
// decode invoice to get amount
const decoded = await decodePaymentRequest({ lnd, request: invoice })
let decoded
try {
decoded = await decodePaymentRequest({ lnd, request: invoice })
} catch (error) {
throw new UserInputError('could not decode invoice')
}
const msatsFee = Number(maxFee) * 1000

View File

@ -18,6 +18,8 @@ export default gql`
nitems: Int!
ncomments: Int!
stacked: Int!
freePosts: Int!
freeComments: Int!
sats: Int!
msats: Int!
}

View File

@ -48,11 +48,10 @@ export function InputSkeleton ({ label }) {
)
}
export function Input ({ label, prepend, append, hint, showValid, noBottomMargin, ...props }) {
export function Input ({ label, prepend, append, hint, showValid, groupClassName, ...props }) {
const [field, meta] = props.readOnly ? [{}, {}] : useField(props)
return (
<BootstrapForm.Group className={noBottomMargin ? 'mb-0' : ''}>
<BootstrapForm.Group className={groupClassName}>
{label && <BootstrapForm.Label>{label}</BootstrapForm.Label>}
<InputGroup hasValidation>
{prepend && (
@ -73,18 +72,18 @@ export function Input ({ label, prepend, append, hint, showValid, noBottomMargin
<BootstrapForm.Control.Feedback type='invalid'>
{meta.touched && meta.error}
</BootstrapForm.Control.Feedback>
{hint && (
<BootstrapForm.Text>
{hint}
</BootstrapForm.Text>
)}
</InputGroup>
{hint && (
<BootstrapForm.Text>
{hint}
</BootstrapForm.Text>
)}
</BootstrapForm.Group>
)
}
export function Form ({
initial, schema, onSubmit, children, initialError, validateOnBlur, ...props
initial, schema, onSubmit, children, initialError, validateImmediately, ...props
}) {
const [error, setError] = useState(initialError)
@ -92,7 +91,8 @@ export function Form ({
<Formik
initialValues={initial}
validationSchema={schema}
validateOnBlur={validateOnBlur}
initialTouched={validateImmediately && initial}
validateOnBlur={false}
onSubmit={async (...args) =>
onSubmit && onSubmit(...args).catch(e => setError(e.message || e))}
>

View File

@ -35,7 +35,7 @@ export function FundErrorModal () {
onHide={() => setError(false)}
>
<Modal.Body>
<p className='font-weight-bolder'>you are out of sats</p>
<p className='font-weight-bolder'>you have no sats</p>
<div className='d-flex justify-content-end'>
<Link href='/wallet?type=fund'>
<Button variant='success' onClick={() => setError(false)}>fund</Button>

View File

@ -6,20 +6,13 @@ import styles from './header.module.css'
import { useRouter } from 'next/router'
import { Button, Container, NavDropdown } from 'react-bootstrap'
import Price from './price'
import { gql, useQuery } from '@apollo/client'
import { useMe } from './me'
function WalletSummary () {
const query = gql`
{
me {
sats
stacked
}
}`
const { data } = useQuery(query, { pollInterval: 1000 })
if (!data) return null
const me = useMe()
if (!me) return null
return `[${data.me.stacked},${data.me.sats}]`
return `[${me.stacked},${me.sats}]`
}
export default function Header () {

35
components/me.js Normal file
View File

@ -0,0 +1,35 @@
import React, { useContext } from 'react'
import { gql, useQuery } from '@apollo/client'
export const MeContext = React.createContext({
me: null
})
export function MeProvider ({ children }) {
const query = gql`
{
me {
id
sats
stacked
freePosts
freeComments
}
}`
const { data } = useQuery(query, { pollInterval: 1000 })
const contextValue = {
me: data ? data.me : null
}
return (
<MeContext.Provider value={contextValue}>
{children}
</MeContext.Provider>
)
}
export function useMe () {
const { me } = useContext(MeContext)
return me
}

View File

@ -3,12 +3,15 @@ import * as Yup from 'yup'
import { gql, useMutation } from '@apollo/client'
import styles from './reply.module.css'
import { COMMENTS } from '../fragments/comments'
import { useMe } from './me'
export const CommentSchema = Yup.object({
text: Yup.string().required('required').trim()
})
export default function Reply ({ parentId, onSuccess, autoFocus }) {
const me = useMe()
const [createComment] = useMutation(
gql`
${COMMENTS}
@ -65,6 +68,7 @@ export default function Reply ({ parentId, onSuccess, autoFocus }) {
rows={4}
autoFocus={autoFocus}
required
hint={me?.freeComments ? <span className='text-success'>{me.freeComments} free comments left</span> : null}
/>
<SubmitButton variant='secondary' className='mt-1'>reply</SubmitButton>
</Form>

View File

@ -8,6 +8,7 @@ import { Form, Input, SubmitButton } from './form'
import InputGroup from 'react-bootstrap/InputGroup'
import * as Yup from 'yup'
import { gql, useApolloClient, useMutation } from '@apollo/client'
import styles from './user-header.module.css'
const NAME_QUERY =
gql`
@ -58,6 +59,7 @@ export default function UserHeader ({ user }) {
initial={{
name: user.name
}}
validateImmediately
onSubmit={async ({ name }) => {
if (name === user.name) {
setEditting(false)
@ -74,7 +76,7 @@ export default function UserHeader ({ user }) {
prepend=<InputGroup.Text>@</InputGroup.Text>
name='name'
autoFocus
noBottomMargin
groupClassName={`mb-0 ${styles.username}`}
showValid
/>
<Satistics user={user} />

View File

@ -0,0 +1,3 @@
.username {
width: 300px;
}

6915
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
},
"dependencies": {
"@apollo/client": "^3.3.13",
"@prisma/client": "^2.22.1",
"@prisma/client": "^2.23.0",
"apollo-server-micro": "^2.21.2",
"async-retry": "^1.3.1",
"bootstrap": "^4.6.0",
@ -44,7 +44,7 @@
"babel-plugin-inline-react-svg": "^2.0.1",
"eslint": "^7.22.0",
"eslint-plugin-compat": "^3.9.0",
"prisma": "^2.22.1",
"prisma": "^2.23.0",
"standard": "^16.0.3"
}
}

View File

@ -2,6 +2,7 @@ import '../styles/globals.scss'
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client'
import { Provider } from 'next-auth/client'
import { FundErrorModal, FundErrorProvider } from '../components/fund-error'
import { MeProvider } from '../components/me'
const client = new ApolloClient({
uri: '/api/graphql',
@ -11,12 +12,14 @@ const client = new ApolloClient({
function MyApp ({ Component, pageProps }) {
return (
<Provider session={pageProps.session}>
<FundErrorProvider>
<FundErrorModal />
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
</FundErrorProvider>
<ApolloProvider client={client}>
<MeProvider>
<FundErrorProvider>
<FundErrorModal />
<Component {...pageProps} />
</FundErrorProvider>
</MeProvider>
</ApolloProvider>
</Provider>
)
}

View File

@ -6,6 +6,7 @@ import * as Yup from 'yup'
import { gql, useMutation } from '@apollo/client'
import LayoutCenter from '../components/layout-center'
import { ensureProtocol } from '../lib/url'
import { useMe } from '../components/me'
export const DiscussionSchema = Yup.object({
title: Yup.string().required('required').trim()
@ -115,6 +116,7 @@ export function LinkForm () {
export function PostForm () {
const router = useRouter()
const me = useMe()
if (!router.query.type) {
return (
@ -126,6 +128,9 @@ export function PostForm () {
<Link href='/post?type=discussion'>
<Button variant='secondary'>discussion</Button>
</Link>
{me?.freePosts
? <div className='text-center font-weight-bold mt-3 text-success'>{me.freePosts} free posts left</div>
: null}
</div>
)
}

View File

@ -0,0 +1,49 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "freeComments" INTEGER NOT NULL DEFAULT 5,
ADD COLUMN "freePosts" INTEGER NOT NULL DEFAULT 2;
-- if user has free comments or posts, use that
CREATE OR REPLACE FUNCTION create_item(title TEXT, url TEXT, text TEXT, parent_id INTEGER, username TEXT)
RETURNS "Item"
LANGUAGE plpgsql
AS $$
DECLARE
user_id INTEGER;
user_sats INTEGER;
free_posts INTEGER;
free_comments INTEGER;
freebie BOOLEAN;
item "Item";
BEGIN
PERFORM ASSERT_SERIALIZED();
SELECT (msats / 1000), id, "freePosts", "freeComments"
INTO user_sats, user_id, free_posts, free_comments
FROM users WHERE name = username;
freebie := (parent_id IS NULL AND free_posts > 0) OR (parent_id IS NOT NULL AND free_comments > 0);
IF NOT freebie AND 1 > user_sats THEN
RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
END IF;
INSERT INTO "Item" (title, url, text, "userId", "parentId", created_at, updated_at)
VALUES (title, url, text, user_id, parent_id, now_utc(), now_utc()) RETURNING * INTO item;
IF freebie THEN
IF parent_id IS NULL THEN
UPDATE users SET "freePosts" = "freePosts" - 1 WHERE id = user_id;
ELSE
UPDATE users SET "freeComments" = "freeComments" - 1 WHERE id = user_id;
END IF;
ELSE
UPDATE users SET msats = msats - 1000 WHERE id = user_id;
INSERT INTO "Vote" (sats, "itemId", "userId", created_at, updated_at)
VALUES (1, item.id, user_id, now_utc(), now_utc());
END IF;
RETURN item;
END;
$$;

View File

@ -24,6 +24,8 @@ model User {
invoices Invoice[]
withdrawls Withdrawl[]
msats Int @default(0)
freeComments Int @default(5)
freePosts Int @default(2)
@@map(name: "users")
}

View File

@ -340,22 +340,22 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
"@prisma/client@^2.22.1":
version "2.22.1"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.22.1.tgz#10fdcd1532a6baf46dd1c464cad9a54af0532bc8"
integrity sha512-JQjbsY6QSfFiovXHEp5WeJHa5p2CuR1ZFPAeYXmUsOAQOaMCrhgQmKAL6w2Q3SRA7ALqPjrKywN9/QfBc4Kp1A==
"@prisma/client@^2.23.0":
version "2.23.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.23.0.tgz#4bf16ab19b140873ba79bd159da86842b1746e0a"
integrity sha512-xsHdo3+wIH0hJVGfKHYTEKtifStjKH0b5t8t7hV32Fypq6+3uxhAi3F25yxuI4XSHXg21nb7Ha82lNwU/0TERA==
dependencies:
"@prisma/engines-version" "2.22.0-21.60cc71d884972ab4e897f0277c4b84383dddaf6c"
"@prisma/engines-version" "2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b"
"@prisma/engines-version@2.22.0-21.60cc71d884972ab4e897f0277c4b84383dddaf6c":
version "2.22.0-21.60cc71d884972ab4e897f0277c4b84383dddaf6c"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-2.22.0-21.60cc71d884972ab4e897f0277c4b84383dddaf6c.tgz#e98ee17217a0ebb54f2f9314fbbfd610b05e6e31"
integrity sha512-OkkVwk6iTzTbwwl8JIKAENyxmh4TFORal55QMKQzrHEY8UzbD0M90mQnmziz3PAopQUZgTFFMlaPAq1WNrLMtA==
"@prisma/engines-version@2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b":
version "2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b.tgz#c813279bbea48dedad039b0bc3b044117d2dbaa1"
integrity sha512-VNgnOe+oPQKmy3HOtWi/Q1fvcKZUQkf1OfTD1pzrLBx9tJPejyxt1Mq54L+OOAuYvfrua6bmfojFVLh7uXuWVw==
"@prisma/engines@2.22.0-21.60cc71d884972ab4e897f0277c4b84383dddaf6c":
version "2.22.0-21.60cc71d884972ab4e897f0277c4b84383dddaf6c"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.22.0-21.60cc71d884972ab4e897f0277c4b84383dddaf6c.tgz#4ccd255e0823605db3d8387a5195b6fdabe3b0c0"
integrity sha512-KmWdogrsfsSLYvfqY3cS3QcDGzaEFklE+T6dNJf+k/KPQum4A29IwDalafMwh5cMN8ivZobUbowNSwWJrMT08Q==
"@prisma/engines@2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b":
version "2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b.tgz#440abe0ebef44b6e1bdaf2b4d14fcde9fe74f18c"
integrity sha512-Tgk3kggO5B9IT6mimJAw6HSxbFoDAuDKL3sHHSS41EnQm76j/nf4uhGZFPzOQwZWOLeT5ZLO2khr4/FCA9Nkhw==
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
@ -4234,12 +4234,12 @@ pretty-format@^3.8.0:
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
integrity sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=
prisma@^2.22.1:
version "2.22.1"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-2.22.1.tgz#884687a90c7b797b34c6110ea413049078c8da6e"
integrity sha512-hwvCM3zyxgSda/+/p+GW7nz93jRebtMU01wAG7YVVnl0OKU+dpw1wPvPFmQRldkZHk8fTCleYmjc24WaSdVPZQ==
prisma@^2.23.0:
version "2.23.0"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-2.23.0.tgz#6464cca0e085ed23b1815013a67c868eff07a7d2"
integrity sha512-3c/lmDy8nsPcEsfCufvCTJUEuwmAcTPbeGg9fL1qjlvS314duLUA/k2nm3n1rq4ImKqzeC5uaKfvI2IoAfwrJA==
dependencies:
"@prisma/engines" "2.22.0-21.60cc71d884972ab4e897f0277c4b84383dddaf6c"
"@prisma/engines" "2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b"
process-nextick-args@~2.0.0:
version "2.0.1"