WIP bios
This commit is contained in:
parent
7e62fad69c
commit
e7787e3e67
|
@ -4,6 +4,6 @@ const prisma = global.prisma || new PrismaClient({
|
||||||
log: ['warn', 'error']
|
log: ['warn', 'error']
|
||||||
})
|
})
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') global.prisma = prisma
|
global.prisma = prisma
|
||||||
|
|
||||||
export default prisma
|
export default prisma
|
||||||
|
|
|
@ -21,6 +21,18 @@ async function comments (models, id) {
|
||||||
return nestComments(flat, id)[0]
|
return nestComments(flat, id)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getItem (parent, { id }, { models }) {
|
||||||
|
console.log(id)
|
||||||
|
const [item] = await models.$queryRaw(`
|
||||||
|
${SELECT}
|
||||||
|
FROM "Item"
|
||||||
|
WHERE id = $1`, Number(id))
|
||||||
|
if (item) {
|
||||||
|
item.comments = comments(models, id)
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
moreItems: async (parent, { sort, cursor, userId }, { me, models }) => {
|
moreItems: async (parent, { sort, cursor, userId }, { me, models }) => {
|
||||||
|
@ -82,16 +94,7 @@ export default {
|
||||||
comments
|
comments
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
item: async (parent, { id }, { models }) => {
|
item: getItem,
|
||||||
const [item] = await models.$queryRaw(`
|
|
||||||
${SELECT}
|
|
||||||
FROM "Item"
|
|
||||||
WHERE id = $1`, Number(id))
|
|
||||||
if (item) {
|
|
||||||
item.comments = comments(models, id)
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
},
|
|
||||||
userComments: async (parent, { userId }, { models }) => {
|
userComments: async (parent, { userId }, { models }) => {
|
||||||
return await models.$queryRaw(`
|
return await models.$queryRaw(`
|
||||||
${SELECT}
|
${SELECT}
|
||||||
|
@ -363,7 +366,7 @@ export default {
|
||||||
|
|
||||||
const namePattern = /\B@[\w_]+/gi
|
const namePattern = /\B@[\w_]+/gi
|
||||||
|
|
||||||
const createMentions = async (item, models) => {
|
export const createMentions = async (item, models) => {
|
||||||
// if we miss a mention, in the rare circumstance there's some kind of
|
// if we miss a mention, in the rare circumstance there's some kind of
|
||||||
// failure, it's not a big deal so we don't do it transactionally
|
// failure, it's not a big deal so we don't do it transactionally
|
||||||
// ideally, we probably would
|
// ideally, we probably would
|
||||||
|
@ -456,7 +459,7 @@ function nestComments (flat, parentId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have to do our own query because ltree is unsupported
|
// we have to do our own query because ltree is unsupported
|
||||||
const SELECT =
|
export const SELECT =
|
||||||
`SELECT "Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title,
|
`SELECT "Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title,
|
||||||
"Item".text, "Item".url, "Item"."userId", "Item"."parentId", ltree2text("Item"."path") AS "path"`
|
"Item".text, "Item".url, "Item"."userId", "Item"."parentId", ltree2text("Item"."path") AS "path"`
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,21 @@
|
||||||
import { AuthenticationError, UserInputError } from 'apollo-server-errors'
|
import { AuthenticationError, UserInputError } from 'apollo-server-errors'
|
||||||
|
import { createMentions, getItem, SELECT } from './item'
|
||||||
|
import serialize from './serial'
|
||||||
|
|
||||||
|
export const createBio = async (parent, { title, text }, { me, models }) => {
|
||||||
|
if (!me) {
|
||||||
|
throw new AuthenticationError('you must be logged in')
|
||||||
|
}
|
||||||
|
|
||||||
|
const [item] = await serialize(models,
|
||||||
|
models.$queryRaw(`${SELECT} FROM create_bio($1, $2, $3) AS "Item"`,
|
||||||
|
title, text, Number(me.id)))
|
||||||
|
|
||||||
|
await createMentions(item, models)
|
||||||
|
|
||||||
|
item.comments = []
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
|
@ -32,7 +49,8 @@ export default {
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
createBio: createBio
|
||||||
},
|
},
|
||||||
|
|
||||||
User: {
|
User: {
|
||||||
|
@ -54,6 +72,10 @@ export default {
|
||||||
sats: async (user, args, { models }) => {
|
sats: async (user, args, { models }) => {
|
||||||
return Math.floor(user.msats / 1000)
|
return Math.floor(user.msats / 1000)
|
||||||
},
|
},
|
||||||
|
bio: async (user, args, { models }) => {
|
||||||
|
console.log(user)
|
||||||
|
return getItem(user, { id: user.bioId }, { models })
|
||||||
|
},
|
||||||
hasNewNotes: async (user, args, { models }) => {
|
hasNewNotes: async (user, args, { models }) => {
|
||||||
// check if any votes have been cast for them since checkedNotesAt
|
// check if any votes have been cast for them since checkedNotesAt
|
||||||
const votes = await models.$queryRaw(`
|
const votes = await models.$queryRaw(`
|
||||||
|
|
|
@ -10,6 +10,7 @@ export default gql`
|
||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
setName(name: String!): Boolean
|
setName(name: String!): Boolean
|
||||||
|
createBio(title: String!, text: String): Item!
|
||||||
}
|
}
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
|
@ -22,6 +23,7 @@ export default gql`
|
||||||
freeComments: Int!
|
freeComments: Int!
|
||||||
hasNewNotes: Boolean!
|
hasNewNotes: Boolean!
|
||||||
tipDefault: Int!
|
tipDefault: Int!
|
||||||
|
bio: Item
|
||||||
sats: Int!
|
sats: Int!
|
||||||
msats: Int!
|
msats: Int!
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,10 @@ function Parent ({ item, rootText }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Comment ({ item, children, replyOpen, includeParent, rootText, noComments, noReply }) {
|
export default function Comment ({
|
||||||
|
item, children, replyOpen, includeParent,
|
||||||
|
rootText, noComments, noReply
|
||||||
|
}) {
|
||||||
const [reply, setReply] = useState(replyOpen)
|
const [reply, setReply] = useState(replyOpen)
|
||||||
const [edit, setEdit] = useState()
|
const [edit, setEdit] = useState()
|
||||||
const [collapse, setCollapse] = useState(false)
|
const [collapse, setCollapse] = useState(false)
|
||||||
|
|
|
@ -12,7 +12,11 @@ export const DiscussionSchema = Yup.object({
|
||||||
...AdvPostSchema
|
...AdvPostSchema
|
||||||
})
|
})
|
||||||
|
|
||||||
export function DiscussionForm ({ item, editThreshold }) {
|
export function DiscussionForm ({
|
||||||
|
item, editThreshold, titleLabel = 'title',
|
||||||
|
textLabel = 'text', buttonText = 'post',
|
||||||
|
adv, handleSubmit
|
||||||
|
}) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [createDiscussion] = useMutation(
|
const [createDiscussion] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
|
@ -53,7 +57,7 @@ export function DiscussionForm ({ item, editThreshold }) {
|
||||||
...AdvPostInitial
|
...AdvPostInitial
|
||||||
}}
|
}}
|
||||||
schema={DiscussionSchema}
|
schema={DiscussionSchema}
|
||||||
onSubmit={async ({ boost, ...values }) => {
|
onSubmit={handleSubmit || (async ({ boost, ...values }) => {
|
||||||
let id, error
|
let id, error
|
||||||
if (item) {
|
if (item) {
|
||||||
({ data: { updateDiscussion: { id } }, error } = await updateDiscussion({ variables: { ...values, id: item.id } }))
|
({ data: { updateDiscussion: { id } }, error } = await updateDiscussion({ variables: { ...values, id: item.id } }))
|
||||||
|
@ -63,17 +67,18 @@ export function DiscussionForm ({ item, editThreshold }) {
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new Error({ message: error.toString() })
|
throw new Error({ message: error.toString() })
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push(`/items/${id}`)
|
router.push(`/items/${id}`)
|
||||||
}}
|
})}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
label='title'
|
label={titleLabel}
|
||||||
name='title'
|
name='title'
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<MarkdownInput
|
<MarkdownInput
|
||||||
label={<>text <small className='text-muted ml-2'>optional</small></>}
|
label={<>{textLabel} <small className='text-muted ml-2'>optional</small></>}
|
||||||
name='text'
|
name='text'
|
||||||
as={TextareaAutosize}
|
as={TextareaAutosize}
|
||||||
minRows={4}
|
minRows={4}
|
||||||
|
@ -81,9 +86,9 @@ export function DiscussionForm ({ item, editThreshold }) {
|
||||||
? <Countdown date={editThreshold} />
|
? <Countdown date={editThreshold} />
|
||||||
: null}
|
: null}
|
||||||
/>
|
/>
|
||||||
{!item && <AdvPostForm />}
|
{!item && adv && <AdvPostForm />}
|
||||||
<ActionTooltip>
|
<ActionTooltip>
|
||||||
<SubmitButton variant='secondary' className='mt-3'>{item ? 'save' : 'post'}</SubmitButton>
|
<SubmitButton variant='secondary' className='mt-3'>{item ? 'save' : buttonText}</SubmitButton>
|
||||||
</ActionTooltip>
|
</ActionTooltip>
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
|
|
|
@ -84,12 +84,12 @@ export default function Footer ({ noLinks }) {
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>}
|
||||||
<small>
|
<small>
|
||||||
<a className='text-dark d-inline-flex' href='https://github.com/stackernews/stacker.news'>
|
<a className='text-dark d-inline-block' href='https://github.com/stackernews/stacker.news'>
|
||||||
This is free open source software <Github width={20} height={20} className='mx-1' />
|
This is free open source software<Github width={20} height={20} className='mx-1' />
|
||||||
</a>
|
</a>
|
||||||
<span className='d-inline-flex text-muted'>
|
<span className='d-inline-block text-muted'>
|
||||||
made with sound love in Austin <Texas className='mx-1' width={20} height={20} />
|
made with sound love in Austin<Texas className='mx-1' width={20} height={20} />
|
||||||
by <a href='https://twitter.com/k00bideh' className='text-twitter d-inline-flex'><Twitter width={20} height={20} className='ml-1' />@k00bideh</a>
|
by<a href='https://twitter.com/k00bideh' className='text-twitter d-inline-block'><Twitter width={20} height={20} className='ml-1' />@k00bideh</a>
|
||||||
</span>
|
</span>
|
||||||
</small>
|
</small>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
import Item, { ItemSkeleton } from './item'
|
||||||
|
import Reply, { ReplySkeleton } from './reply'
|
||||||
|
import Comment from './comment'
|
||||||
|
import Text from './text'
|
||||||
|
import Comments, { CommentsSkeleton } from './comments'
|
||||||
|
import { COMMENTS } from '../fragments/comments'
|
||||||
|
import { ITEM_FIELDS } from '../fragments/items'
|
||||||
|
import { gql, useQuery } from '@apollo/client'
|
||||||
|
import styles from '../styles/item.module.css'
|
||||||
|
import { NOFOLLOW_LIMIT } from '../lib/constants'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
|
export default function ItemFull ({ item: qItem, minimal }) {
|
||||||
|
const query = gql`
|
||||||
|
${ITEM_FIELDS}
|
||||||
|
${COMMENTS}
|
||||||
|
{
|
||||||
|
item(id: ${qItem.id}) {
|
||||||
|
...ItemFields
|
||||||
|
text
|
||||||
|
comments {
|
||||||
|
...CommentsRecursive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const { error, data } = useQuery(query, {
|
||||||
|
fetchPolicy: router.query.cache ? 'cache-first' : undefined
|
||||||
|
})
|
||||||
|
if (error) return <div>Failed to load!</div>
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ItemSkeleton>
|
||||||
|
<ReplySkeleton />
|
||||||
|
</ItemSkeleton>
|
||||||
|
<div className={styles.comments}>
|
||||||
|
<CommentsSkeleton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { item } = data
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{item.parentId
|
||||||
|
? <Comment item={item} replyOpen includeParent noComments />
|
||||||
|
: (minimal
|
||||||
|
? (
|
||||||
|
<>
|
||||||
|
{item.text &&
|
||||||
|
<div className='mb-3'>
|
||||||
|
<Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.text}</Text>
|
||||||
|
</div>}
|
||||||
|
</>)
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<Item item={item}>
|
||||||
|
{item.text &&
|
||||||
|
<div className='mb-3'>
|
||||||
|
<Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.text}</Text>
|
||||||
|
</div>}
|
||||||
|
<Reply parentId={item.id} />
|
||||||
|
</Item>
|
||||||
|
</>)
|
||||||
|
)}
|
||||||
|
<div className={styles.comments}>
|
||||||
|
<Comments comments={item.comments} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import { LightningProvider } from './lightning'
|
||||||
import Footer from './footer'
|
import Footer from './footer'
|
||||||
import Seo from './seo'
|
import Seo from './seo'
|
||||||
|
|
||||||
export default function Layout ({ noContain, noFooter, noFooterLinks, noSeo, children }) {
|
export default function Layout ({ noContain, noFooter, noFooterLinks, containClassName, noSeo, children }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!noSeo && <Seo />}
|
{!noSeo && <Seo />}
|
||||||
|
@ -17,7 +17,7 @@ export default function Layout ({ noContain, noFooter, noFooterLinks, noSeo, chi
|
||||||
{noContain
|
{noContain
|
||||||
? children
|
? children
|
||||||
: (
|
: (
|
||||||
<Container className='px-sm-0'>
|
<Container className={`px-sm-0 ${containClassName || ''}`}>
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default function UserHeader ({ user }) {
|
||||||
const client = useApolloClient()
|
const client = useApolloClient()
|
||||||
const [setName] = useMutation(NAME_MUTATION)
|
const [setName] = useMutation(NAME_MUTATION)
|
||||||
|
|
||||||
const Satistics = () => <h1 className='ml-2'><small className='text-success'>{user.sats} sats \ {user.stacked} stacked</small></h1>
|
const Satistics = () => <h1 className='mb-0'><small className='text-success'>{user.sats} sats \ {user.stacked} stacked</small></h1>
|
||||||
|
|
||||||
const UserSchema = Yup.object({
|
const UserSchema = Yup.object({
|
||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
|
@ -51,66 +51,73 @@ export default function UserHeader ({ user }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editting
|
<div>
|
||||||
? (
|
{editting
|
||||||
<Form
|
? (
|
||||||
className='d-flex align-items-center flex-wrap'
|
<Form
|
||||||
schema={UserSchema}
|
schema={UserSchema}
|
||||||
initial={{
|
initial={{
|
||||||
name: user.name
|
name: user.name
|
||||||
}}
|
}}
|
||||||
validateImmediately
|
className='d-flex align-items-center'
|
||||||
onSubmit={async ({ name }) => {
|
validateImmediately
|
||||||
if (name === user.name) {
|
onSubmit={async ({ name }) => {
|
||||||
setEditting(false)
|
if (name === user.name) {
|
||||||
return
|
setEditting(false)
|
||||||
}
|
return
|
||||||
const { error } = await setName({ variables: { name } })
|
}
|
||||||
if (error) {
|
const { error } = await setName({ variables: { name } })
|
||||||
throw new Error({ message: error.toString() })
|
if (error) {
|
||||||
}
|
throw new Error({ message: error.toString() })
|
||||||
router.replace(`/${name}`)
|
}
|
||||||
session.user.name = name
|
router.replace(`/${name}`)
|
||||||
|
session.user.name = name
|
||||||
|
|
||||||
client.writeFragment({
|
client.writeFragment({
|
||||||
id: `User:${user.id}`,
|
id: `User:${user.id}`,
|
||||||
fragment: gql`
|
fragment: gql`
|
||||||
fragment CurUser on User {
|
fragment CurUser on User {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
data: {
|
data: {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
setEditting(false)
|
setEditting(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
prepend=<InputGroup.Text>@</InputGroup.Text>
|
prepend=<InputGroup.Text>@</InputGroup.Text>
|
||||||
name='name'
|
name='name'
|
||||||
autoFocus
|
autoFocus
|
||||||
groupClassName={`mb-0 ${styles.username}`}
|
groupClassName={`mb-0 ${styles.username}`}
|
||||||
showValid
|
showValid
|
||||||
/>
|
/>
|
||||||
<Satistics user={user} />
|
<SubmitButton variant='link' onClick={() => setEditting(true)}>save</SubmitButton>
|
||||||
<SubmitButton className='ml-2' variant='info' size='sm' onClick={() => setEditting(true)}>save</SubmitButton>
|
</Form>
|
||||||
</Form>
|
)
|
||||||
)
|
: (
|
||||||
: (
|
<div className='d-flex align-items-center'>
|
||||||
<div className='d-flex align-items-center flex-wrap'>
|
<h2 className='mb-0'>@{user.name}</h2>
|
||||||
<h1>@{user.name}</h1>
|
{session?.user?.name === user.name &&
|
||||||
<Satistics user={user} />
|
<Button variant='link' onClick={() => setEditting(true)}>edit nym</Button>}
|
||||||
{session && session.user && session.user.name === user.name &&
|
</div>
|
||||||
<Button className='ml-2' variant='boost' size='sm' onClick={() => setEditting(true)}>edit</Button>}
|
)}
|
||||||
</div>
|
<Satistics user={user} />
|
||||||
)}
|
</div>
|
||||||
<Nav
|
<Nav
|
||||||
|
className={styles.nav}
|
||||||
activeKey={router.asPath}
|
activeKey={router.asPath}
|
||||||
>
|
>
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Link href={'/' + user.name} passHref>
|
<Link href={'/' + user.name} passHref>
|
||||||
|
<Nav.Link>bio</Nav.Link>
|
||||||
|
</Link>
|
||||||
|
</Nav.Item>
|
||||||
|
<Nav.Item>
|
||||||
|
<Link href={'/' + user.name + '/posts'} passHref>
|
||||||
<Nav.Link>{user.nitems} posts</Nav.Link>
|
<Nav.Link>{user.nitems} posts</Nav.Link>
|
||||||
</Link>
|
</Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
|
@ -121,7 +128,7 @@ export default function UserHeader ({ user }) {
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
{/* <Nav.Item>
|
{/* <Nav.Item>
|
||||||
<Link href={'/' + user.name + '/sativity'} passHref>
|
<Link href={'/' + user.name + '/sativity'} passHref>
|
||||||
<Nav.Link>sativity</Nav.Link>
|
<Nav.Link>satistics</Nav.Link>
|
||||||
</Link>
|
</Link>
|
||||||
</Nav.Item> */}
|
</Nav.Item> */}
|
||||||
</Nav>
|
</Nav>
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
.username {
|
.username {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
margin-top: 1rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav div:first-child a {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav div:last-child a {
|
||||||
|
padding-right: 0;
|
||||||
}
|
}
|
|
@ -1,14 +1,22 @@
|
||||||
import Layout from '../components/layout'
|
import Layout from '../components/layout'
|
||||||
import Items from '../components/items'
|
import { gql, useMutation } from '@apollo/client'
|
||||||
import { gql } from '@apollo/client'
|
|
||||||
import ApolloClient from '../api/client'
|
import ApolloClient from '../api/client'
|
||||||
import UserHeader from '../components/user-header'
|
import UserHeader from '../components/user-header'
|
||||||
import Seo from '../components/seo'
|
import Seo from '../components/seo'
|
||||||
|
import { Button } from 'react-bootstrap'
|
||||||
|
import styles from '../styles/user.module.css'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { DiscussionForm } from '../components/discussion-form'
|
||||||
|
import { useSession } from 'next-auth/client'
|
||||||
|
import { ITEM_FIELDS } from '../fragments/items'
|
||||||
|
import ItemFull from '../components/item-full'
|
||||||
|
|
||||||
export async function getServerSideProps ({ req, params }) {
|
export async function getServerSideProps ({ req, params }) {
|
||||||
const { error, data: { user } } = await (await ApolloClient(req)).query({
|
const { error, data: { user } } = await (await ApolloClient(req)).query({
|
||||||
query:
|
query:
|
||||||
gql`{
|
gql`
|
||||||
|
${ITEM_FIELDS}
|
||||||
|
{
|
||||||
user(name: "${params.username}") {
|
user(name: "${params.username}") {
|
||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
|
@ -17,6 +25,9 @@ export async function getServerSideProps ({ req, params }) {
|
||||||
ncomments
|
ncomments
|
||||||
stacked
|
stacked
|
||||||
sats
|
sats
|
||||||
|
bio {
|
||||||
|
...ItemFields
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
})
|
})
|
||||||
|
@ -34,12 +45,61 @@ export async function getServerSideProps ({ req, params }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function User ({ user }) {
|
function BioForm () {
|
||||||
|
const [createBio] = useMutation(
|
||||||
|
gql`
|
||||||
|
mutation createBio($title: String!, $text: String) {
|
||||||
|
createBio(title: $title, text: $text) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}`, {
|
||||||
|
update (cache, { data: { createBio } }) {
|
||||||
|
cache.modify({
|
||||||
|
id: `User:${createBio.userId}`,
|
||||||
|
fields: {
|
||||||
|
bio () {
|
||||||
|
return createBio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout noSeo>
|
<div className={styles.createFormContainer}>
|
||||||
|
<DiscussionForm
|
||||||
|
titleLabel='one line bio' textLabel='full bio' buttonText='create'
|
||||||
|
handleSubmit={async values => {
|
||||||
|
const { error } = await createBio({ variables: values })
|
||||||
|
if (error) {
|
||||||
|
throw new Error({ message: error.toString() })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function User ({ user }) {
|
||||||
|
const [create, setCreate] = useState(false)
|
||||||
|
const [session] = useSession()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout noSeo containClassName={styles.contain}>
|
||||||
<Seo user={user} />
|
<Seo user={user} />
|
||||||
<UserHeader user={user} />
|
<UserHeader user={user} />
|
||||||
<Items variables={{ sort: 'user', userId: user.id }} />
|
{user.bio
|
||||||
|
? <ItemFull item={user.bio} minimal />
|
||||||
|
: (
|
||||||
|
<div className={styles.create}>
|
||||||
|
{create
|
||||||
|
? <BioForm />
|
||||||
|
: (
|
||||||
|
session?.user?.name === user.name &&
|
||||||
|
<Button onClick={setCreate} size='md' variant='secondary'>create bio</Button>
|
||||||
|
)}
|
||||||
|
</div>)}
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import Layout from '../../components/layout'
|
||||||
|
import { gql } from '@apollo/client'
|
||||||
|
import ApolloClient from '../../api/client'
|
||||||
|
import UserHeader from '../../components/user-header'
|
||||||
|
import Seo from '../../components/seo'
|
||||||
|
import Items from '../../components/items'
|
||||||
|
|
||||||
|
export async function getServerSideProps ({ req, params }) {
|
||||||
|
const { error, data: { user } } = await (await ApolloClient(req)).query({
|
||||||
|
query:
|
||||||
|
gql`{
|
||||||
|
user(name: "${params.username}") {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
name
|
||||||
|
nitems
|
||||||
|
ncomments
|
||||||
|
stacked
|
||||||
|
sats
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user || error) {
|
||||||
|
return {
|
||||||
|
notFound: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UserPosts ({ user }) {
|
||||||
|
return (
|
||||||
|
<Layout noSeo>
|
||||||
|
<Seo user={user} />
|
||||||
|
<UserHeader user={user} />
|
||||||
|
<Items variables={{ sort: 'user', userId: user.id }} />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,17 +1,9 @@
|
||||||
import Item, { ItemSkeleton } from '../../components/item'
|
|
||||||
import Layout from '../../components/layout'
|
import Layout from '../../components/layout'
|
||||||
import Reply, { ReplySkeleton } from '../../components/reply'
|
|
||||||
import Comment from '../../components/comment'
|
|
||||||
import Text from '../../components/text'
|
|
||||||
import Comments, { CommentsSkeleton } from '../../components/comments'
|
|
||||||
import { COMMENTS } from '../../fragments/comments'
|
|
||||||
import { ITEM_FIELDS } from '../../fragments/items'
|
import { ITEM_FIELDS } from '../../fragments/items'
|
||||||
import { gql, useQuery } from '@apollo/client'
|
import { gql } from '@apollo/client'
|
||||||
import styles from '../../styles/item.module.css'
|
|
||||||
import Seo from '../../components/seo'
|
import Seo from '../../components/seo'
|
||||||
import ApolloClient from '../../api/client'
|
import ApolloClient from '../../api/client'
|
||||||
import { NOFOLLOW_LIMIT } from '../../lib/constants'
|
import ItemFull from '../../components/item-full'
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
|
|
||||||
// ssr the item without comments so that we can populate metatags
|
// ssr the item without comments so that we can populate metatags
|
||||||
export async function getServerSideProps ({ req, params: { id } }) {
|
export async function getServerSideProps ({ req, params: { id } }) {
|
||||||
|
@ -46,65 +38,11 @@ export async function getServerSideProps ({ req, params: { id } }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FullItem ({ item }) {
|
export default function AnItem ({ item }) {
|
||||||
const query = gql`
|
|
||||||
${ITEM_FIELDS}
|
|
||||||
${COMMENTS}
|
|
||||||
{
|
|
||||||
item(id: ${item.id}) {
|
|
||||||
...ItemFields
|
|
||||||
text
|
|
||||||
comments {
|
|
||||||
...CommentsRecursive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout noSeo>
|
<Layout noSeo>
|
||||||
<Seo item={item} />
|
<Seo item={item} />
|
||||||
<LoadItem query={query} />
|
<ItemFull item={item} />
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function LoadItem ({ query }) {
|
|
||||||
const router = useRouter()
|
|
||||||
const { error, data } = useQuery(query, {
|
|
||||||
fetchPolicy: router.query.cache ? 'cache-first' : undefined
|
|
||||||
})
|
|
||||||
if (error) return <div>Failed to load!</div>
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ItemSkeleton>
|
|
||||||
<ReplySkeleton />
|
|
||||||
</ItemSkeleton>
|
|
||||||
<div className={styles.comments}>
|
|
||||||
<CommentsSkeleton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { item } = data
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{item.parentId
|
|
||||||
? <Comment item={item} replyOpen includeParent noComments />
|
|
||||||
: (
|
|
||||||
<>
|
|
||||||
<Item item={item}>
|
|
||||||
{item.text && <div className='mb-3'><Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.text}</Text></div>}
|
|
||||||
<Reply parentId={item.id} />
|
|
||||||
</Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<div className={styles.comments}>
|
|
||||||
<Comments comments={item.comments} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "bioId" INTEGER;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "users" ADD FOREIGN KEY ("bioId") REFERENCES "Item"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
@ -0,0 +1,16 @@
|
||||||
|
CREATE OR REPLACE FUNCTION create_bio(title TEXT, text TEXT, user_id INTEGER)
|
||||||
|
RETURNS "Item"
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
item "Item";
|
||||||
|
BEGIN
|
||||||
|
PERFORM ASSERT_SERIALIZED();
|
||||||
|
|
||||||
|
SELECT * INTO item FROM create_item(title, NULL, text, 0, NULL, user_id);
|
||||||
|
|
||||||
|
UPDATE users SET "bioId" = item.id WHERE id = user_id;
|
||||||
|
|
||||||
|
RETURN item;
|
||||||
|
END;
|
||||||
|
$$;
|
|
@ -24,6 +24,8 @@ model User {
|
||||||
actions ItemAct[]
|
actions ItemAct[]
|
||||||
invoices Invoice[]
|
invoices Invoice[]
|
||||||
withdrawls Withdrawl[]
|
withdrawls Withdrawl[]
|
||||||
|
bio Item? @relation(name: "Item", fields: [bioId], references: [id])
|
||||||
|
bioId Int?
|
||||||
msats Int @default(0)
|
msats Int @default(0)
|
||||||
freeComments Int @default(5)
|
freeComments Int @default(5)
|
||||||
freePosts Int @default(2)
|
freePosts Int @default(2)
|
||||||
|
@ -65,6 +67,7 @@ model Item {
|
||||||
mentions Mention[]
|
mentions Mention[]
|
||||||
path Unsupported("LTREE")?
|
path Unsupported("LTREE")?
|
||||||
|
|
||||||
|
User User[] @relation("Item")
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([parentId])
|
@@index([parentId])
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
.contain {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create {
|
||||||
|
flex-grow: 1;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.createFormContainer {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 740px;
|
||||||
|
}
|
Loading…
Reference in New Issue