improved top
This commit is contained in:
parent
30b1ee33aa
commit
a398784f26
@ -50,8 +50,8 @@ export async function getItem (parent, { id }, { me, models }) {
|
|||||||
function topClause (within) {
|
function topClause (within) {
|
||||||
let interval = ' AND "Item".created_at >= $1 - INTERVAL '
|
let interval = ' AND "Item".created_at >= $1 - INTERVAL '
|
||||||
switch (within) {
|
switch (within) {
|
||||||
case 'day':
|
case 'forever':
|
||||||
interval += "'1 day'"
|
interval = ''
|
||||||
break
|
break
|
||||||
case 'week':
|
case 'week':
|
||||||
interval += "'7 days'"
|
interval += "'7 days'"
|
||||||
@ -63,12 +63,23 @@ function topClause (within) {
|
|||||||
interval += "'1 year'"
|
interval += "'1 year'"
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
interval = ''
|
interval += "'1 day'"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return interval
|
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) {
|
export async function orderByNumerator (me, models) {
|
||||||
if (me) {
|
if (me) {
|
||||||
const user = await models.user.findUnique({ where: { id: me.id } })
|
const user = await models.user.findUnique({ where: { id: me.id } })
|
||||||
@ -124,6 +135,40 @@ export default {
|
|||||||
|
|
||||||
return count
|
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 }) => {
|
items: async (parent, { sub, sort, cursor, name, within }, { me, models }) => {
|
||||||
const decodedCursor = decodeCursor(cursor)
|
const decodedCursor = decodeCursor(cursor)
|
||||||
let items; let user; let pins; let subFull
|
let items; let user; let pins; let subFull
|
||||||
|
@ -6,8 +6,8 @@ import serialize from './serial'
|
|||||||
export function topClause (within) {
|
export function topClause (within) {
|
||||||
let interval = ' AND "ItemAct".created_at >= $1 - INTERVAL '
|
let interval = ' AND "ItemAct".created_at >= $1 - INTERVAL '
|
||||||
switch (within) {
|
switch (within) {
|
||||||
case 'day':
|
case 'forever':
|
||||||
interval += "'1 day'"
|
interval = ''
|
||||||
break
|
break
|
||||||
case 'week':
|
case 'week':
|
||||||
interval += "'7 days'"
|
interval += "'7 days'"
|
||||||
@ -19,7 +19,7 @@ export function topClause (within) {
|
|||||||
interval += "'1 year'"
|
interval += "'1 year'"
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
interval = ''
|
interval += "'1 day'"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return interval
|
return interval
|
||||||
@ -28,8 +28,8 @@ export function topClause (within) {
|
|||||||
export function earnWithin (within) {
|
export function earnWithin (within) {
|
||||||
let interval = ' AND "Earn".created_at >= $1 - INTERVAL '
|
let interval = ' AND "Earn".created_at >= $1 - INTERVAL '
|
||||||
switch (within) {
|
switch (within) {
|
||||||
case 'day':
|
case 'forever':
|
||||||
interval += "'1 day'"
|
interval = ''
|
||||||
break
|
break
|
||||||
case 'week':
|
case 'week':
|
||||||
interval += "'7 days'"
|
interval += "'7 days'"
|
||||||
@ -41,8 +41,30 @@ export function earnWithin (within) {
|
|||||||
interval += "'1 year'"
|
interval += "'1 year'"
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
|
interval += "'1 day'"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return interval
|
||||||
|
}
|
||||||
|
|
||||||
|
export function itemWithin (within) {
|
||||||
|
let interval = ' AND "Item".created_at >= $1 - INTERVAL '
|
||||||
|
switch (within) {
|
||||||
|
case 'forever':
|
||||||
interval = ''
|
interval = ''
|
||||||
break
|
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
|
return interval
|
||||||
}
|
}
|
||||||
@ -94,37 +116,59 @@ export default {
|
|||||||
|
|
||||||
return user.name?.toUpperCase() === name?.toUpperCase() || !(await models.user.findUnique({ where: { name } }))
|
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)
|
const decodedCursor = decodeCursor(cursor)
|
||||||
let users
|
let users
|
||||||
if (userType === 'spent') {
|
if (sort === 'spent') {
|
||||||
users = await models.$queryRaw(`
|
users = await models.$queryRaw(`
|
||||||
SELECT users.name, users.created_at, sum("ItemAct".sats) as amount
|
SELECT users.*, sum("ItemAct".sats) as spent
|
||||||
FROM "ItemAct"
|
FROM "ItemAct"
|
||||||
JOIN users on "ItemAct"."userId" = users.id
|
JOIN users on "ItemAct"."userId" = users.id
|
||||||
WHERE "ItemAct".created_at <= $1
|
WHERE "ItemAct".created_at <= $1
|
||||||
${topClause(within)}
|
${topClause(when)}
|
||||||
GROUP BY users.id, users.name
|
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
|
OFFSET $2
|
||||||
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
||||||
} else {
|
} else {
|
||||||
users = await models.$queryRaw(`
|
users = await models.$queryRaw(`
|
||||||
SELECT name, created_at, sum(sats) as amount
|
SELECT u.id, u.name, u."photoId", sum(amount) as stacked
|
||||||
FROM
|
FROM
|
||||||
((SELECT users.name, users.created_at, "ItemAct".sats as sats
|
((SELECT users.*, "ItemAct".sats as amount
|
||||||
FROM "ItemAct"
|
FROM "ItemAct"
|
||||||
JOIN "Item" on "ItemAct"."itemId" = "Item".id
|
JOIN "Item" on "ItemAct"."itemId" = "Item".id
|
||||||
JOIN users on "Item"."userId" = users.id
|
JOIN users on "Item"."userId" = users.id
|
||||||
WHERE act <> 'BOOST' AND "ItemAct"."userId" <> users.id AND "ItemAct".created_at <= $1
|
WHERE act <> 'BOOST' AND "ItemAct"."userId" <> users.id AND "ItemAct".created_at <= $1
|
||||||
${topClause(within)})
|
${topClause(when)})
|
||||||
UNION ALL
|
UNION ALL
|
||||||
(SELECT users.name, users.created_at, "Earn".msats/1000 as sats
|
(SELECT users.*, "Earn".msats/1000 as amount
|
||||||
FROM "Earn"
|
FROM "Earn"
|
||||||
JOIN users on users.id = "Earn"."userId"
|
JOIN users on users.id = "Earn"."userId"
|
||||||
WHERE "Earn".msats > 0 ${earnWithin(within)})) u
|
WHERE "Earn".msats > 0 ${earnWithin(when)})) u
|
||||||
GROUP BY name, created_at
|
GROUP BY u.id, u.name, u.created_at, u."photoId"
|
||||||
ORDER BY amount DESC NULLS LAST, created_at DESC
|
ORDER BY stacked DESC NULLS LAST, created_at DESC
|
||||||
OFFSET $2
|
OFFSET $2
|
||||||
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
||||||
}
|
}
|
||||||
@ -260,9 +304,15 @@ export default {
|
|||||||
User: {
|
User: {
|
||||||
authMethods,
|
authMethods,
|
||||||
nitems: async (user, args, { models }) => {
|
nitems: async (user, args, { models }) => {
|
||||||
|
if (user.nitems) {
|
||||||
|
return user.nitems
|
||||||
|
}
|
||||||
return await models.item.count({ where: { userId: user.id, parentId: null } })
|
return await models.item.count({ where: { userId: user.id, parentId: null } })
|
||||||
},
|
},
|
||||||
ncomments: async (user, args, { models }) => {
|
ncomments: async (user, args, { models }) => {
|
||||||
|
if (user.ncomments) {
|
||||||
|
return user.ncomments
|
||||||
|
}
|
||||||
return await models.item.count({ where: { userId: user.id, parentId: { not: null } } })
|
return await models.item.count({ where: { userId: user.id, parentId: { not: null } } })
|
||||||
},
|
},
|
||||||
stacked: async (user, args, { models }) => {
|
stacked: async (user, args, { models }) => {
|
||||||
@ -273,6 +323,10 @@ export default {
|
|||||||
return Math.floor((user.stackedMsats || 0) / 1000)
|
return Math.floor((user.stackedMsats || 0) / 1000)
|
||||||
},
|
},
|
||||||
spent: async (user, args, { models }) => {
|
spent: async (user, args, { models }) => {
|
||||||
|
if (user.spent) {
|
||||||
|
return user.spent
|
||||||
|
}
|
||||||
|
|
||||||
const { sum: { sats } } = await models.itemAct.aggregate({
|
const { sum: { sats } } = await models.itemAct.aggregate({
|
||||||
sum: {
|
sum: {
|
||||||
sats: true
|
sats: true
|
||||||
|
@ -15,6 +15,8 @@ export default gql`
|
|||||||
outlawedItems(cursor: String): Items
|
outlawedItems(cursor: String): Items
|
||||||
borderlandItems(cursor: String): Items
|
borderlandItems(cursor: String): Items
|
||||||
freebieItems(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 {
|
type ItemActResult {
|
||||||
|
@ -7,7 +7,7 @@ export default gql`
|
|||||||
user(name: String!): User
|
user(name: String!): User
|
||||||
users: [User!]
|
users: [User!]
|
||||||
nameAvailable(name: String!): Boolean!
|
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!]!
|
searchUsers(q: String!, limit: Int, similarity: Float): [User!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,17 +16,6 @@ export default gql`
|
|||||||
users: [User!]!
|
users: [User!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type TopUsers {
|
|
||||||
cursor: String
|
|
||||||
users: [TopUser!]!
|
|
||||||
}
|
|
||||||
|
|
||||||
type TopUser {
|
|
||||||
name: String!
|
|
||||||
createdAt: String!
|
|
||||||
amount: Int!
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
setName(name: String!): Boolean
|
setName(name: String!): Boolean
|
||||||
setSettings(tipDefault: Int!, fiatCurrency: String!, noteItemSats: Boolean!, noteEarning: Boolean!,
|
setSettings(tipDefault: Int!, fiatCurrency: String!, noteItemSats: Boolean!, noteEarning: Boolean!,
|
||||||
|
@ -17,6 +17,7 @@ import { useMe } from './me'
|
|||||||
import DontLikeThis from './dont-link-this'
|
import DontLikeThis from './dont-link-this'
|
||||||
import Flag from '../svgs/flag-fill.svg'
|
import Flag from '../svgs/flag-fill.svg'
|
||||||
import { Badge } from 'react-bootstrap'
|
import { Badge } from 'react-bootstrap'
|
||||||
|
import { abbrNum } from '../lib/format'
|
||||||
|
|
||||||
function Parent ({ item, rootText }) {
|
function Parent ({ item, rootText }) {
|
||||||
const ParentFrag = () => (
|
const ParentFrag = () => (
|
||||||
@ -114,11 +115,11 @@ export default function Comment ({
|
|||||||
<div className={`${itemStyles.hunk} ${styles.hunk}`}>
|
<div className={`${itemStyles.hunk} ${styles.hunk}`}>
|
||||||
<div className='d-flex align-items-center'>
|
<div className='d-flex align-items-center'>
|
||||||
<div className={`${itemStyles.other} ${styles.other}`}>
|
<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>
|
<span> \ </span>
|
||||||
{item.boost > 0 &&
|
{item.boost > 0 &&
|
||||||
<>
|
<>
|
||||||
<span>{item.boost} boost</span>
|
<span>{abbrNum(item.boost)} boost</span>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
</>}
|
</>}
|
||||||
<Link href={`/items/${item.id}`} passHref>
|
<Link href={`/items/${item.id}`} passHref>
|
||||||
|
@ -3,8 +3,8 @@ import { MORE_FLAT_COMMENTS } from '../fragments/comments'
|
|||||||
import { CommentFlat, CommentSkeleton } from './comment'
|
import { CommentFlat, CommentSkeleton } from './comment'
|
||||||
import MoreFooter from './more-footer'
|
import MoreFooter from './more-footer'
|
||||||
|
|
||||||
export default function CommentsFlat ({ variables, comments, cursor, ...props }) {
|
export default function CommentsFlat ({ variables, query, destructureData, comments, cursor, ...props }) {
|
||||||
const { data, fetchMore } = useQuery(MORE_FLAT_COMMENTS, {
|
const { data, fetchMore } = useQuery(query || MORE_FLAT_COMMENTS, {
|
||||||
variables
|
variables
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -13,7 +13,11 @@ export default function CommentsFlat ({ variables, comments, cursor, ...props })
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
({ moreFlatComments: { comments, cursor } } = data)
|
if (destructureData) {
|
||||||
|
({ comments, cursor } = destructureData(data))
|
||||||
|
} else {
|
||||||
|
({ moreFlatComments: { comments, cursor } } = data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -5,6 +5,7 @@ import styles from './header.module.css'
|
|||||||
import { Nav, Navbar } from 'react-bootstrap'
|
import { Nav, Navbar } from 'react-bootstrap'
|
||||||
import { COMMENTS_QUERY } from '../fragments/items'
|
import { COMMENTS_QUERY } from '../fragments/items'
|
||||||
import { COMMENTS } from '../fragments/comments'
|
import { COMMENTS } from '../fragments/comments'
|
||||||
|
import { abbrNum } from '../lib/format'
|
||||||
|
|
||||||
export function CommentsHeader ({ handleSort, commentSats }) {
|
export function CommentsHeader ({ handleSort, commentSats }) {
|
||||||
const [sort, setSort] = useState('hot')
|
const [sort, setSort] = useState('hot')
|
||||||
@ -23,7 +24,7 @@ export function CommentsHeader ({ handleSort, commentSats }) {
|
|||||||
activeKey={sort}
|
activeKey={sort}
|
||||||
>
|
>
|
||||||
<Nav.Item className='text-muted'>
|
<Nav.Item className='text-muted'>
|
||||||
{commentSats} sats
|
{abbrNum(commentSats)} sats
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
<div className='ml-auto d-flex'>
|
<div className='ml-auto d-flex'>
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
|
@ -11,7 +11,7 @@ import { signOut, signIn } from 'next-auth/client'
|
|||||||
import { useLightning } from './lightning'
|
import { useLightning } from './lightning'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { randInRange } from '../lib/rand'
|
import { randInRange } from '../lib/rand'
|
||||||
import { formatSats } from '../lib/format'
|
import { abbrNum } from '../lib/format'
|
||||||
import NoteIcon from '../svgs/notification-4-fill.svg'
|
import NoteIcon from '../svgs/notification-4-fill.svg'
|
||||||
import { useQuery, gql } from '@apollo/client'
|
import { useQuery, gql } from '@apollo/client'
|
||||||
import LightningIcon from '../svgs/bolt.svg'
|
import LightningIcon from '../svgs/bolt.svg'
|
||||||
@ -19,7 +19,7 @@ import LightningIcon from '../svgs/bolt.svg'
|
|||||||
function WalletSummary ({ me }) {
|
function WalletSummary ({ me }) {
|
||||||
if (!me) return null
|
if (!me) return null
|
||||||
|
|
||||||
return `${formatSats(me.sats)}`
|
return `${abbrNum(me.sats)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Header ({ sub }) {
|
export default function Header ({ sub }) {
|
||||||
@ -154,7 +154,7 @@ export default function Header ({ sub }) {
|
|||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
{!prefix &&
|
{!prefix &&
|
||||||
<Nav.Item className={className}>
|
<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>
|
<Nav.Link eventKey='top' className={styles.navLink}>top</Nav.Link>
|
||||||
</Link>
|
</Link>
|
||||||
</Nav.Item>}
|
</Nav.Item>}
|
||||||
@ -230,7 +230,7 @@ const NavItemsStatic = ({ className }) => {
|
|||||||
</Link>
|
</Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
<Nav.Item className={className}>
|
<Nav.Item className={className}>
|
||||||
<Link href='/top/posts/week' passHref>
|
<Link href='/top/posts' passHref>
|
||||||
<Nav.Link className={styles.navLink}>top</Nav.Link>
|
<Nav.Link className={styles.navLink}>top</Nav.Link>
|
||||||
</Link>
|
</Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
|
@ -14,6 +14,7 @@ import { newComments } from '../lib/new-comments'
|
|||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
import DontLikeThis from './dont-link-this'
|
import DontLikeThis from './dont-link-this'
|
||||||
import Flag from '../svgs/flag-fill.svg'
|
import Flag from '../svgs/flag-fill.svg'
|
||||||
|
import { abbrNum } from '../lib/format'
|
||||||
|
|
||||||
export function SearchTitle ({ title }) {
|
export function SearchTitle ({ title }) {
|
||||||
return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => {
|
return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => {
|
||||||
@ -87,12 +88,12 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) {
|
|||||||
<div className={`${styles.other}`}>
|
<div className={`${styles.other}`}>
|
||||||
{!item.position &&
|
{!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>
|
<span> \ </span>
|
||||||
</>}
|
</>}
|
||||||
{item.boost > 0 &&
|
{item.boost > 0 &&
|
||||||
<>
|
<>
|
||||||
<span>{item.boost} boost</span>
|
<span>{abbrNum(item.boost)} boost</span>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
</>}
|
</>}
|
||||||
<Link href={`/items/${item.id}`} passHref>
|
<Link href={`/items/${item.id}`} passHref>
|
||||||
|
@ -127,6 +127,14 @@ a.link:visited {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.skeleton .name {
|
||||||
|
background-color: var(--theme-grey);
|
||||||
|
width: 100px;
|
||||||
|
border-radius: .4rem;
|
||||||
|
height: 17px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.skeleton .link {
|
.skeleton .link {
|
||||||
height: 14px;
|
height: 14px;
|
||||||
background-color: var(--theme-grey);
|
background-color: var(--theme-grey);
|
||||||
|
@ -7,15 +7,19 @@ import MoreFooter from './more-footer'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Comment from './comment'
|
import Comment from './comment'
|
||||||
|
|
||||||
export default function Items ({ variables = {}, rank, items, pins, cursor }) {
|
export default function Items ({ variables = {}, query, destructureData, rank, items, pins, cursor }) {
|
||||||
const { data, fetchMore } = useQuery(ITEMS, { variables })
|
const { data, fetchMore } = useQuery(query || ITEMS, { variables })
|
||||||
|
|
||||||
if (!data && !items) {
|
if (!data && !items) {
|
||||||
return <ItemsSkeleton rank={rank} />
|
return <ItemsSkeleton rank={rank} />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data) {
|
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 }, {})
|
const pinMap = pins?.reduce((a, p) => { a[p.position] = p; return a }, {})
|
||||||
|
@ -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 { useRouter } from 'next/router'
|
||||||
|
import { Form, Select } from './form'
|
||||||
|
|
||||||
export default function TopHeader ({ cat }) {
|
export default function TopHeader ({ cat }) {
|
||||||
const router = useRouter()
|
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 (
|
return (
|
||||||
<>
|
<div className='d-flex'>
|
||||||
<Navbar className='pt-0'>
|
<Form
|
||||||
<Nav
|
className='mr-auto'
|
||||||
className={`${styles.navbarNav} justify-content-around`}
|
initial={{
|
||||||
activeKey={cat.split('/')[0]}
|
what: cat,
|
||||||
>
|
sort: router.query.sort || '',
|
||||||
<Nav.Item>
|
when: router.query.when || ''
|
||||||
<Link href={`/top/posts/${within}`} passHref>
|
}}
|
||||||
<Nav.Link
|
onSubmit={top}
|
||||||
eventKey='posts'
|
>
|
||||||
className={styles.navLink}
|
<div className='text-muted font-weight-bold my-3 d-flex align-items-center'>
|
||||||
>
|
top
|
||||||
posts
|
<Select
|
||||||
</Nav.Link>
|
groupClassName='mx-2 mb-0'
|
||||||
</Link>
|
onChange={(formik, e) => top({ ...formik?.values, what: e.target.value })}
|
||||||
</Nav.Item>
|
name='what'
|
||||||
<Nav.Item>
|
size='sm'
|
||||||
<Link href={`/top/comments/${within}`} passHref>
|
items={['posts', 'comments', 'users']}
|
||||||
<Nav.Link
|
/>
|
||||||
eventKey='comments'
|
by
|
||||||
className={styles.navLink}
|
<Select
|
||||||
>
|
groupClassName='mx-2 mb-0'
|
||||||
comments
|
onChange={(formik, e) => top({ ...formik?.values, sort: e.target.value })}
|
||||||
</Nav.Link>
|
name='sort'
|
||||||
</Link>
|
size='sm'
|
||||||
</Nav.Item>
|
items={cat === 'users' ? ['stacked', 'spent', 'comments', 'posts'] : ['votes', 'comments', 'sats']}
|
||||||
<Nav.Item>
|
/>
|
||||||
<Link href={`/top/users/stacked/${within}`} passHref>
|
for
|
||||||
<Nav.Link
|
<Select
|
||||||
eventKey='users'
|
groupClassName='mb-0 ml-2'
|
||||||
className={styles.navLink}
|
onChange={(formik, e) => top({ ...formik?.values, when: e.target.value })}
|
||||||
>
|
name='when'
|
||||||
users
|
size='sm'
|
||||||
</Nav.Link>
|
items={['day', 'week', 'month', 'year', 'forever']}
|
||||||
</Link>
|
/>
|
||||||
</Nav.Item>
|
</div>
|
||||||
</Nav>
|
</Form>
|
||||||
</Navbar>
|
</div>
|
||||||
{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>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { Image } from 'react-bootstrap'
|
import { Image } from 'react-bootstrap'
|
||||||
|
import { abbrNum } from '../lib/format'
|
||||||
import styles from './item.module.css'
|
import styles from './item.module.css'
|
||||||
import userStyles from './user-header.module.css'
|
import userStyles from './user-header.module.css'
|
||||||
|
|
||||||
@ -22,19 +23,19 @@ export default function UserList ({ users }) {
|
|||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<div className={styles.other}>
|
<div className={styles.other}>
|
||||||
<span>{user.stacked} stacked</span>
|
<span>{abbrNum(user.stacked)} stacked</span>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
<span>{user.spent} spent</span>
|
<span>{abbrNum(user.spent)} spent</span>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
<Link href={`/${user.name}/posts`} passHref>
|
<Link href={`/${user.name}/posts`} passHref>
|
||||||
<a className='text-reset'>
|
<a className='text-reset'>
|
||||||
{user.nitems} posts
|
{abbrNum(user.nitems)} posts
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
<Link href={`/${user.name}/comments`} passHref>
|
<Link href={`/${user.name}/comments`} passHref>
|
||||||
<a className='text-reset'>
|
<a className='text-reset'>
|
||||||
{user.ncomments} comments
|
{abbrNum(user.ncomments)} comments
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -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`
|
export const COMMENTS = gql`
|
||||||
${COMMENT_FIELDS}
|
${COMMENT_FIELDS}
|
||||||
|
|
||||||
|
@ -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`
|
export const OUTLAWED_ITEMS = gql`
|
||||||
${ITEM_FIELDS}
|
${ITEM_FIELDS}
|
||||||
|
|
||||||
|
@ -152,11 +152,15 @@ export const USER_FIELDS = gql`
|
|||||||
}`
|
}`
|
||||||
|
|
||||||
export const TOP_USERS = gql`
|
export const TOP_USERS = gql`
|
||||||
query TopUsers($cursor: String, $within: String!, $userType: String!) {
|
query TopUsers($cursor: String, $when: String, $sort: String) {
|
||||||
topUsers(cursor: $cursor, within: $within, userType: $userType) {
|
topUsers(cursor: $cursor, when: $when, sort: $sort) {
|
||||||
users {
|
users {
|
||||||
name
|
name
|
||||||
amount
|
photoId
|
||||||
|
stacked
|
||||||
|
spent
|
||||||
|
ncomments
|
||||||
|
nitems
|
||||||
}
|
}
|
||||||
cursor
|
cursor
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ export default function getApolloClient () {
|
|||||||
Query: {
|
Query: {
|
||||||
fields: {
|
fields: {
|
||||||
topUsers: {
|
topUsers: {
|
||||||
keyArgs: ['within'],
|
keyArgs: ['when', 'sort'],
|
||||||
merge (existing, incoming) {
|
merge (existing, incoming) {
|
||||||
if (isFirstPage(incoming.cursor, existing?.users)) {
|
if (isFirstPage(incoming.cursor, existing?.users)) {
|
||||||
return incoming
|
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: {
|
outlawedItems: {
|
||||||
keyArgs: [],
|
keyArgs: [],
|
||||||
merge (existing, incoming) {
|
merge (existing, incoming) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export const formatSats = n => {
|
export const abbrNum = n => {
|
||||||
if (n < 1e4) return n
|
if (n < 1e4) return n
|
||||||
if (n >= 1e4 && n < 1e6) return +(n / 1e3).toFixed(1) + 'k'
|
if (n >= 1e4 && n < 1e6) return +(n / 1e3).toFixed(1) + 'k'
|
||||||
if (n >= 1e6 && n < 1e9) return +(n / 1e6).toFixed(1) + 'm'
|
if (n >= 1e6 && n < 1e9) return +(n / 1e6).toFixed(1) + 'm'
|
||||||
|
@ -2,12 +2,12 @@ import Layout from '../../../components/layout'
|
|||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { getGetServerSideProps } from '../../../api/ssrApollo'
|
import { getGetServerSideProps } from '../../../api/ssrApollo'
|
||||||
import TopHeader from '../../../components/top-header'
|
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'
|
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()
|
const router = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -15,7 +15,9 @@ export default function Index ({ data: { moreFlatComments: { comments, cursor }
|
|||||||
<TopHeader cat='comments' />
|
<TopHeader cat='comments' />
|
||||||
<CommentsFlat
|
<CommentsFlat
|
||||||
comments={comments} cursor={cursor}
|
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
|
includeParent noReply
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
@ -2,13 +2,12 @@ import Layout from '../../../components/layout'
|
|||||||
import Items from '../../../components/items'
|
import Items from '../../../components/items'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { getGetServerSideProps } from '../../../api/ssrApollo'
|
import { getGetServerSideProps } from '../../../api/ssrApollo'
|
||||||
import { ITEMS } from '../../../fragments/items'
|
import { TOP_ITEMS } from '../../../fragments/items'
|
||||||
|
|
||||||
import TopHeader from '../../../components/top-header'
|
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()
|
const router = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -16,7 +15,9 @@ export default function Index ({ data: { items: { items, cursor } } }) {
|
|||||||
<TopHeader cat='posts' />
|
<TopHeader cat='posts' />
|
||||||
<Items
|
<Items
|
||||||
items={items} cursor={cursor}
|
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>
|
</Layout>
|
||||||
)
|
)
|
@ -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
30
pages/top/users/index.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -3,7 +3,7 @@ import { getGetServerSideProps } from '../../api/ssrApollo'
|
|||||||
import Layout from '../../components/layout'
|
import Layout from '../../components/layout'
|
||||||
import { LineChart, Line, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer, AreaChart, Area } from 'recharts'
|
import { LineChart, Line, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer, AreaChart, Area } from 'recharts'
|
||||||
import { Col, Row } from 'react-bootstrap'
|
import { Col, Row } from 'react-bootstrap'
|
||||||
import { formatSats } from '../../lib/format'
|
import { abbrNum } from '../../lib/format'
|
||||||
import { UsageHeader } from '../../components/usage-header'
|
import { UsageHeader } from '../../components/usage-header'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps(
|
export const getServerSideProps = getGetServerSideProps(
|
||||||
@ -115,7 +115,7 @@ function GrowthAreaChart ({ data, xName, title }) {
|
|||||||
dataKey='time' tickFormatter={dateFormatter} name={xName}
|
dataKey='time' tickFormatter={dateFormatter} name={xName}
|
||||||
tick={{ fill: 'var(--theme-grey)' }}
|
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)' }} />
|
<Tooltip labelFormatter={dateFormatter} contentStyle={{ color: 'var(--theme-color)', backgroundColor: 'var(--theme-body)' }} />
|
||||||
<Legend />
|
<Legend />
|
||||||
{Object.keys(data[0]).filter(v => v !== 'time' && v !== '__typename').map((v, i) =>
|
{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}
|
dataKey='time' tickFormatter={dateFormatter} name={xName}
|
||||||
tick={{ fill: 'var(--theme-grey)' }}
|
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)' }} />
|
<Tooltip labelFormatter={dateFormatter} contentStyle={{ color: 'var(--theme-color)', backgroundColor: 'var(--theme-body)' }} />
|
||||||
<Legend />
|
<Legend />
|
||||||
<Line type='monotone' dataKey='num' name={yName} stroke='var(--secondary)' />
|
<Line type='monotone' dataKey='num' name={yName} stroke='var(--secondary)' />
|
||||||
|
@ -49,18 +49,18 @@ export default function Growth ({
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col className='mt-3'>
|
<Col className='mt-3 p-0'>
|
||||||
<div className='text-center text-muted font-weight-bold'>items</div>
|
<div className='text-center text-muted font-weight-bold'>items</div>
|
||||||
<GrowthPieChart data={itemsWeekly} />
|
<GrowthPieChart data={itemsWeekly} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col className='mt-3'>
|
<Col className='mt-3 p-0'>
|
||||||
<div className='text-center text-muted font-weight-bold'>stacked</div>
|
|
||||||
<GrowthPieChart data={stackedWeekly} />
|
|
||||||
</Col>
|
|
||||||
<Col className='mt-3'>
|
|
||||||
<div className='text-center text-muted font-weight-bold'>spent</div>
|
<div className='text-center text-muted font-weight-bold'>spent</div>
|
||||||
<GrowthPieChart data={spentWeekly} />
|
<GrowthPieChart data={spentWeekly} />
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col className='mt-3 p-0'>
|
||||||
|
<div className='text-center text-muted font-weight-bold'>stacked</div>
|
||||||
|
<GrowthPieChart data={stackedWeekly} />
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user