diff --git a/api/resolvers/user.js b/api/resolvers/user.js index 63da54a1..983a0b15 100644 --- a/api/resolvers/user.js +++ b/api/resolvers/user.js @@ -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: { diff --git a/api/typeDefs/user.js b/api/typeDefs/user.js index 532bebee..8ce4d543 100644 --- a/api/typeDefs/user.js +++ b/api/typeDefs/user.js @@ -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! diff --git a/components/comment.js b/components/comment.js index 3730edc3..f5d878c0 100644 --- a/components/comment.js +++ b/components/comment.js @@ -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 ({
- + {!noReply && + } {children}
{item.comments && !noComments diff --git a/components/item-full.js b/components/item-full.js index 0aec76ce..2a78c8e8 100644 --- a/components/item-full.js +++ b/components/item-full.js @@ -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 }) { <> {me?.name === item.user.name && - - edit bio - } + } ) @@ -43,12 +46,12 @@ function ItemText ({ item }) { return {item.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
Failed to load!
- - if (!data) { - return ( -
- - - -
- -
-
- ) + if (error) { + return
Failed to load!
} - const { item } = data + // XXX replace item with cache version + if (data) { + ({ item } = data) + } return ( <> {item.parentId - ? + ? : (bio - ? - : + ? + : )} -
- -
+ {item.comments && +
+ +
} ) } diff --git a/components/user-header.js b/components/user-header.js index 2b086c66..a76cd35c 100644 --- a/components/user-header.js +++ b/components/user-header.js @@ -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) }} > - @ - name='name' - autoFocus - groupClassName={`mb-0 ${styles.username}`} - showValid - /> - setEditting(true)}>save +
+ @ + name='name' + autoFocus + groupClassName={`mb-0 ${styles.username}`} + showValid + /> + setEditting(true)}>save +
) : ( diff --git a/fragments/users.js b/fragments/users.js new file mode 100644 index 00000000..e53c29da --- /dev/null +++ b/fragments/users.js @@ -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 + } + }` diff --git a/pages/[username].js b/pages/[username].js index df8dd71d..0ab532d4 100644 --- a/pages/[username].js +++ b/pages/[username].js @@ -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 () {
{ - const { error } = await createBio({ variables: values }) + const { error } = await upsertBio({ variables: values }) if (error) { throw new Error({ message: error.toString() }) } + handleSuccess && handleSuccess() }} > - create + {bio?.text ? 'save' : 'create'}
@@ -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
Failed to load!
+ } + + // XXX replace item with cache version + if (data) { + ({ user } = data) + } + + const mine = me?.name === user.name + return ( {user.bio - ? - : (me?.name === user.name && + ? (edit + ? ( +
+ setEdit(false)} /> +
) + : + ) + : (mine &&
{create - ? + ? setCreate(false)} /> : ( - session?.user?.name === user.name && - + mine && + )}
)}