notifications done
This commit is contained in:
parent
f7b4618b4a
commit
01922e4b88
|
@ -37,18 +37,21 @@ function nextCursorEncoded (cursor) {
|
|||
|
||||
export default {
|
||||
Query: {
|
||||
moreItems: async (parent, { sort, cursor, userId }, { models }) => {
|
||||
moreItems: async (parent, { sort, cursor, userId }, { me, models }) => {
|
||||
const decodedCursor = decodeCursor(cursor)
|
||||
const items = userId
|
||||
? await models.$queryRaw(`
|
||||
${SELECT}
|
||||
FROM "Item"
|
||||
WHERE "userId" = $1 AND "parentId" IS NULL AND created_at <= $2
|
||||
ORDER BY created_at DESC
|
||||
OFFSET $3
|
||||
LIMIT ${LIMIT}`, Number(userId), decodedCursor.time, decodedCursor.offset)
|
||||
: sort === 'hot'
|
||||
? await models.$queryRaw(`
|
||||
let items
|
||||
switch (sort) {
|
||||
case 'user':
|
||||
items = await models.$queryRaw(`
|
||||
${SELECT}
|
||||
FROM "Item"
|
||||
WHERE "userId" = $1 AND "parentId" IS NULL AND created_at <= $2
|
||||
ORDER BY created_at DESC
|
||||
OFFSET $3
|
||||
LIMIT ${LIMIT}`, Number(userId), decodedCursor.time, decodedCursor.offset)
|
||||
break
|
||||
case 'hot':
|
||||
items = await models.$queryRaw(`
|
||||
${SELECT}
|
||||
FROM "Item"
|
||||
${timedLeftJoinSats(1)}
|
||||
|
@ -56,18 +59,66 @@ export default {
|
|||
${timedOrderBySats(1)}
|
||||
OFFSET $2
|
||||
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
||||
: await models.$queryRaw(`
|
||||
break
|
||||
default:
|
||||
items = await models.$queryRaw(`
|
||||
${SELECT}
|
||||
FROM "Item"
|
||||
WHERE "parentId" IS NULL AND created_at <= $1
|
||||
ORDER BY created_at DESC
|
||||
OFFSET $2
|
||||
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
||||
break
|
||||
}
|
||||
return {
|
||||
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
|
||||
items
|
||||
}
|
||||
},
|
||||
moreFlatComments: async (parent, { cursor, userId }, { me, models }) => {
|
||||
const decodedCursor = decodeCursor(cursor)
|
||||
let comments
|
||||
if (userId) {
|
||||
comments = await models.$queryRaw(`
|
||||
${SELECT}
|
||||
FROM "Item"
|
||||
WHERE "userId" = $1 AND "parentId" IS NOT NULL
|
||||
AND created_at <= $2
|
||||
ORDER BY created_at DESC
|
||||
OFFSET $3
|
||||
LIMIT ${LIMIT}`, Number(userId), decodedCursor.time, decodedCursor.offset)
|
||||
} else {
|
||||
if (!me) {
|
||||
throw new AuthenticationError('you must be logged in')
|
||||
}
|
||||
const user = await models.user.findUnique({ where: { name: me.name } })
|
||||
comments = await models.$queryRaw(`
|
||||
${SELECT}
|
||||
From "Item"
|
||||
JOIN "Item" p ON "Item"."parentId" = p.id AND p."userId" = $1
|
||||
AND "Item"."userId" <> $1 AND "Item".created_at <= $2
|
||||
ORDER BY "Item".created_at DESC
|
||||
OFFSET $3
|
||||
LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
|
||||
}
|
||||
return {
|
||||
cursor: comments.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
|
||||
comments
|
||||
}
|
||||
},
|
||||
notifications: async (parent, args, { me, models }) => {
|
||||
if (!me) {
|
||||
throw new AuthenticationError('you must be logged in')
|
||||
}
|
||||
const user = await models.user.findUnique({ where: { name: me.name } })
|
||||
|
||||
return await models.$queryRaw(`
|
||||
${SELECT}
|
||||
From "Item"
|
||||
JOIN "Item" p ON "Item"."parentId" = p.id AND p."userId" = $1
|
||||
AND "Item"."userId" <> $1
|
||||
ORDER BY "Item".created_at DESC`, user.id)
|
||||
},
|
||||
item: async (parent, { id }, { models }) => {
|
||||
const [item] = await models.$queryRaw(`
|
||||
${SELECT}
|
||||
|
@ -104,8 +155,6 @@ export default {
|
|||
throw new UserInputError('link must have url', { argumentName: 'url' })
|
||||
}
|
||||
|
||||
console.log(ensureProtocol(url))
|
||||
|
||||
return await createItem(parent, { title, url: ensureProtocol(url) }, { me, models })
|
||||
},
|
||||
createDiscussion: async (parent, { title, text }, { me, models }) => {
|
||||
|
|
|
@ -15,6 +15,25 @@ export default {
|
|||
}
|
||||
|
||||
return me.name === name || !(await models.user.findUnique({ where: { name } }))
|
||||
},
|
||||
recentlyStacked: async (parent, args, { models, me }) => {
|
||||
if (!me) {
|
||||
throw new AuthenticationError('you must be logged in')
|
||||
}
|
||||
|
||||
const user = await models.user.findUnique({ where: { name: me.name } })
|
||||
|
||||
const [{ sum }] = await models.$queryRaw(`
|
||||
SELECT sum("Vote".sats)
|
||||
FROM "Item"
|
||||
LEFT JOIN "Vote" on "Vote"."itemId" = "Item".id
|
||||
AND "Vote"."userId" <> $1
|
||||
AND ("Vote".created_at > $2 OR $2 IS NULL)
|
||||
AND "Vote".boost = false
|
||||
WHERE "Item"."userId" = $1`, user.id, user.checkedNotesAt)
|
||||
|
||||
await models.user.update({ where: { name: me.name }, data: { checkedNotesAt: new Date() } })
|
||||
return sum || 0
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -46,12 +65,36 @@ export default {
|
|||
const [{ sum }] = await models.$queryRaw`
|
||||
SELECT sum("Vote".sats)
|
||||
FROM "Item"
|
||||
LEFT JOIN "Vote" on "Vote"."itemId" = "Item".id AND "Vote"."userId" <> ${user.id}
|
||||
LEFT JOIN "Vote" on "Vote"."itemId" = "Item".id AND "Vote"."userId" <> ${user.id} AND boost = false
|
||||
WHERE "Item"."userId" = ${user.id}`
|
||||
return sum || 0
|
||||
},
|
||||
sats: async (user, args, { models }) => {
|
||||
return Math.floor(user.msats / 1000)
|
||||
},
|
||||
hasNewNotes: async (user, args, { models }) => {
|
||||
// check if any votes have been cast for them since checkedNotesAt
|
||||
const votes = await models.$queryRaw(`
|
||||
SELECT "Vote".id, "Vote".created_at
|
||||
FROM "Vote"
|
||||
LEFT JOIN "Item" on "Vote"."itemId" = "Item".id
|
||||
AND "Vote"."userId" <> $1
|
||||
AND ("Vote".created_at > $2 OR $2 IS NULL)
|
||||
AND "Vote".boost = false
|
||||
WHERE "Item"."userId" = $1
|
||||
LIMIT 1`, user.id, user.checkedNotesAt)
|
||||
if (votes.length > 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if they have any replies since checkedNotesAt
|
||||
const newReplies = await models.$queryRaw(`
|
||||
SELECT "Item".id, "Item".created_at
|
||||
From "Item"
|
||||
JOIN "Item" p ON "Item"."parentId" = p.id AND p."userId" = $1
|
||||
AND ("Item".created_at > $2 OR $2 IS NULL) AND "Item"."userId" <> $1
|
||||
LIMIT 1`, user.id, user.checkedNotesAt)
|
||||
return !!newReplies.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ import { gql } from 'apollo-server-micro'
|
|||
export default gql`
|
||||
extend type Query {
|
||||
moreItems(sort: String!, cursor: String, userId: ID): Items
|
||||
moreFlatComments(cursor: String, userId: ID): Comments
|
||||
notifications: [Item!]!
|
||||
item(id: ID!): Item
|
||||
userComments(userId: ID!): [Item!]
|
||||
root(id: ID!): Item
|
||||
|
@ -20,6 +22,11 @@ export default gql`
|
|||
items: [Item!]!
|
||||
}
|
||||
|
||||
type Comments {
|
||||
cursor: String
|
||||
comments: [Item!]!
|
||||
}
|
||||
|
||||
type Item {
|
||||
id: ID!
|
||||
createdAt: String!
|
||||
|
|
|
@ -6,6 +6,7 @@ export default gql`
|
|||
user(name: String!): User
|
||||
users: [User!]
|
||||
nameAvailable(name: String!): Boolean!
|
||||
recentlyStacked: Int!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
|
@ -20,6 +21,7 @@ export default gql`
|
|||
stacked: Int!
|
||||
freePosts: Int!
|
||||
freeComments: Int!
|
||||
hasNewNotes: Boolean!
|
||||
sats: Int!
|
||||
msats: Int!
|
||||
}
|
||||
|
|
|
@ -3,12 +3,13 @@ import styles from './comment.module.css'
|
|||
import Text from './text'
|
||||
import Link from 'next/link'
|
||||
import Reply from './reply'
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { timeSince } from '../lib/time'
|
||||
import UpVote from './upvote'
|
||||
import Eye from '../svgs/eye-fill.svg'
|
||||
import EyeClose from '../svgs/eye-close-line.svg'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
function Parent ({ item }) {
|
||||
const { data } = useQuery(
|
||||
|
@ -24,7 +25,7 @@ function Parent ({ item }) {
|
|||
<>
|
||||
<span> \ </span>
|
||||
<Link href={`/items/${item.parentId}`} passHref>
|
||||
<a className='text-reset'>parent</a>
|
||||
<a onClick={e => e.stopPropagation()} className='text-reset'>parent</a>
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
|
@ -38,18 +39,33 @@ function Parent ({ item }) {
|
|||
{data.root.id !== item.parentId && <ParentFrag />}
|
||||
<span> \ </span>
|
||||
<Link href={`/items/${data.root.id}`} passHref>
|
||||
<a className='text-reset'>{data.root.title}</a>
|
||||
<a onClick={e => e.stopPropagation()} className='text-reset'>root: {data.root.title}</a>
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Comment ({ item, children, replyOpen, includeParent, cacheId, noComments, noReply }) {
|
||||
export default function Comment ({ item, children, replyOpen, includeParent, cacheId, noComments, noReply, clickToContext }) {
|
||||
const [reply, setReply] = useState(replyOpen)
|
||||
const [collapse, setCollapse] = useState(false)
|
||||
const ref = useRef(null)
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (Number(router.query.commentId) === Number(item.id)) {
|
||||
ref.current.scrollIntoView()
|
||||
// ref.current.classList.add('flash-it')
|
||||
}
|
||||
}, [item])
|
||||
|
||||
return (
|
||||
<div className={includeParent ? '' : `${styles.comment} ${collapse ? styles.collapsed : ''}`}>
|
||||
<div
|
||||
ref={ref} onClick={() => {
|
||||
if (clickToContext) {
|
||||
router.push(`/items/${item.parentId}?commentId=${item.id}`, `/items/${item.parentId}`)
|
||||
}
|
||||
}} className={includeParent ? `${clickToContext ? styles.clickToContext : ''}` : `${styles.comment} ${collapse ? styles.collapsed : ''}`}
|
||||
>
|
||||
<div className={`${itemStyles.item} ${styles.item}`}>
|
||||
<UpVote itemId={item.id} meSats={item.meSats} className={styles.upvote} />
|
||||
<div className={`${itemStyles.hunk} ${styles.hunk}`}>
|
||||
|
@ -60,11 +76,11 @@ export default function Comment ({ item, children, replyOpen, includeParent, cac
|
|||
<span>{item.boost} boost</span>
|
||||
<span> \ </span>
|
||||
<Link href={`/items/${item.id}`} passHref>
|
||||
<a className='text-reset'>{item.ncomments} replies</a>
|
||||
<a onClick={e => e.stopPropagation()} className='text-reset'>{item.ncomments} replies</a>
|
||||
</Link>
|
||||
<span> \ </span>
|
||||
<Link href={`/${item.user.name}`} passHref>
|
||||
<a>@{item.user.name}</a>
|
||||
<a onClick={e => e.stopPropagation()}>@{item.user.name}</a>
|
||||
</Link>
|
||||
<span> </span>
|
||||
<span>{timeSince(new Date(item.createdAt))}</span>
|
||||
|
|
|
@ -80,6 +80,15 @@
|
|||
padding-left: .2rem;
|
||||
}
|
||||
|
||||
.clickToContext {
|
||||
border-radius: .4rem;
|
||||
padding: .2rem 0;
|
||||
}
|
||||
|
||||
.clickToContext:hover {
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.comment:not(:last-child) {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
@ -89,4 +98,9 @@
|
|||
padding-top: .25rem;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.clickToContext {
|
||||
scroll-behavior: smooth;
|
||||
cursor: pointer;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import { useQuery } from '@apollo/client'
|
||||
import Button from 'react-bootstrap/Button'
|
||||
import { MORE_FLAT_COMMENTS } from '../fragments/comments'
|
||||
import { useState } from 'react'
|
||||
import Comment, { CommentSkeleton } from './comment'
|
||||
|
||||
export default function CommentsFlat ({ variables, ...props }) {
|
||||
const { loading, error, data, fetchMore } = useQuery(MORE_FLAT_COMMENTS, {
|
||||
variables
|
||||
})
|
||||
if (error) return <div>Failed to load!</div>
|
||||
if (loading) {
|
||||
return <CommentsFlatSkeleton />
|
||||
}
|
||||
|
||||
const { moreFlatComments: { comments, cursor } } = data
|
||||
return (
|
||||
<>
|
||||
{comments.map(item => (
|
||||
<Comment key={item.id} item={item} {...props} />
|
||||
))}
|
||||
<MoreFooter cursor={cursor} fetchMore={fetchMore} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function CommentsFlatSkeleton () {
|
||||
const comments = new Array(21).fill(null)
|
||||
|
||||
return (
|
||||
<div>{comments.map((_, i) => (
|
||||
<CommentSkeleton key={i} skeletonChildren={0} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function MoreFooter ({ cursor, fetchMore }) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
if (loading) {
|
||||
return <div><CommentsFlatSkeleton /></div>
|
||||
}
|
||||
|
||||
let Footer
|
||||
if (cursor) {
|
||||
Footer = () => (
|
||||
<Button
|
||||
variant='primary'
|
||||
size='md'
|
||||
onClick={async () => {
|
||||
setLoading(true)
|
||||
await fetchMore({
|
||||
variables: {
|
||||
cursor
|
||||
}
|
||||
})
|
||||
setLoading(false)
|
||||
}}
|
||||
>more
|
||||
</Button>
|
||||
)
|
||||
} else {
|
||||
Footer = () => (
|
||||
<div className='text-muted' style={{ fontFamily: 'lightning', fontSize: '2rem', opacity: '0.6' }}>GENISIS</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <div className='d-flex justify-content-center mt-4 mb-2'><Footer /></div>
|
||||
}
|
|
@ -1,7 +1,16 @@
|
|||
import { useQuery } from '@apollo/client'
|
||||
import { useEffect } from 'react'
|
||||
import Comment, { CommentSkeleton } from './comment'
|
||||
|
||||
export default function Comments ({ comments, ...props }) {
|
||||
useEffect(() => {
|
||||
// Your code here
|
||||
const hash = window.location.hash
|
||||
if (hash) {
|
||||
document.querySelector(hash).scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}, [])
|
||||
|
||||
return comments.map(item => (
|
||||
<Comment key={item.id} item={item} {...props} />
|
||||
))
|
||||
|
|
|
@ -7,11 +7,9 @@ import { useRouter } from 'next/router'
|
|||
import { Button, Container, NavDropdown } from 'react-bootstrap'
|
||||
import Price from './price'
|
||||
import { useMe } from './me'
|
||||
import { useApolloClient } from '@apollo/client'
|
||||
|
||||
function WalletSummary () {
|
||||
const me = useMe()
|
||||
if (!me) return null
|
||||
|
||||
function WalletSummary ({ me }) {
|
||||
return `[${me.stacked},${me.sats}]`
|
||||
}
|
||||
|
||||
|
@ -19,6 +17,8 @@ export default function Header () {
|
|||
const [session, loading] = useSession()
|
||||
const router = useRouter()
|
||||
const path = router.asPath.split('?')[0]
|
||||
const me = useMe()
|
||||
const client = useApolloClient()
|
||||
|
||||
const Corner = () => {
|
||||
if (loading) {
|
||||
|
@ -28,35 +28,56 @@ export default function Header () {
|
|||
if (session) {
|
||||
return (
|
||||
<div className='d-flex align-items-center'>
|
||||
<NavDropdown className='pl-0' title={`@${session.user.name}`} alignRight>
|
||||
<Link href={'/' + session.user.name} passHref>
|
||||
<NavDropdown.Item>profile</NavDropdown.Item>
|
||||
</Link>
|
||||
<Link href='/wallet' passHref>
|
||||
<NavDropdown.Item>wallet</NavDropdown.Item>
|
||||
</Link>
|
||||
<div>
|
||||
<NavDropdown.Divider />
|
||||
<Link href='/recent' passHref>
|
||||
<NavDropdown.Item>recent</NavDropdown.Item>
|
||||
<div className='position-relative'>
|
||||
<NavDropdown className='pl-0' title={`@${session.user.name}`} alignRight>
|
||||
<Link href={'/' + session.user.name} passHref>
|
||||
<NavDropdown.Item>profile</NavDropdown.Item>
|
||||
</Link>
|
||||
{session
|
||||
? (
|
||||
<Link href='/post' passHref>
|
||||
<NavDropdown.Item>post</NavDropdown.Item>
|
||||
</Link>
|
||||
)
|
||||
: <NavDropdown.Item onClick={signIn}>post</NavDropdown.Item>}
|
||||
<NavDropdown.Item href='https://bitcoinerjobs.co' target='_blank'>jobs</NavDropdown.Item>
|
||||
</div>
|
||||
<NavDropdown.Divider />
|
||||
<NavDropdown.Item onClick={signOut}>logout</NavDropdown.Item>
|
||||
</NavDropdown>
|
||||
<Nav.Item>
|
||||
<Link href='/wallet' passHref>
|
||||
<Nav.Link className='text-success px-0'><WalletSummary /></Nav.Link>
|
||||
</Link>
|
||||
</Nav.Item>
|
||||
<Link href='/notifications' passHref>
|
||||
<NavDropdown.Item onClick={() => {
|
||||
// when it's a fresh click evict old notification cache
|
||||
client.cache.evict({ id: 'ROOT_QUERY', fieldName: 'moreFlatComments:{}' })
|
||||
client.cache.evict({ id: 'ROOT_QUERY', fieldName: 'recentlyStacked' })
|
||||
}}
|
||||
>
|
||||
notifications
|
||||
{me && me.hasNewNotes &&
|
||||
<div className='p-1 d-inline-block bg-danger rounded-circle ml-1'>
|
||||
<span className='invisible'>{' '}</span>
|
||||
</div>}
|
||||
</NavDropdown.Item>
|
||||
</Link>
|
||||
<Link href='/wallet' passHref>
|
||||
<NavDropdown.Item>wallet</NavDropdown.Item>
|
||||
</Link>
|
||||
<div>
|
||||
<NavDropdown.Divider />
|
||||
<Link href='/recent' passHref>
|
||||
<NavDropdown.Item>recent</NavDropdown.Item>
|
||||
</Link>
|
||||
{session
|
||||
? (
|
||||
<Link href='/post' passHref>
|
||||
<NavDropdown.Item>post</NavDropdown.Item>
|
||||
</Link>
|
||||
)
|
||||
: <NavDropdown.Item onClick={signIn}>post</NavDropdown.Item>}
|
||||
<NavDropdown.Item href='https://bitcoinerjobs.co' target='_blank'>jobs</NavDropdown.Item>
|
||||
</div>
|
||||
<NavDropdown.Divider />
|
||||
<NavDropdown.Item onClick={signOut}>logout</NavDropdown.Item>
|
||||
</NavDropdown>
|
||||
{me && me.hasNewNotes &&
|
||||
<span className='position-absolute p-1 bg-danger rounded-circle' style={{ top: '5px', right: '0px' }}>
|
||||
<span className='invisible'>{' '}</span>
|
||||
</span>}
|
||||
</div>
|
||||
{me &&
|
||||
<Nav.Item>
|
||||
<Link href='/wallet' passHref>
|
||||
<Nav.Link className='text-success px-0'><WalletSummary me={me} /></Nav.Link>
|
||||
</Link>
|
||||
</Nav.Item>}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
|
|
|
@ -50,8 +50,8 @@ function MoreFooter ({ cursor, fetchMore, offset }) {
|
|||
if (cursor) {
|
||||
Footer = () => (
|
||||
<Button
|
||||
variant='secondary'
|
||||
size='sm'
|
||||
variant='primary'
|
||||
size='md'
|
||||
onClick={async () => {
|
||||
setLoading(true)
|
||||
await fetchMore({
|
||||
|
@ -61,7 +61,7 @@ function MoreFooter ({ cursor, fetchMore, offset }) {
|
|||
})
|
||||
setLoading(false)
|
||||
}}
|
||||
>Load more
|
||||
>more
|
||||
</Button>
|
||||
)
|
||||
} else {
|
||||
|
|
|
@ -14,6 +14,7 @@ export function MeProvider ({ children }) {
|
|||
stacked
|
||||
freePosts
|
||||
freeComments
|
||||
hasNewNotes
|
||||
}
|
||||
}`
|
||||
const { data } = useQuery(query, { pollInterval: 1000 })
|
||||
|
|
|
@ -45,7 +45,9 @@ export default function UpVote ({ itemId, meSats, className }) {
|
|||
}
|
||||
onClick={
|
||||
session
|
||||
? async () => {
|
||||
? async (e) => {
|
||||
e.stopPropagation()
|
||||
strike()
|
||||
if (!itemId) return
|
||||
try {
|
||||
await vote({ variables: { id: itemId, sats: 1 } })
|
||||
|
@ -56,8 +58,6 @@ export default function UpVote ({ itemId, meSats, className }) {
|
|||
}
|
||||
throw new Error({ message: error.toString() })
|
||||
}
|
||||
|
||||
strike()
|
||||
}
|
||||
: signIn
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
}
|
||||
|
||||
.upvote.stimi {
|
||||
fill: #993DF5;
|
||||
filter: drop-shadow(0 0 7px #C28BF9);
|
||||
/* fill: #993DF5;
|
||||
filter: drop-shadow(0 0 7px #C28BF9); */
|
||||
fill: #F6911D;
|
||||
filter: drop-shadow(0 0 7px #F6911D);
|
||||
}
|
|
@ -16,6 +16,19 @@ export const COMMENT_FIELDS = gql`
|
|||
}
|
||||
`
|
||||
|
||||
export const MORE_FLAT_COMMENTS = gql`
|
||||
${COMMENT_FIELDS}
|
||||
|
||||
query MoreFlatComments($cursor: String, $userId: ID) {
|
||||
moreFlatComments(cursor: $cursor, userId: $userId) {
|
||||
cursor
|
||||
comments {
|
||||
...CommentFields
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const COMMENTS = gql`
|
||||
${COMMENT_FIELDS}
|
||||
|
||||
|
|
|
@ -656,9 +656,9 @@
|
|||
}
|
||||
},
|
||||
"@prisma/engines": {
|
||||
"version": "2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b.tgz",
|
||||
"integrity": "sha512-Tgk3kggO5B9IT6mimJAw6HSxbFoDAuDKL3sHHSS41EnQm76j/nf4uhGZFPzOQwZWOLeT5ZLO2khr4/FCA9Nkhw=="
|
||||
"version": "2.25.0-36.c838e79f39885bc8e1611849b1eb28b5bb5bc922",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-2.25.0-36.c838e79f39885bc8e1611849b1eb28b5bb5bc922.tgz",
|
||||
"integrity": "sha512-vjLCk8AFRZu3D8h/SMcWDzTo0xkMuUDyXQzXekn8gzAGjb47B6LQXGR6rDoZ3/uPM13JNTLPvF62mtVaY6fVeQ=="
|
||||
},
|
||||
"@prisma/engines-version": {
|
||||
"version": "2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b",
|
||||
|
@ -5285,11 +5285,11 @@
|
|||
"integrity": "sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U="
|
||||
},
|
||||
"prisma": {
|
||||
"version": "2.23.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-2.23.0.tgz",
|
||||
"integrity": "sha512-3c/lmDy8nsPcEsfCufvCTJUEuwmAcTPbeGg9fL1qjlvS314duLUA/k2nm3n1rq4ImKqzeC5uaKfvI2IoAfwrJA==",
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-2.25.0.tgz",
|
||||
"integrity": "sha512-AdAlP+PShvugljIx62Omu+eLKu6Cozz06dehmClIHSb0/yFiVnyBtrRVV4LZus+QX6Ayg7CTDvtzroACAWl+Zw==",
|
||||
"requires": {
|
||||
"@prisma/engines": "2.23.0-36.adf5e8cba3daf12d456d911d72b6e9418681b28b"
|
||||
"@prisma/engines": "2.25.0-36.c838e79f39885bc8e1611849b1eb28b5bb5bc922"
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"next": "10.0.9",
|
||||
"next-auth": "^3.13.3",
|
||||
"next-seo": "^4.24.0",
|
||||
"prisma": "^2.23.0",
|
||||
"prisma": "^2.25.0",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"react": "17.0.1",
|
||||
"react-bootstrap": "^1.5.2",
|
||||
|
@ -52,4 +52,4 @@
|
|||
"eslint-plugin-compat": "^3.9.0",
|
||||
"standard": "^16.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ export default function User ({ user }) {
|
|||
return (
|
||||
<Layout>
|
||||
<UserHeader user={user} />
|
||||
<Items variables={{ sort: 'recent', userId: user.id }} />
|
||||
<Items variables={{ sort: 'user', userId: user.id }} />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import Layout from '../../components/layout'
|
||||
import { CommentsQuery } from '../../components/comments'
|
||||
import { COMMENT_FIELDS } from '../../fragments/comments'
|
||||
import { gql } from '@apollo/client'
|
||||
import ApolloClient from '../../api/client'
|
||||
import UserHeader from '../../components/user-header'
|
||||
import CommentsFlat from '../../components/comments-flat'
|
||||
|
||||
export async function getServerSideProps ({ req, params }) {
|
||||
const { error, data: { user } } = await (await ApolloClient(req)).query({
|
||||
|
@ -34,19 +33,11 @@ export async function getServerSideProps ({ req, params }) {
|
|||
}
|
||||
}
|
||||
|
||||
export default function User ({ user }) {
|
||||
const query = gql`
|
||||
${COMMENT_FIELDS}
|
||||
{
|
||||
comments: userComments(userId: ${user.id}) {
|
||||
...CommentFields
|
||||
}
|
||||
}
|
||||
`
|
||||
export default function UserComments ({ user }) {
|
||||
return (
|
||||
<Layout>
|
||||
<UserHeader user={user} />
|
||||
<CommentsQuery query={query} includeParent noReply />
|
||||
<CommentsFlat variables={{ userId: user.id }} includeParent noReply clickToContext />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -28,6 +28,25 @@ const client = new ApolloClient({
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
moreFlatComments: {
|
||||
keyArgs: ['userId'],
|
||||
merge (existing, incoming, { readField }) {
|
||||
const comments = existing ? existing.comments : []
|
||||
return {
|
||||
cursor: incoming.cursor,
|
||||
comments: [...comments, ...incoming.comments]
|
||||
}
|
||||
},
|
||||
|
||||
read (existing) {
|
||||
if (existing) {
|
||||
return {
|
||||
cursor: existing.cursor,
|
||||
comments: existing.comments
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { gql, useQuery } from '@apollo/client'
|
||||
import CommentsFlat from '../components/comments-flat'
|
||||
import Layout from '../components/layout'
|
||||
|
||||
export function RecentlyStacked () {
|
||||
const query = gql`
|
||||
{
|
||||
recentlyStacked
|
||||
}`
|
||||
const { data } = useQuery(query)
|
||||
if (!data || !data.recentlyStacked) return null
|
||||
|
||||
return (
|
||||
<h2 className='visible text-success text-center py-3'>
|
||||
you stacked <span className='text-monospace'>{data.recentlyStacked}</span> sats
|
||||
</h2>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Notifications ({ user }) {
|
||||
return (
|
||||
<Layout>
|
||||
<RecentlyStacked />
|
||||
<h6 className='text-muted'>replies</h6>
|
||||
<CommentsFlat noReply includeParent clickToContext />
|
||||
</Layout>
|
||||
)
|
||||
}
|
|
@ -123,7 +123,6 @@ export function WithdrawlForm () {
|
|||
initialError={error ? error.toString() : undefined}
|
||||
schema={WithdrawlSchema}
|
||||
onSubmit={async ({ invoice, maxFee }) => {
|
||||
console.log('calling')
|
||||
const { data } = await createWithdrawl({ variables: { invoice, maxFee: Number(maxFee) } })
|
||||
router.push(`/withdrawls/${data.createWithdrawl.id}`)
|
||||
}}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "checkedNotesAt" TIMESTAMP(3);
|
|
@ -11,21 +11,22 @@ generator client {
|
|||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map(name: "updated_at")
|
||||
name String? @unique
|
||||
email String? @unique
|
||||
emailVerified DateTime? @map(name: "email_verified")
|
||||
image String?
|
||||
items Item[]
|
||||
messages Message[]
|
||||
votes Vote[]
|
||||
invoices Invoice[]
|
||||
withdrawls Withdrawl[]
|
||||
msats Int @default(0)
|
||||
freeComments Int @default(5)
|
||||
freePosts Int @default(2)
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map(name: "updated_at")
|
||||
name String? @unique
|
||||
email String? @unique
|
||||
emailVerified DateTime? @map(name: "email_verified")
|
||||
image String?
|
||||
items Item[]
|
||||
messages Message[]
|
||||
votes Vote[]
|
||||
invoices Invoice[]
|
||||
withdrawls Withdrawl[]
|
||||
msats Int @default(0)
|
||||
freeComments Int @default(5)
|
||||
freePosts Int @default(2)
|
||||
checkedNotesAt DateTime?
|
||||
|
||||
@@map(name: "users")
|
||||
}
|
||||
|
|
|
@ -160,6 +160,10 @@ footer {
|
|||
opacity: .2;
|
||||
}
|
||||
|
||||
.flash-it {
|
||||
animation: flash 2s linear 2;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(359deg); }
|
||||
|
|
Loading…
Reference in New Issue