sort by top posts

This commit is contained in:
keyan 2021-10-21 17:05:06 -05:00
parent cd61263159
commit 68ddd0f86b
10 changed files with 11827 additions and 56 deletions

View File

@ -34,9 +34,10 @@ export async function getItem (parent, { id }, { models }) {
export default {
Query: {
moreItems: async (parent, { sort, cursor, userId }, { me, models }) => {
moreItems: async (parent, { sort, cursor, userId, within }, { me, models }) => {
const decodedCursor = decodeCursor(cursor)
let items
let interval = 'INTERVAL '
switch (sort) {
case 'user':
items = await models.$queryRaw(`
@ -77,6 +78,32 @@ export default {
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
}
break
case 'top':
switch (within) {
case 'day':
interval += "'1 day'"
break
case 'week':
interval += "'7 days'"
break
case 'month':
interval += "'1 month'"
break
case 'year':
interval += "'1 year'"
break
}
items = await models.$queryRaw(`
${SELECT}
FROM "Item"
${timedLeftJoinSats(1)}
WHERE "parentId" IS NULL AND created_at <= $1
${within ? ` AND created_at >= $1 - ${interval}` : ''}
ORDER BY x.sats DESC NULLS LAST, created_at DESC
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
break
default:
items = await models.$queryRaw(`
${SELECT}

View File

@ -2,7 +2,7 @@ import { gql } from 'apollo-server-micro'
export default gql`
extend type Query {
moreItems(sort: String!, cursor: String, userId: ID): Items
moreItems(sort: String!, cursor: String, userId: ID, within: String): Items
moreFlatComments(cursor: String, userId: ID): Comments
item(id: ID!): Item
userComments(userId: ID!): [Item!]

View File

@ -3,13 +3,13 @@ import Nav from 'react-bootstrap/Nav'
import Link from 'next/link'
import styles from './header.module.css'
import { useRouter } from 'next/router'
import { Button, Container, NavDropdown } from 'react-bootstrap'
import { Button, Container, NavDropdown, SplitButton, Dropdown } from 'react-bootstrap'
import Price from './price'
import { useMe } from './me'
import Head from 'next/head'
import { signOut, signIn, useSession } from 'next-auth/client'
import { useLightning } from './lightning'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { randInRange } from '../lib/rand'
function WalletSummary ({ me }) {
@ -31,6 +31,17 @@ export default function Header () {
const path = router.asPath.split('?')[0]
const me = useMe()
const [session, loading] = useSession()
const [sort, setSort] = useState('recent')
const [within, setWithin] = useState()
useEffect(() => {
setSort(localStorage.getItem('sort') || 'recent')
setWithin(localStorage.getItem('topWithin'))
}, [])
const otherSort = sort === 'recent' ? 'top' : 'recent'
const sortLink = `/${sort}${sort === 'top' && within ? `/${within}` : ''}`
const otherSortLink = `/${otherSort}${otherSort === 'top' && within ? `/${within}` : ''}`
const Corner = () => {
if (me) {
@ -76,6 +87,9 @@ export default function Header () {
<RefreshableLink href='/recent' passHref>
<NavDropdown.Item>recent</NavDropdown.Item>
</RefreshableLink>
<RefreshableLink href={`/top${within ? `/${within}` : ''}`} passHref>
<NavDropdown.Item>top</NavDropdown.Item>
</RefreshableLink>
{me
? (
<Link href='/post' passHref>
@ -131,10 +145,22 @@ export default function Header () {
<RefreshableLink href='/' passHref>
<Navbar.Brand className={`${styles.brand} d-block d-sm-none`}>SN</Navbar.Brand>
</RefreshableLink>
<Nav.Item className='d-md-flex d-none'>
<RefreshableLink href='/recent' passHref>
<Nav.Link className={styles.navLink}>recent</Nav.Link>
</RefreshableLink>
<Nav.Item className='d-md-flex d-none nav-dropdown-toggle'>
<SplitButton
title={
<RefreshableLink href={sortLink} passHref>
<Nav.Link className={styles.navLink}>{sort}</Nav.Link>
</RefreshableLink>
}
key={`/${sort}`}
id='recent-top-button'
variant='link'
className='p-0'
>
<Link href={otherSortLink} passHref>
<Dropdown.Item onClick={() => localStorage.setItem('sort', otherSort)}>{otherSort}</Dropdown.Item>
</Link>
</SplitButton>
</Nav.Item>
<Nav.Item className='d-md-flex d-none'>
{me

View File

@ -9,14 +9,14 @@ services:
ports:
- "5431:5432"
env_file:
- .env.sample
- ./.env.sample
app:
container_name: app
build: ./
depends_on:
- db
env_file:
- .env.sample
- ./.env.sample
ports:
- "3000:3000"
volumes:

View File

@ -28,8 +28,8 @@ export const ITEM_FIELDS = gql`
export const MORE_ITEMS = gql`
${ITEM_FIELDS}
query MoreItems($sort: String!, $cursor: String, $userId: ID) {
moreItems(sort: $sort, cursor: $cursor, userId: $userId) {
query MoreItems($sort: String!, $cursor: String, $userId: ID, $within: String) {
moreItems(sort: $sort, cursor: $cursor, userId: $userId, within: $within) {
cursor
items {
...ItemFields

View File

@ -26,7 +26,7 @@ export default function getApolloClient () {
Query: {
fields: {
moreItems: {
keyArgs: ['sort', 'userId'],
keyArgs: ['sort', 'userId', 'within'],
merge (existing, incoming) {
if (isFirstPage(incoming.cursor, existing)) {
return incoming

11683
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -55,7 +55,6 @@ export async function getServerSideProps ({ req, res, query: { id, error = null
}
function InviteHeader ({ invite }) {
console.log(invite.poor)
let Inner
if (invite.revoked) {
Inner = () => <div className='text-danger'>this invite link expired</div>

View File

@ -0,0 +1,94 @@
import Layout from '../../components/layout'
import Items from '../../components/items'
import { useRouter } from 'next/router'
import getSSRApolloClient from '../../api/ssrApollo'
import { MORE_ITEMS } from '../../fragments/items'
import { Nav, Navbar } from 'react-bootstrap'
import styles from '../../components/header.module.css'
import Link from 'next/link'
export async function getServerSideProps ({ req }) {
const client = await getSSRApolloClient(req)
const { data } = await client.query({
query: MORE_ITEMS,
variables: { sort: 'top', within: req.query?.within?.pop() }
})
let items, cursor
if (data) {
({ moreItems: { items, cursor } } = data)
}
return {
props: {
items,
cursor
}
}
}
export default function Index ({ items, cursor }) {
const router = useRouter()
const path = router.asPath.split('?')[0]
return (
<Layout>
<Navbar className={styles.navbar}>
<Nav
className={styles.navbarNav}
activeKey={path}
>
<Nav.Item>
<Link href='/top/day' passHref>
<Nav.Link
className={styles.navLink}
onClick={() => localStorage.setItem('topWithin', 'day')}>
day
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href='/top/week' passHref>
<Nav.Link
className={styles.navLink}
onClick={() => localStorage.setItem('topWithin', 'week')}>
week
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href='/top/month' passHref>
<Nav.Link
className={styles.navLink}
onClick={() => localStorage.setItem('topWithin', 'month')}>
month
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href='/top/year' passHref>
<Nav.Link
className={styles.navLink}
onClick={() => localStorage.setItem('topWithin', 'year')}>
year
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href='/top' passHref>
<Nav.Link
className={styles.navLink}
onClick={() => localStorage.removeItem('topWithin')}>
forever
</Nav.Link>
</Link>
</Nav.Item>
</Nav>
</Navbar>
<Items
items={items} cursor={cursor}
variables={{ sort: 'top', within: router.query?.within?.pop() }} rank key={router.query.key}
/>
</Layout>
)
}

View File

@ -129,6 +129,32 @@ footer {
font-weight: 500;
}
.nav-dropdown-toggle .dropdown-toggle::after {
display: inline-block;
}
.nav-dropdown-toggle .btn-link {
color: rgba(0, 0, 0, 0.5) !important;
}
.nav-dropdown-toggle .btn-link[aria-expanded="true"] {
color: rgba(0, 0, 0, 0.9) !important;
}
.nav-dropdown-toggle .dropdown .btn {
padding: 0;
}
.nav-dropdown-toggle .dropdown .dropdown-toggle {
padding-left: .75rem;
padding-right: .5rem;
}
.nav-dropdown-toggle .nav-link {
padding-right: 0 !important;
font-weight: 500;
}
.dropdown-toggle::after {
display: none;
}