user bios mostly working
This commit is contained in:
parent
027ba6a048
commit
a339516a54
@ -2,21 +2,6 @@ import { AuthenticationError, UserInputError } from 'apollo-server-errors'
|
||||
import { createMentions, getItem, SELECT } from './item'
|
||||
import serialize from './serial'
|
||||
|
||||
export const createBio = async (parent, { bio }, { 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"`,
|
||||
`@${me.name}'s bio`, bio, Number(me.id)))
|
||||
|
||||
await createMentions(item, models)
|
||||
|
||||
item.comments = []
|
||||
return item
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
me: async (parent, args, { models, me }) =>
|
||||
@ -50,7 +35,31 @@ export default {
|
||||
throw error
|
||||
}
|
||||
},
|
||||
createBio: createBio
|
||||
upsertBio: async (parent, { bio }, { me, models }) => {
|
||||
if (!me) {
|
||||
throw new AuthenticationError('you must be logged in')
|
||||
}
|
||||
|
||||
const user = await models.user.findUnique({ where: { id: me.id } })
|
||||
|
||||
let item
|
||||
if (user.bioId) {
|
||||
item = await models.item.update({
|
||||
where: { id: Number(user.bioId) },
|
||||
data: {
|
||||
text: bio
|
||||
}
|
||||
})
|
||||
} else {
|
||||
([item] = await serialize(models,
|
||||
models.$queryRaw(`${SELECT} FROM create_bio($1, $2, $3) AS "Item"`,
|
||||
`@${me.name}'s bio`, bio, Number(me.id))))
|
||||
}
|
||||
|
||||
await createMentions(item, models)
|
||||
|
||||
return await models.user.findUnique({ where: { id: me.id } })
|
||||
}
|
||||
},
|
||||
|
||||
User: {
|
||||
|
@ -10,11 +10,12 @@ export default gql`
|
||||
|
||||
extend type Mutation {
|
||||
setName(name: String!): Boolean
|
||||
createBio(bio: String!): Item!
|
||||
upsertBio(bio: String!): User!
|
||||
}
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
createdAt: String!
|
||||
name: String
|
||||
nitems: Int!
|
||||
ncomments: Int!
|
||||
|
@ -41,7 +41,7 @@ function Parent ({ item, rootText }) {
|
||||
|
||||
export default function Comment ({
|
||||
item, children, replyOpen, includeParent,
|
||||
rootText, noComments
|
||||
rootText, noComments, noReply
|
||||
}) {
|
||||
const [edit, setEdit] = useState()
|
||||
const [collapse, setCollapse] = useState(false)
|
||||
@ -130,9 +130,10 @@ export default function Comment ({
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${styles.children}`}>
|
||||
<Reply
|
||||
parentId={item.id} replyOpen={replyOpen}
|
||||
/>
|
||||
{!noReply &&
|
||||
<Reply
|
||||
parentId={item.id} replyOpen={replyOpen}
|
||||
/>}
|
||||
{children}
|
||||
<div className={`${styles.comments} ml-sm-1 ml-md-3`}>
|
||||
{item.comments && !noComments
|
||||
|
@ -1,18 +1,18 @@
|
||||
import Item, { ItemSkeleton } from './item'
|
||||
import Reply, { ReplySkeleton } from './reply'
|
||||
import Item from './item'
|
||||
import Reply from './reply'
|
||||
import Comment from './comment'
|
||||
import Text from './text'
|
||||
import Comments, { CommentsSkeleton } from './comments'
|
||||
import Comments 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'
|
||||
import Link from 'next/link'
|
||||
import { useMe } from './me'
|
||||
import { Button } from 'react-bootstrap'
|
||||
|
||||
function BioItem ({ item }) {
|
||||
function BioItem ({ item, handleClick }) {
|
||||
const me = useMe()
|
||||
if (!item.text) {
|
||||
return null
|
||||
@ -22,9 +22,12 @@ function BioItem ({ item }) {
|
||||
<>
|
||||
<ItemText item={item} />
|
||||
{me?.name === item.user.name &&
|
||||
<Link href={`/items/${item.id}/edit`} passHref>
|
||||
<a className='text-right'>edit bio</a>
|
||||
</Link>}
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
size='md' variant='link'
|
||||
className='text-right'
|
||||
>edit bio
|
||||
</Button>}
|
||||
<Reply parentId={item.id} />
|
||||
</>
|
||||
)
|
||||
@ -43,12 +46,12 @@ function ItemText ({ item }) {
|
||||
return <Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.text}</Text>
|
||||
}
|
||||
|
||||
export default function ItemFull ({ item: qItem, bio }) {
|
||||
export default function ItemFull ({ item, bio, ...props }) {
|
||||
const query = gql`
|
||||
${ITEM_FIELDS}
|
||||
${COMMENTS}
|
||||
{
|
||||
item(id: ${qItem.id}) {
|
||||
item(id: ${item.id}) {
|
||||
...ItemFields
|
||||
text
|
||||
comments {
|
||||
@ -61,34 +64,27 @@ export default function ItemFull ({ item: qItem, bio }) {
|
||||
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>
|
||||
)
|
||||
if (error) {
|
||||
return <div>Failed to load!</div>
|
||||
}
|
||||
|
||||
const { item } = data
|
||||
// XXX replace item with cache version
|
||||
if (data) {
|
||||
({ item } = data)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{item.parentId
|
||||
? <Comment item={item} replyOpen includeParent noComments />
|
||||
? <Comment item={item} replyOpen includeParent noComments {...props} />
|
||||
: (bio
|
||||
? <BioItem item={item} />
|
||||
: <TopLevelItem item={item} />
|
||||
? <BioItem item={item} {...props} />
|
||||
: <TopLevelItem item={item} {...props} />
|
||||
)}
|
||||
<div className={styles.comments}>
|
||||
<Comments comments={item.comments} />
|
||||
</div>
|
||||
{item.comments &&
|
||||
<div className={styles.comments}>
|
||||
<Comments comments={item.comments} />
|
||||
</div>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -59,7 +59,6 @@ export default function UserHeader ({ user }) {
|
||||
initial={{
|
||||
name: user.name
|
||||
}}
|
||||
className='d-flex align-items-center'
|
||||
validateImmediately
|
||||
onSubmit={async ({ name }) => {
|
||||
if (name === user.name) {
|
||||
@ -88,14 +87,16 @@ export default function UserHeader ({ user }) {
|
||||
setEditting(false)
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
prepend=<InputGroup.Text>@</InputGroup.Text>
|
||||
name='name'
|
||||
autoFocus
|
||||
groupClassName={`mb-0 ${styles.username}`}
|
||||
showValid
|
||||
/>
|
||||
<SubmitButton variant='link' onClick={() => setEditting(true)}>save</SubmitButton>
|
||||
<div className='d-flex align-items-center'>
|
||||
<Input
|
||||
prepend=<InputGroup.Text>@</InputGroup.Text>
|
||||
name='name'
|
||||
autoFocus
|
||||
groupClassName={`mb-0 ${styles.username}`}
|
||||
showValid
|
||||
/>
|
||||
<SubmitButton variant='link' onClick={() => setEditting(true)}>save</SubmitButton>
|
||||
</div>
|
||||
</Form>
|
||||
)
|
||||
: (
|
||||
|
18
fragments/users.js
Normal file
18
fragments/users.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { gql } from '@apollo/client'
|
||||
import { ITEM_FIELDS } from './items'
|
||||
|
||||
export const USER_FIELDS = gql`
|
||||
${ITEM_FIELDS}
|
||||
fragment UserFields on User {
|
||||
id
|
||||
createdAt
|
||||
name
|
||||
nitems
|
||||
ncomments
|
||||
stacked
|
||||
sats
|
||||
bio {
|
||||
...ItemFields
|
||||
text
|
||||
}
|
||||
}`
|
@ -1,37 +1,29 @@
|
||||
import Layout from '../components/layout'
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import { gql, useMutation, useQuery } from '@apollo/client'
|
||||
import ApolloClient from '../api/client'
|
||||
import UserHeader from '../components/user-header'
|
||||
import Seo from '../components/seo'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import styles from '../styles/user.module.css'
|
||||
import { useState } from 'react'
|
||||
import { useSession } from 'next-auth/client'
|
||||
import { ITEM_FIELDS } from '../fragments/items'
|
||||
import ItemFull from '../components/item-full'
|
||||
import * as Yup from 'yup'
|
||||
import { Form, MarkdownInput, SubmitButton } from '../components/form'
|
||||
import ActionTooltip from '../components/action-tooltip'
|
||||
import TextareaAutosize from 'react-textarea-autosize'
|
||||
import { useMe } from '../components/me'
|
||||
import { USER_FIELDS } from '../fragments/users'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ITEM_FIELDS } from '../fragments/items'
|
||||
|
||||
export async function getServerSideProps ({ req, params }) {
|
||||
const { error, data: { user } } = await (await ApolloClient(req)).query({
|
||||
query:
|
||||
gql`
|
||||
${ITEM_FIELDS}
|
||||
${USER_FIELDS}
|
||||
{
|
||||
user(name: "${params.username}") {
|
||||
id
|
||||
createdAt
|
||||
name
|
||||
nitems
|
||||
ncomments
|
||||
stacked
|
||||
sats
|
||||
bio {
|
||||
...ItemFields
|
||||
}
|
||||
...UserFields
|
||||
}
|
||||
}`
|
||||
})
|
||||
@ -53,20 +45,25 @@ const BioSchema = Yup.object({
|
||||
bio: Yup.string().required('required').trim()
|
||||
})
|
||||
|
||||
export function BioForm () {
|
||||
const [createBio] = useMutation(
|
||||
export function BioForm ({ handleSuccess, bio }) {
|
||||
const [upsertBio] = useMutation(
|
||||
gql`
|
||||
mutation createBio($bio: String!) {
|
||||
createBio(bio: $bio) {
|
||||
${ITEM_FIELDS}
|
||||
mutation upsertBio($bio: String!) {
|
||||
upsertBio(bio: $bio) {
|
||||
id
|
||||
bio {
|
||||
...ItemFields
|
||||
text
|
||||
}
|
||||
}
|
||||
}`, {
|
||||
update (cache, { data: { createBio } }) {
|
||||
update (cache, { data: { upsertBio } }) {
|
||||
cache.modify({
|
||||
id: `User:${createBio.userId}`,
|
||||
id: `User:${upsertBio.id}`,
|
||||
fields: {
|
||||
bio () {
|
||||
return createBio
|
||||
return upsertBio.bio
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -78,14 +75,15 @@ export function BioForm () {
|
||||
<div className={styles.createFormContainer}>
|
||||
<Form
|
||||
initial={{
|
||||
bio: ''
|
||||
bio: bio?.text || ''
|
||||
}}
|
||||
schema={BioSchema}
|
||||
onSubmit={async values => {
|
||||
const { error } = await createBio({ variables: values })
|
||||
const { error } = await upsertBio({ variables: values })
|
||||
if (error) {
|
||||
throw new Error({ message: error.toString() })
|
||||
}
|
||||
handleSuccess && handleSuccess()
|
||||
}}
|
||||
>
|
||||
<MarkdownInput
|
||||
@ -94,7 +92,7 @@ export function BioForm () {
|
||||
minRows={4}
|
||||
/>
|
||||
<ActionTooltip>
|
||||
<SubmitButton variant='secondary' className='mt-3'>create</SubmitButton>
|
||||
<SubmitButton variant='secondary' className='mt-3'>{bio?.text ? 'save' : 'create'}</SubmitButton>
|
||||
</ActionTooltip>
|
||||
</Form>
|
||||
</div>
|
||||
@ -103,22 +101,55 @@ export function BioForm () {
|
||||
|
||||
export default function User ({ user }) {
|
||||
const [create, setCreate] = useState(false)
|
||||
const [session] = useSession()
|
||||
const [edit, setEdit] = useState(false)
|
||||
const me = useMe()
|
||||
|
||||
const query = gql`
|
||||
${USER_FIELDS}
|
||||
{
|
||||
user(name: "${user.name}") {
|
||||
...UserFields
|
||||
}
|
||||
}`
|
||||
|
||||
const router = useRouter()
|
||||
const { error, data } = useQuery(query, {
|
||||
fetchPolicy: router.query.cache ? 'cache-first' : undefined
|
||||
})
|
||||
if (error) {
|
||||
return <div>Failed to load!</div>
|
||||
}
|
||||
|
||||
// XXX replace item with cache version
|
||||
if (data) {
|
||||
({ user } = data)
|
||||
}
|
||||
|
||||
const mine = me?.name === user.name
|
||||
|
||||
return (
|
||||
<Layout noSeo containClassName={styles.contain}>
|
||||
<Seo user={user} />
|
||||
<UserHeader user={user} />
|
||||
{user.bio
|
||||
? <ItemFull item={user.bio} bio />
|
||||
: (me?.name === user.name &&
|
||||
? (edit
|
||||
? (
|
||||
<div className={styles.create}>
|
||||
<BioForm bio={user.bio} handleSuccess={() => setEdit(false)} />
|
||||
</div>)
|
||||
: <ItemFull item={user.bio} bio handleClick={setEdit} />
|
||||
)
|
||||
: (mine &&
|
||||
<div className={styles.create}>
|
||||
{create
|
||||
? <BioForm />
|
||||
? <BioForm handleSuccess={() => setCreate(false)} />
|
||||
: (
|
||||
session?.user?.name === user.name &&
|
||||
<Button onClick={setCreate} size='md' variant='secondary'>create bio</Button>
|
||||
mine &&
|
||||
<Button
|
||||
onClick={setCreate}
|
||||
size='md' variant='secondary'
|
||||
>create bio
|
||||
</Button>
|
||||
)}
|
||||
</div>)}
|
||||
</Layout>
|
||||
|
Loading…
x
Reference in New Issue
Block a user