finish mvp
This commit is contained in:
parent
a9ea341a7b
commit
80ff13abd6
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ export default gql`
|
|||
nitems: Int!
|
||||
ncomments: Int!
|
||||
stacked: Int!
|
||||
freePosts: Int!
|
||||
freeComments: Int!
|
||||
sats: Int!
|
||||
msats: Int!
|
||||
}
|
||||
|
|
|
@ -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))}
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.username {
|
||||
width: 300px;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
$$;
|
|
@ -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")
|
||||
}
|
||||
|
|
36
yarn.lock
36
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue