improved top

This commit is contained in:
keyan 2022-10-25 16:35:32 -05:00
parent 30b1ee33aa
commit a398784f26
24 changed files with 353 additions and 255 deletions

View File

@ -50,8 +50,8 @@ export async function getItem (parent, { id }, { me, models }) {
function topClause (within) {
let interval = ' AND "Item".created_at >= $1 - INTERVAL '
switch (within) {
case 'day':
interval += "'1 day'"
case 'forever':
interval = ''
break
case 'week':
interval += "'7 days'"
@ -63,12 +63,23 @@ function topClause (within) {
interval += "'1 year'"
break
default:
interval = ''
interval += "'1 day'"
break
}
return interval
}
async function topOrderClause (sort, me, models) {
switch (sort) {
case 'comments':
return 'ORDER BY ncomments DESC'
case 'sats':
return 'ORDER BY sats DESC'
default:
return await topOrderByWeightedSats(me, models)
}
}
export async function orderByNumerator (me, models) {
if (me) {
const user = await models.user.findUnique({ where: { id: me.id } })
@ -124,6 +135,40 @@ export default {
return count
},
topItems: async (parent, { cursor, sort, when }, { me, models }) => {
const decodedCursor = decodeCursor(cursor)
const items = await models.$queryRaw(`
${SELECT}
FROM "Item"
WHERE "parentId" IS NULL AND "Item".created_at <= $1
AND "pinId" IS NULL
${topClause(when)}
${await filterClause(me, models)}
${await topOrderClause(sort, me, models)}
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
return {
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
items
}
},
topComments: async (parent, { cursor, sort, when }, { me, models }) => {
const decodedCursor = decodeCursor(cursor)
const comments = await models.$queryRaw(`
${SELECT}
FROM "Item"
WHERE "parentId" IS NOT NULL
AND "Item".created_at <= $1
${topClause(when)}
${await filterClause(me, models)}
${await topOrderClause(sort, me, models)}
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
return {
cursor: comments.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
comments
}
},
items: async (parent, { sub, sort, cursor, name, within }, { me, models }) => {
const decodedCursor = decodeCursor(cursor)
let items; let user; let pins; let subFull

View File

@ -6,8 +6,8 @@ import serialize from './serial'
export function topClause (within) {
let interval = ' AND "ItemAct".created_at >= $1 - INTERVAL '
switch (within) {
case 'day':
interval += "'1 day'"
case 'forever':
interval = ''
break
case 'week':
interval += "'7 days'"
@ -19,7 +19,7 @@ export function topClause (within) {
interval += "'1 year'"
break
default:
interval = ''
interval += "'1 day'"
break
}
return interval
@ -28,8 +28,8 @@ export function topClause (within) {
export function earnWithin (within) {
let interval = ' AND "Earn".created_at >= $1 - INTERVAL '
switch (within) {
case 'day':
interval += "'1 day'"
case 'forever':
interval = ''
break
case 'week':
interval += "'7 days'"
@ -41,8 +41,30 @@ export function earnWithin (within) {
interval += "'1 year'"
break
default:
interval += "'1 day'"
break
}
return interval
}
export function itemWithin (within) {
let interval = ' AND "Item".created_at >= $1 - INTERVAL '
switch (within) {
case 'forever':
interval = ''
break
case 'week':
interval += "'7 days'"
break
case 'month':
interval += "'1 month'"
break
case 'year':
interval += "'1 year'"
break
default:
interval += "'1 day'"
break
}
return interval
}
@ -94,37 +116,59 @@ export default {
return user.name?.toUpperCase() === name?.toUpperCase() || !(await models.user.findUnique({ where: { name } }))
},
topUsers: async (parent, { cursor, within, userType }, { models, me }) => {
topUsers: async (parent, { cursor, when, sort }, { models, me }) => {
const decodedCursor = decodeCursor(cursor)
let users
if (userType === 'spent') {
if (sort === 'spent') {
users = await models.$queryRaw(`
SELECT users.name, users.created_at, sum("ItemAct".sats) as amount
SELECT users.*, sum("ItemAct".sats) as spent
FROM "ItemAct"
JOIN users on "ItemAct"."userId" = users.id
WHERE "ItemAct".created_at <= $1
${topClause(within)}
${topClause(when)}
GROUP BY users.id, users.name
ORDER BY amount DESC NULLS LAST, users.created_at DESC
ORDER BY spent DESC NULLS LAST, users.created_at DESC
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
} else if (sort === 'posts') {
users = await models.$queryRaw(`
SELECT users.*, count(*) as nitems
FROM users
JOIN "Item" on "Item"."userId" = users.id
WHERE "Item".created_at <= $1 AND "Item"."parentId" IS NULL
${itemWithin(when)}
GROUP BY users.id
ORDER BY nitems DESC NULLS LAST, users.created_at DESC
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
} else if (sort === 'comments') {
users = await models.$queryRaw(`
SELECT users.*, count(*) as ncomments
FROM users
JOIN "Item" on "Item"."userId" = users.id
WHERE "Item".created_at <= $1 AND "Item"."parentId" IS NOT NULL
${itemWithin(when)}
GROUP BY users.id
ORDER BY ncomments DESC NULLS LAST, users.created_at DESC
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
} else {
users = await models.$queryRaw(`
SELECT name, created_at, sum(sats) as amount
SELECT u.id, u.name, u."photoId", sum(amount) as stacked
FROM
((SELECT users.name, users.created_at, "ItemAct".sats as sats
((SELECT users.*, "ItemAct".sats as amount
FROM "ItemAct"
JOIN "Item" on "ItemAct"."itemId" = "Item".id
JOIN users on "Item"."userId" = users.id
WHERE act <> 'BOOST' AND "ItemAct"."userId" <> users.id AND "ItemAct".created_at <= $1
${topClause(within)})
${topClause(when)})
UNION ALL
(SELECT users.name, users.created_at, "Earn".msats/1000 as sats
(SELECT users.*, "Earn".msats/1000 as amount
FROM "Earn"
JOIN users on users.id = "Earn"."userId"
WHERE "Earn".msats > 0 ${earnWithin(within)})) u
GROUP BY name, created_at
ORDER BY amount DESC NULLS LAST, created_at DESC
WHERE "Earn".msats > 0 ${earnWithin(when)})) u
GROUP BY u.id, u.name, u.created_at, u."photoId"
ORDER BY stacked DESC NULLS LAST, created_at DESC
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
}
@ -260,9 +304,15 @@ export default {
User: {
authMethods,
nitems: async (user, args, { models }) => {
if (user.nitems) {
return user.nitems
}
return await models.item.count({ where: { userId: user.id, parentId: null } })
},
ncomments: async (user, args, { models }) => {
if (user.ncomments) {
return user.ncomments
}
return await models.item.count({ where: { userId: user.id, parentId: { not: null } } })
},
stacked: async (user, args, { models }) => {
@ -273,6 +323,10 @@ export default {
return Math.floor((user.stackedMsats || 0) / 1000)
},
spent: async (user, args, { models }) => {
if (user.spent) {
return user.spent
}
const { sum: { sats } } = await models.itemAct.aggregate({
sum: {
sats: true

View File

@ -15,6 +15,8 @@ export default gql`
outlawedItems(cursor: String): Items
borderlandItems(cursor: String): Items
freebieItems(cursor: String): Items
topItems(cursor: String, sort: String, when: String): Items
topComments(cursor: String, sort: String, when: String): Comments
}
type ItemActResult {

View File

@ -7,7 +7,7 @@ export default gql`
user(name: String!): User
users: [User!]
nameAvailable(name: String!): Boolean!
topUsers(cursor: String, within: String!, userType: String!): TopUsers
topUsers(cursor: String, when: String, sort: String): Users
searchUsers(q: String!, limit: Int, similarity: Float): [User!]!
}
@ -16,17 +16,6 @@ export default gql`
users: [User!]!
}
type TopUsers {
cursor: String
users: [TopUser!]!
}
type TopUser {
name: String!
createdAt: String!
amount: Int!
}
extend type Mutation {
setName(name: String!): Boolean
setSettings(tipDefault: Int!, fiatCurrency: String!, noteItemSats: Boolean!, noteEarning: Boolean!,

View File

@ -17,6 +17,7 @@ import { useMe } from './me'
import DontLikeThis from './dont-link-this'
import Flag from '../svgs/flag-fill.svg'
import { Badge } from 'react-bootstrap'
import { abbrNum } from '../lib/format'
function Parent ({ item, rootText }) {
const ParentFrag = () => (
@ -114,11 +115,11 @@ export default function Comment ({
<div className={`${itemStyles.hunk} ${styles.hunk}`}>
<div className='d-flex align-items-center'>
<div className={`${itemStyles.other} ${styles.other}`}>
<span title={`from ${item.upvotes} users ${item.mine ? `\\ ${item.meSats} sats to post` : `(${item.meSats} sats from me)`}`}>{item.sats} sats</span>
<span title={`from ${item.upvotes} users ${item.mine ? `\\ ${item.meSats} sats to post` : `(${item.meSats} sats from me)`}`}>{abbrNum(item.sats)} sats</span>
<span> \ </span>
{item.boost > 0 &&
<>
<span>{item.boost} boost</span>
<span>{abbrNum(item.boost)} boost</span>
<span> \ </span>
</>}
<Link href={`/items/${item.id}`} passHref>

View File

@ -3,8 +3,8 @@ import { MORE_FLAT_COMMENTS } from '../fragments/comments'
import { CommentFlat, CommentSkeleton } from './comment'
import MoreFooter from './more-footer'
export default function CommentsFlat ({ variables, comments, cursor, ...props }) {
const { data, fetchMore } = useQuery(MORE_FLAT_COMMENTS, {
export default function CommentsFlat ({ variables, query, destructureData, comments, cursor, ...props }) {
const { data, fetchMore } = useQuery(query || MORE_FLAT_COMMENTS, {
variables
})
@ -13,7 +13,11 @@ export default function CommentsFlat ({ variables, comments, cursor, ...props })
}
if (data) {
({ moreFlatComments: { comments, cursor } } = data)
if (destructureData) {
({ comments, cursor } = destructureData(data))
} else {
({ moreFlatComments: { comments, cursor } } = data)
}
}
return (

View File

@ -5,6 +5,7 @@ import styles from './header.module.css'
import { Nav, Navbar } from 'react-bootstrap'
import { COMMENTS_QUERY } from '../fragments/items'
import { COMMENTS } from '../fragments/comments'
import { abbrNum } from '../lib/format'
export function CommentsHeader ({ handleSort, commentSats }) {
const [sort, setSort] = useState('hot')
@ -23,7 +24,7 @@ export function CommentsHeader ({ handleSort, commentSats }) {
activeKey={sort}
>
<Nav.Item className='text-muted'>
{commentSats} sats
{abbrNum(commentSats)} sats
</Nav.Item>
<div className='ml-auto d-flex'>
<Nav.Item>

View File

@ -11,7 +11,7 @@ import { signOut, signIn } from 'next-auth/client'
import { useLightning } from './lightning'
import { useEffect, useState } from 'react'
import { randInRange } from '../lib/rand'
import { formatSats } from '../lib/format'
import { abbrNum } from '../lib/format'
import NoteIcon from '../svgs/notification-4-fill.svg'
import { useQuery, gql } from '@apollo/client'
import LightningIcon from '../svgs/bolt.svg'
@ -19,7 +19,7 @@ import LightningIcon from '../svgs/bolt.svg'
function WalletSummary ({ me }) {
if (!me) return null
return `${formatSats(me.sats)}`
return `${abbrNum(me.sats)}`
}
export default function Header ({ sub }) {
@ -154,7 +154,7 @@ export default function Header ({ sub }) {
</Nav.Item>
{!prefix &&
<Nav.Item className={className}>
<Link href='/top/posts/week' passHref>
<Link href='/top/posts' passHref>
<Nav.Link eventKey='top' className={styles.navLink}>top</Nav.Link>
</Link>
</Nav.Item>}
@ -230,7 +230,7 @@ const NavItemsStatic = ({ className }) => {
</Link>
</Nav.Item>
<Nav.Item className={className}>
<Link href='/top/posts/week' passHref>
<Link href='/top/posts' passHref>
<Nav.Link className={styles.navLink}>top</Nav.Link>
</Link>
</Nav.Item>

View File

@ -14,6 +14,7 @@ import { newComments } from '../lib/new-comments'
import { useMe } from './me'
import DontLikeThis from './dont-link-this'
import Flag from '../svgs/flag-fill.svg'
import { abbrNum } from '../lib/format'
export function SearchTitle ({ title }) {
return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => {
@ -87,12 +88,12 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) {
<div className={`${styles.other}`}>
{!item.position &&
<>
<span title={`from ${item.upvotes} users ${item.mine ? `\\ ${item.meSats} sats to post` : `(${item.meSats} sats from me)`} `}>{item.sats} sats</span>
<span title={`from ${item.upvotes} users ${item.mine ? `\\ ${item.meSats} sats to post` : `(${item.meSats} sats from me)`} `}>{abbrNum(item.sats)} sats</span>
<span> \ </span>
</>}
{item.boost > 0 &&
<>
<span>{item.boost} boost</span>
<span>{abbrNum(item.boost)} boost</span>
<span> \ </span>
</>}
<Link href={`/items/${item.id}`} passHref>

View File

@ -127,6 +127,14 @@ a.link:visited {
margin: 0;
}
.skeleton .name {
background-color: var(--theme-grey);
width: 100px;
border-radius: .4rem;
height: 17px;
margin: 0;
}
.skeleton .link {
height: 14px;
background-color: var(--theme-grey);

View File

@ -7,15 +7,19 @@ import MoreFooter from './more-footer'
import React from 'react'
import Comment from './comment'
export default function Items ({ variables = {}, rank, items, pins, cursor }) {
const { data, fetchMore } = useQuery(ITEMS, { variables })
export default function Items ({ variables = {}, query, destructureData, rank, items, pins, cursor }) {
const { data, fetchMore } = useQuery(query || ITEMS, { variables })
if (!data && !items) {
return <ItemsSkeleton rank={rank} />
}
if (data) {
({ items: { items, pins, cursor } } = data)
if (destructureData) {
({ items, pins, cursor } = destructureData(data))
} else {
({ items: { items, pins, cursor } } = data)
}
}
const pinMap = pins?.reduce((a, p) => { a[p.position] = p; return a }, {})

View File

@ -1,137 +1,58 @@
import { Nav, Navbar } from 'react-bootstrap'
import styles from './header.module.css'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Form, Select } from './form'
export default function TopHeader ({ cat }) {
const router = useRouter()
const within = router.query.within
const userType = router.query.userType || 'stacked'
const top = async values => {
const what = values.what
delete values.what
if (values.sort === '') delete values.sort
if (values.when === '') delete values.when
await router.push({
pathname: `/top/${what}`,
query: values
})
}
return (
<>
<Navbar className='pt-0'>
<Nav
className={`${styles.navbarNav} justify-content-around`}
activeKey={cat.split('/')[0]}
>
<Nav.Item>
<Link href={`/top/posts/${within}`} passHref>
<Nav.Link
eventKey='posts'
className={styles.navLink}
>
posts
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={`/top/comments/${within}`} passHref>
<Nav.Link
eventKey='comments'
className={styles.navLink}
>
comments
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={`/top/users/stacked/${within}`} passHref>
<Nav.Link
eventKey='users'
className={styles.navLink}
>
users
</Nav.Link>
</Link>
</Nav.Item>
</Nav>
</Navbar>
{cat.split('/')[0] === 'users' &&
<Navbar className='pt-0'>
<Nav
className={`${styles.navbarNav} justify-content-around`}
activeKey={userType}
>
<Nav.Item>
<Link href={`/top/users/stacked/${within}`} passHref>
<Nav.Link
eventKey='stacked'
className={styles.navLink}
>
stacked
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={`/top/users/spent/${within}`} passHref>
<Nav.Link
eventKey='spent'
className={styles.navLink}
>
spent
</Nav.Link>
</Link>
</Nav.Item>
</Nav>
</Navbar>}
<Navbar className='pt-0'>
<Nav
className={styles.navbarNav}
activeKey={within}
>
<Nav.Item>
<Link href={`/top/${cat}/day`} passHref>
<Nav.Link
eventKey='day'
className={styles.navLink}
>
day
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={`/top/${cat}/week`} passHref>
<Nav.Link
eventKey='week'
className={styles.navLink}
>
week
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={`/top/${cat}/month`} passHref>
<Nav.Link
eventKey='month'
className={styles.navLink}
>
month
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={`/top/${cat}/year`} passHref>
<Nav.Link
eventKey='year'
className={styles.navLink}
>
year
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={`/top/${cat}/forever`} passHref>
<Nav.Link
eventKey='forever'
className={styles.navLink}
>
forever
</Nav.Link>
</Link>
</Nav.Item>
</Nav>
</Navbar>
</>
<div className='d-flex'>
<Form
className='mr-auto'
initial={{
what: cat,
sort: router.query.sort || '',
when: router.query.when || ''
}}
onSubmit={top}
>
<div className='text-muted font-weight-bold my-3 d-flex align-items-center'>
top
<Select
groupClassName='mx-2 mb-0'
onChange={(formik, e) => top({ ...formik?.values, what: e.target.value })}
name='what'
size='sm'
items={['posts', 'comments', 'users']}
/>
by
<Select
groupClassName='mx-2 mb-0'
onChange={(formik, e) => top({ ...formik?.values, sort: e.target.value })}
name='sort'
size='sm'
items={cat === 'users' ? ['stacked', 'spent', 'comments', 'posts'] : ['votes', 'comments', 'sats']}
/>
for
<Select
groupClassName='mb-0 ml-2'
onChange={(formik, e) => top({ ...formik?.values, when: e.target.value })}
name='when'
size='sm'
items={['day', 'week', 'month', 'year', 'forever']}
/>
</div>
</Form>
</div>
)
}

View File

@ -1,5 +1,6 @@
import Link from 'next/link'
import { Image } from 'react-bootstrap'
import { abbrNum } from '../lib/format'
import styles from './item.module.css'
import userStyles from './user-header.module.css'
@ -22,19 +23,19 @@ export default function UserList ({ users }) {
</a>
</Link>
<div className={styles.other}>
<span>{user.stacked} stacked</span>
<span>{abbrNum(user.stacked)} stacked</span>
<span> \ </span>
<span>{user.spent} spent</span>
<span>{abbrNum(user.spent)} spent</span>
<span> \ </span>
<Link href={`/${user.name}/posts`} passHref>
<a className='text-reset'>
{user.nitems} posts
{abbrNum(user.nitems)} posts
</a>
</Link>
<span> \ </span>
<Link href={`/${user.name}/comments`} passHref>
<a className='text-reset'>
{user.ncomments} comments
{abbrNum(user.ncomments)} comments
</a>
</Link>
</div>
@ -44,3 +45,28 @@ export default function UserList ({ users }) {
</>
)
}
export function UsersSkeleton () {
const users = new Array(21).fill(null)
return (
<div>{users.map((_, i) => (
<div className={`${styles.item} ${styles.skeleton} mb-2`} key={i}>
<Image
src='/clouds.jpeg' width='32' height='32'
className={`${userStyles.userimg} clouds mr-2`}
/>
<div className={styles.hunk}>
<div className={`${styles.name} clouds text-reset`} />
<div className={styles.other}>
<span className={`${styles.otherItem} clouds`} />
<span className={`${styles.otherItem} clouds`} />
<span className={`${styles.otherItem} ${styles.otherItemLonger} clouds`} />
<span className={`${styles.otherItem} ${styles.otherItemLonger} clouds`} />
</div>
</div>
</div>
))}
</div>
)
}

View File

@ -45,6 +45,19 @@ export const MORE_FLAT_COMMENTS = gql`
}
`
export const TOP_COMMENTS = gql`
${COMMENT_FIELDS}
query topComments($sort: String, $cursor: String, $when: String) {
topComments(sort: $sort, cursor: $cursor, when: $when) {
cursor
comments {
...CommentFields
}
}
}
`
export const COMMENTS = gql`
${COMMENT_FIELDS}

View File

@ -70,6 +70,23 @@ export const ITEMS = gql`
}
}`
export const TOP_ITEMS = gql`
${ITEM_FIELDS}
query topItems($sort: String, $cursor: String, $when: String) {
topItems(sort: $sort, cursor: $cursor, when: $when) {
cursor
items {
...ItemFields
position
},
pins {
...ItemFields
position
}
}
}`
export const OUTLAWED_ITEMS = gql`
${ITEM_FIELDS}

View File

@ -152,11 +152,15 @@ export const USER_FIELDS = gql`
}`
export const TOP_USERS = gql`
query TopUsers($cursor: String, $within: String!, $userType: String!) {
topUsers(cursor: $cursor, within: $within, userType: $userType) {
query TopUsers($cursor: String, $when: String, $sort: String) {
topUsers(cursor: $cursor, when: $when, sort: $sort) {
users {
name
amount
photoId
stacked
spent
ncomments
nitems
}
cursor
}

View File

@ -26,7 +26,7 @@ export default function getApolloClient () {
Query: {
fields: {
topUsers: {
keyArgs: ['within'],
keyArgs: ['when', 'sort'],
merge (existing, incoming) {
if (isFirstPage(incoming.cursor, existing?.users)) {
return incoming
@ -52,6 +52,33 @@ export default function getApolloClient () {
}
}
},
topItems: {
keyArgs: ['sort', 'when'],
merge (existing, incoming) {
if (isFirstPage(incoming.cursor, existing?.items)) {
return incoming
}
return {
cursor: incoming.cursor,
items: [...(existing?.items || []), ...incoming.items],
pins: existing?.pins || null
}
}
},
topComments: {
keyArgs: ['sort', 'when'],
merge (existing, incoming) {
if (isFirstPage(incoming.cursor, existing?.comments)) {
return incoming
}
return {
cursor: incoming.cursor,
comments: [...(existing?.comments || []), ...incoming.comments]
}
}
},
outlawedItems: {
keyArgs: [],
merge (existing, incoming) {

View File

@ -1,4 +1,4 @@
export const formatSats = n => {
export const abbrNum = n => {
if (n < 1e4) return n
if (n >= 1e4 && n < 1e6) return +(n / 1e3).toFixed(1) + 'k'
if (n >= 1e6 && n < 1e9) return +(n / 1e6).toFixed(1) + 'm'

View File

@ -2,12 +2,12 @@ import Layout from '../../../components/layout'
import { useRouter } from 'next/router'
import { getGetServerSideProps } from '../../../api/ssrApollo'
import TopHeader from '../../../components/top-header'
import { MORE_FLAT_COMMENTS } from '../../../fragments/comments'
import { TOP_COMMENTS } from '../../../fragments/comments'
import CommentsFlat from '../../../components/comments-flat'
export const getServerSideProps = getGetServerSideProps(MORE_FLAT_COMMENTS, { sort: 'top' })
export const getServerSideProps = getGetServerSideProps(TOP_COMMENTS)
export default function Index ({ data: { moreFlatComments: { comments, cursor } } }) {
export default function Index ({ data: { topComments: { comments, cursor } } }) {
const router = useRouter()
return (
@ -15,7 +15,9 @@ export default function Index ({ data: { moreFlatComments: { comments, cursor }
<TopHeader cat='comments' />
<CommentsFlat
comments={comments} cursor={cursor}
variables={{ sort: 'top', within: router.query?.within }}
query={TOP_COMMENTS}
destructureData={data => data.topComments}
variables={{ sort: router.query.sort, when: router.query.when }}
includeParent noReply
/>
</Layout>

View File

@ -2,13 +2,12 @@ import Layout from '../../../components/layout'
import Items from '../../../components/items'
import { useRouter } from 'next/router'
import { getGetServerSideProps } from '../../../api/ssrApollo'
import { ITEMS } from '../../../fragments/items'
import { TOP_ITEMS } from '../../../fragments/items'
import TopHeader from '../../../components/top-header'
export const getServerSideProps = getGetServerSideProps(ITEMS, { sort: 'top' })
export const getServerSideProps = getGetServerSideProps(TOP_ITEMS)
export default function Index ({ data: { items: { items, cursor } } }) {
export default function Index ({ data: { topItems: { items, cursor } } }) {
const router = useRouter()
return (
@ -16,7 +15,9 @@ export default function Index ({ data: { items: { items, cursor } } }) {
<TopHeader cat='posts' />
<Items
items={items} cursor={cursor}
variables={{ sort: 'top', within: router.query?.within }} rank
query={TOP_ITEMS}
destructureData={data => data.topItems}
variables={{ sort: router.query.sort, when: router.query.when }} rank
/>
</Layout>
)

View File

@ -1,52 +0,0 @@
import Layout from '../../../../components/layout'
import { useRouter } from 'next/router'
import { getGetServerSideProps } from '../../../../api/ssrApollo'
import TopHeader from '../../../../components/top-header'
import { TOP_USERS } from '../../../../fragments/users'
import { useQuery } from '@apollo/client'
import Link from 'next/link'
import MoreFooter from '../../../../components/more-footer'
export const getServerSideProps = getGetServerSideProps(TOP_USERS)
export default function Index ({ data: { topUsers: { users, cursor } } }) {
const router = useRouter()
const userType = router.query.userType
const { data, fetchMore } = useQuery(TOP_USERS, {
variables: { within: router.query?.within, userType: router.query?.userType }
})
if (data) {
({ topUsers: { users, cursor } } = data)
}
return (
<Layout>
<TopHeader cat={'users/' + userType} />
{users.map(user => (
<Link href={`/${user.name}`} key={user.name}>
<div className='d-flex align-items-center pointer'>
<h3 className='mb-0'>@{user.name}</h3>
<h2 className='ml-2 mb-0'><small className='text-success'>{user.amount} {userType}</small></h2>
</div>
</Link>
))}
<MoreFooter cursor={cursor} fetchMore={fetchMore} Skeleton={UsersSkeleton} />
</Layout>
)
}
function UsersSkeleton () {
const users = new Array(21).fill(null)
return (
<div>{users.map((_, i) => (
<div key={i} className='d-flex align-items-center' style={{ height: '34px' }}>
<div className='clouds' style={{ width: '172px', borderRadius: '.4rem', height: '27px' }} />
<div className='ml-2 clouds' style={{ width: '137px', borderRadius: '.4rem', height: '30px', margin: '3px 0px' }} />
</div>
))}
</div>
)
}

30
pages/top/users/index.js Normal file
View File

@ -0,0 +1,30 @@
import Layout from '../../../components/layout'
import { useRouter } from 'next/router'
import { getGetServerSideProps } from '../../../api/ssrApollo'
import TopHeader from '../../../components/top-header'
import { TOP_USERS } from '../../../fragments/users'
import { useQuery } from '@apollo/client'
import MoreFooter from '../../../components/more-footer'
import UserList, { UsersSkeleton } from '../../../components/user-list'
export const getServerSideProps = getGetServerSideProps(TOP_USERS)
export default function Index ({ data: { topUsers: { users, cursor } } }) {
const router = useRouter()
const { data, fetchMore } = useQuery(TOP_USERS, {
variables: { when: router.query.when, sort: router.query.sort }
})
if (data) {
({ topUsers: { users, cursor } } = data)
}
return (
<Layout>
<TopHeader cat='users' />
<UserList users={users} />
<MoreFooter cursor={cursor} fetchMore={fetchMore} Skeleton={UsersSkeleton} />
</Layout>
)
}

View File

@ -3,7 +3,7 @@ import { getGetServerSideProps } from '../../api/ssrApollo'
import Layout from '../../components/layout'
import { LineChart, Line, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer, AreaChart, Area } from 'recharts'
import { Col, Row } from 'react-bootstrap'
import { formatSats } from '../../lib/format'
import { abbrNum } from '../../lib/format'
import { UsageHeader } from '../../components/usage-header'
export const getServerSideProps = getGetServerSideProps(
@ -115,7 +115,7 @@ function GrowthAreaChart ({ data, xName, title }) {
dataKey='time' tickFormatter={dateFormatter} name={xName}
tick={{ fill: 'var(--theme-grey)' }}
/>
<YAxis tickFormatter={formatSats} tick={{ fill: 'var(--theme-grey)' }} />
<YAxis tickFormatter={abbrNum} tick={{ fill: 'var(--theme-grey)' }} />
<Tooltip labelFormatter={dateFormatter} contentStyle={{ color: 'var(--theme-color)', backgroundColor: 'var(--theme-body)' }} />
<Legend />
{Object.keys(data[0]).filter(v => v !== 'time' && v !== '__typename').map((v, i) =>
@ -141,7 +141,7 @@ function GrowthLineChart ({ data, xName, yName }) {
dataKey='time' tickFormatter={dateFormatter} name={xName}
tick={{ fill: 'var(--theme-grey)' }}
/>
<YAxis tickFormatter={formatSats} tick={{ fill: 'var(--theme-grey)' }} />
<YAxis tickFormatter={abbrNum} tick={{ fill: 'var(--theme-grey)' }} />
<Tooltip labelFormatter={dateFormatter} contentStyle={{ color: 'var(--theme-color)', backgroundColor: 'var(--theme-body)' }} />
<Legend />
<Line type='monotone' dataKey='num' name={yName} stroke='var(--secondary)' />

View File

@ -49,18 +49,18 @@ export default function Growth ({
</Col>
</Row>
<Row>
<Col className='mt-3'>
<Col className='mt-3 p-0'>
<div className='text-center text-muted font-weight-bold'>items</div>
<GrowthPieChart data={itemsWeekly} />
</Col>
<Col className='mt-3'>
<div className='text-center text-muted font-weight-bold'>stacked</div>
<GrowthPieChart data={stackedWeekly} />
</Col>
<Col className='mt-3'>
<Col className='mt-3 p-0'>
<div className='text-center text-muted font-weight-bold'>spent</div>
<GrowthPieChart data={spentWeekly} />
</Col>
<Col className='mt-3 p-0'>
<div className='text-center text-muted font-weight-bold'>stacked</div>
<GrowthPieChart data={stackedWeekly} />
</Col>
</Row>
</Layout>
)