smarter use of SSR and caching

This commit is contained in:
keyan 2021-10-26 15:49:37 -05:00
parent 06b0424b82
commit 07b9da353b
31 changed files with 295 additions and 467 deletions

View File

@ -34,19 +34,28 @@ export async function getItem (parent, { id }, { models }) {
export default { export default {
Query: { Query: {
moreItems: async (parent, { sort, cursor, userId, within }, { me, models }) => { moreItems: async (parent, { sort, cursor, name, within }, { me, models }) => {
const decodedCursor = decodeCursor(cursor) const decodedCursor = decodeCursor(cursor)
let items let items; let user; let interval = 'INTERVAL '
let interval = 'INTERVAL '
switch (sort) { switch (sort) {
case 'user': case 'user':
if (!name) {
throw new UserInputError('must supply name', { argumentName: 'name' })
}
user = await models.user.findUnique({ where: { name } })
if (!user) {
throw new UserInputError('no user has that name', { argumentName: 'name' })
}
items = await models.$queryRaw(` items = await models.$queryRaw(`
${SELECT} ${SELECT}
FROM "Item" FROM "Item"
WHERE "userId" = $1 AND "parentId" IS NULL AND created_at <= $2 WHERE "userId" = $1 AND "parentId" IS NULL AND created_at <= $2
ORDER BY created_at DESC ORDER BY created_at DESC
OFFSET $3 OFFSET $3
LIMIT ${LIMIT}`, Number(userId), decodedCursor.time, decodedCursor.offset) LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
break break
case 'hot': case 'hot':
// HACK we can speed hack the first hot page, by limiting our query to only // HACK we can speed hack the first hot page, by limiting our query to only
@ -67,7 +76,7 @@ export default {
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, new Date(new Date() - 7)) LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, new Date(new Date() - 7))
} }
if (decodedCursor.offset !== 0 || items.length < LIMIT) { if (decodedCursor.offset !== 0 || items?.length < LIMIT) {
items = await models.$queryRaw(` items = await models.$queryRaw(`
${SELECT} ${SELECT}
FROM "Item" FROM "Item"
@ -79,7 +88,7 @@ export default {
} }
break break
case 'top': case 'top':
switch (within) { switch (within?.pop()) {
case 'day': case 'day':
interval += "'1 day'" interval += "'1 day'"
break break
@ -119,11 +128,16 @@ export default {
items items
} }
}, },
moreFlatComments: async (parent, { cursor, userId }, { me, models }) => { moreFlatComments: async (parent, { cursor, name }, { me, models }) => {
const decodedCursor = decodeCursor(cursor) const decodedCursor = decodeCursor(cursor)
if (!userId) { if (!name) {
throw new UserInputError('must supply userId', { argumentName: 'userId' }) throw new UserInputError('must supply name', { argumentName: 'name' })
}
const user = await models.user.findUnique({ where: { name } })
if (!user) {
throw new UserInputError('no user has that name', { argumentName: 'name' })
} }
const comments = await models.$queryRaw(` const comments = await models.$queryRaw(`
@ -133,7 +147,7 @@ export default {
AND created_at <= $2 AND created_at <= $2
ORDER BY created_at DESC ORDER BY created_at DESC
OFFSET $3 OFFSET $3
LIMIT ${LIMIT}`, Number(userId), decodedCursor.time, decodedCursor.offset) LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
return { return {
cursor: comments.length === LIMIT ? nextCursorEncoded(decodedCursor) : null, cursor: comments.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
@ -141,13 +155,6 @@ export default {
} }
}, },
item: getItem, item: getItem,
userComments: async (parent, { userId }, { models }) => {
return await models.$queryRaw(`
${SELECT}
FROM "Item"
WHERE "userId" = $1 AND "parentId" IS NOT NULL
ORDER BY created_at DESC`, Number(userId))
},
pageTitle: async (parent, { url }, { models }) => { pageTitle: async (parent, { url }, { models }) => {
try { try {
const response = await fetch(ensureProtocol(url), { redirect: 'follow' }) const response = await fetch(ensureProtocol(url), { redirect: 'follow' })
@ -524,11 +531,12 @@ const LEFT_JOIN_SATS =
JOIN "ItemAct" ON i.id = "ItemAct"."itemId" JOIN "ItemAct" ON i.id = "ItemAct"."itemId"
GROUP BY i.id) x ON "Item".id = x.id` GROUP BY i.id) x ON "Item".id = x.id`
/* NOTE: because many items will have the same rank, we need to tie break with a unique field so pagination works */
function timedOrderBySats (num) { function timedOrderBySats (num) {
return `ORDER BY ((x.sats-1)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 1.5) + return `ORDER BY ((x.sats-1)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 1.5) +
(x.boost)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 5)) DESC NULLS LAST` (x.boost)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 5)) DESC NULLS LAST, "Item".id DESC`
} }
const ORDER_BY_SATS = const ORDER_BY_SATS =
`ORDER BY ((x.sats-1)/POWER(EXTRACT(EPOCH FROM ((NOW() AT TIME ZONE 'UTC') - "Item".created_at))/3600+2, 1.5) + `ORDER BY ((x.sats-1)/POWER(EXTRACT(EPOCH FROM ((NOW() AT TIME ZONE 'UTC') - "Item".created_at))/3600+2, 1.5) +
(x.boost)/POWER(EXTRACT(EPOCH FROM ((NOW() AT TIME ZONE 'UTC') - "Item".created_at))/3600+2, 5)) DESC NULLS LAST` (x.boost)/POWER(EXTRACT(EPOCH FROM ((NOW() AT TIME ZONE 'UTC') - "Item".created_at))/3600+2, 5)) DESC NULLS LAST, "Item".id DESC`

View File

@ -5,6 +5,7 @@ import { getSession } from 'next-auth/client'
import resolvers from './resolvers' import resolvers from './resolvers'
import typeDefs from './typeDefs' import typeDefs from './typeDefs'
import models from './models' import models from './models'
import { print } from 'graphql'
export default async function getSSRApolloClient (req) { export default async function getSSRApolloClient (req) {
const session = req && await getSession({ req }) const session = req && await getSession({ req })
@ -23,3 +24,29 @@ export default async function getSSRApolloClient (req) {
cache: new InMemoryCache() cache: new InMemoryCache()
}) })
} }
export function getGetServerSideProps (query, variables = null) {
return async function ({ req, params }) {
const client = await getSSRApolloClient(req)
const { error, data } = await client.query({
query,
variables: { ...params, ...variables }
})
if (error || !data) {
return {
notFound: true
}
}
return {
props: {
apollo: {
query: print(query),
variables: { ...params, ...variables }
},
data
}
}
}
}

View File

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

View File

@ -8,8 +8,7 @@ import MoreFooter from './more-footer'
export default function CommentsFlat ({ variables, comments, cursor, ...props }) { export default function CommentsFlat ({ variables, comments, cursor, ...props }) {
const router = useRouter() const router = useRouter()
const { data, fetchMore } = useQuery(MORE_FLAT_COMMENTS, { const { data, fetchMore } = useQuery(MORE_FLAT_COMMENTS, {
variables, variables
fetchPolicy: router.query.cache ? 'cache-first' : undefined
}) })
if (!data && !comments) { if (!data && !comments) {

View File

@ -1,5 +1,4 @@
import { useQuery } from '@apollo/client' import { useQuery } from '@apollo/client'
import { useRouter } from 'next/router'
import { useEffect } from 'react' import { useEffect } from 'react'
import Comment, { CommentSkeleton } from './comment' import Comment, { CommentSkeleton } from './comment'
@ -22,10 +21,7 @@ export function CommentsSkeleton () {
} }
export function CommentsQuery ({ query, ...props }) { export function CommentsQuery ({ query, ...props }) {
const router = useRouter() const { error, data } = useQuery(query)
const { error, data } = useQuery(query, {
fetchPolicy: router.query.cache ? 'cache-first' : undefined
})
if (error) return <div>Failed to load!</div> if (error) return <div>Failed to load!</div>
if (!data) { if (!data) {

View File

@ -16,16 +16,6 @@ function WalletSummary ({ me }) {
return `${me?.sats} \\ ${me?.stacked}` return `${me?.sats} \\ ${me?.stacked}`
} }
function RefreshableLink ({ href, children, ...props }) {
const router = useRouter()
const same = router.asPath === href
return (
<Link href={same ? `${href}?key=${Math.random()}` : href} as={href} {...props}>
{children}
</Link>
)
}
export default function Header () { export default function Header () {
const router = useRouter() const router = useRouter()
const path = router.asPath.split('?')[0] const path = router.asPath.split('?')[0]
@ -61,7 +51,7 @@ export default function Header () {
</div>} </div>}
</NavDropdown.Item> </NavDropdown.Item>
</Link> </Link>
<RefreshableLink href='/notifications' passHref> <Link href='/notifications' passHref>
<NavDropdown.Item> <NavDropdown.Item>
notifications notifications
{me?.hasNewNotes && {me?.hasNewNotes &&
@ -69,7 +59,7 @@ export default function Header () {
<span className='invisible'>{' '}</span> <span className='invisible'>{' '}</span>
</div>} </div>}
</NavDropdown.Item> </NavDropdown.Item>
</RefreshableLink> </Link>
<Link href='/wallet' passHref> <Link href='/wallet' passHref>
<NavDropdown.Item>wallet</NavDropdown.Item> <NavDropdown.Item>wallet</NavDropdown.Item>
</Link> </Link>
@ -84,12 +74,12 @@ export default function Header () {
</Link> </Link>
<div> <div>
<NavDropdown.Divider /> <NavDropdown.Divider />
<RefreshableLink href='/recent' passHref> <Link href='/recent' passHref>
<NavDropdown.Item>recent</NavDropdown.Item> <NavDropdown.Item>recent</NavDropdown.Item>
</RefreshableLink> </Link>
<RefreshableLink href={`/top${within ? `/${within}` : ''}`} passHref> <Link href={`/top${within ? `/${within}` : ''}`} passHref>
<NavDropdown.Item>top</NavDropdown.Item> <NavDropdown.Item>top</NavDropdown.Item>
</RefreshableLink> </Link>
{me {me
? ( ? (
<Link href='/post' passHref> <Link href='/post' passHref>
@ -139,18 +129,18 @@ export default function Header () {
className={styles.navbarNav} className={styles.navbarNav}
activeKey={path} activeKey={path}
> >
<RefreshableLink href='/' passHref> <Link href='/' passHref>
<Navbar.Brand className={`${styles.brand} d-none d-sm-block`}>STACKER NEWS</Navbar.Brand> <Navbar.Brand className={`${styles.brand} d-none d-sm-block`}>STACKER NEWS</Navbar.Brand>
</RefreshableLink> </Link>
<RefreshableLink href='/' passHref> <Link href='/' passHref>
<Navbar.Brand className={`${styles.brand} d-block d-sm-none`}>SN</Navbar.Brand> <Navbar.Brand className={`${styles.brand} d-block d-sm-none`}>SN</Navbar.Brand>
</RefreshableLink> </Link>
<Nav.Item className='d-md-flex d-none nav-dropdown-toggle'> <Nav.Item className='d-md-flex d-none nav-dropdown-toggle'>
<SplitButton <SplitButton
title={ title={
<RefreshableLink href={sortLink} passHref> <Link href={sortLink} passHref>
<Nav.Link className={styles.navLink}>{sort}</Nav.Link> <Nav.Link className={styles.navLink}>{sort}</Nav.Link>
</RefreshableLink> </Link>
} }
key={`/${sort}`} key={`/${sort}`}
id='recent-top-button' id='recent-top-button'

View File

@ -2,15 +2,10 @@ import { useQuery } from '@apollo/client'
import Item, { ItemSkeleton } from './item' import Item, { ItemSkeleton } from './item'
import styles from './items.module.css' import styles from './items.module.css'
import { MORE_ITEMS } from '../fragments/items' import { MORE_ITEMS } from '../fragments/items'
import { useRouter } from 'next/router'
import MoreFooter from './more-footer' import MoreFooter from './more-footer'
export default function Items ({ variables, rank, items, cursor }) { export default function Items ({ variables, rank, items, cursor }) {
const router = useRouter() const { data, fetchMore } = useQuery(MORE_ITEMS, { variables })
const { data, fetchMore } = useQuery(MORE_ITEMS, {
variables,
fetchPolicy: router.query.cache ? 'cache-first' : undefined
})
if (!data && !items) { if (!data && !items) {
return <ItemsSkeleton rank={rank} /> return <ItemsSkeleton rank={rank} />

View File

@ -44,11 +44,7 @@ function Notification ({ n }) {
} }
export default function Notifications ({ notifications, cursor, lastChecked, variables }) { export default function Notifications ({ notifications, cursor, lastChecked, variables }) {
const router = useRouter() const { data, fetchMore } = useQuery(NOTIFICATIONS, { variables })
const { data, fetchMore } = useQuery(NOTIFICATIONS, {
variables,
fetchPolicy: router.query.cache ? 'cache-first' : undefined
})
if (data) { if (data) {
({ notifications: { notifications, cursor } } = data) ({ notifications: { notifications, cursor } } = data)

View File

@ -71,7 +71,7 @@ export default function UserHeader ({ user }) {
} }
router.replace({ router.replace({
pathname: router.pathname, pathname: router.pathname,
query: { ...router.query, username: name } query: { ...router.query, name }
}) })
client.writeFragment({ client.writeFragment({

View File

@ -27,8 +27,8 @@ export const COMMENT_FIELDS = gql`
export const MORE_FLAT_COMMENTS = gql` export const MORE_FLAT_COMMENTS = gql`
${COMMENT_FIELDS} ${COMMENT_FIELDS}
query MoreFlatComments($cursor: String, $userId: ID) { query MoreFlatComments($cursor: String, $name: String!) {
moreFlatComments(cursor: $cursor, userId: $userId) { moreFlatComments(cursor: $cursor, name: $name) {
cursor cursor
comments { comments {
...CommentFields ...CommentFields

View File

@ -28,8 +28,8 @@ export const ITEM_FIELDS = gql`
export const MORE_ITEMS = gql` export const MORE_ITEMS = gql`
${ITEM_FIELDS} ${ITEM_FIELDS}
query MoreItems($sort: String!, $cursor: String, $userId: ID, $within: String) { query MoreItems($sort: String!, $cursor: String, $name: String, $within: [String]) {
moreItems(sort: $sort, cursor: $cursor, userId: $userId, within: $within) { moreItems(sort: $sort, cursor: $cursor, name: $name, within: $within) {
cursor cursor
items { items {
...ItemFields ...ItemFields
@ -37,11 +37,21 @@ export const MORE_ITEMS = gql`
} }
}` }`
export const ITEM_FULL = id => gql` export const ITEM = gql`
${ITEM_FIELDS}
query Item($id: ID!) {
item(id: $id) {
...ItemFields
text
}
}`
export const ITEM_FULL = gql`
${ITEM_FIELDS} ${ITEM_FIELDS}
${COMMENTS} ${COMMENTS}
{ query Item($id: ID!) {
item(id: ${id}) { item(id: $id) {
...ItemFields ...ItemFields
text text
comments { comments {

View File

@ -1,4 +1,5 @@
import { gql } from '@apollo/client' import { gql } from '@apollo/client'
import { COMMENT_FIELDS } from './comments'
import { ITEM_FIELDS, ITEM_WITH_COMMENTS } from './items' import { ITEM_FIELDS, ITEM_WITH_COMMENTS } from './items'
export const USER_FIELDS = gql` export const USER_FIELDS = gql`
@ -17,14 +18,52 @@ export const USER_FIELDS = gql`
} }
}` }`
export const USER_FULL = name => gql` export const USER_FULL = gql`
${USER_FIELDS} ${USER_FIELDS}
${ITEM_WITH_COMMENTS} ${ITEM_WITH_COMMENTS}
{ query User($name: String!) {
user(name: "${name}") { user(name: $name) {
...UserFields ...UserFields
bio { bio {
...ItemWithComments ...ItemWithComments
} }
} }
}` }`
export const USER_WITH_COMMENTS = gql`
${USER_FIELDS}
${ITEM_WITH_COMMENTS}
${COMMENT_FIELDS}
query UserWithComments($name: String!) {
user(name: $name) {
...UserFields
bio {
...ItemWithComments
}
}
moreFlatComments(name: $name) {
cursor
comments {
...CommentFields
}
}
}`
export const USER_WITH_POSTS = gql`
${USER_FIELDS}
${ITEM_WITH_COMMENTS}
${ITEM_FIELDS}
query UserWithPosts($name: String!, $sort: String!) {
user(name: $name) {
...UserFields
bio {
...ItemWithComments
}
}
moreItems(sort: $sort, name: $name) {
cursor
items {
...ItemFields
}
}
}`

25
fragments/wallet.js Normal file
View File

@ -0,0 +1,25 @@
import { gql } from '@apollo/client'
export const INVOICE = gql`
query Invoice($id: ID!) {
invoice(id: $id) {
id
bolt11
msatsReceived
cancelled
confirmedAt
expiresAt
}
}`
export const WITHDRAWL = gql`
query Withdrawl($id: ID!) {
withdrawl(id: $id) {
id
bolt11
satsPaid
satsFeePaying
satsFeePaid
status
}
}`

View File

@ -7,14 +7,14 @@ const additiveLink = from([
new HttpLink({ uri: '/api/graphql' }) new HttpLink({ uri: '/api/graphql' })
]) ])
function isFirstPage (cursor, existing) { function isFirstPage (cursor, existingThings) {
if (cursor) { if (cursor) {
const decursor = decodeCursor(cursor) const decursor = decodeCursor(cursor)
return decursor.offset === LIMIT return decursor.offset === LIMIT
} else { } else {
// we don't have anything cached, or our existing items are less than // we don't have anything cached, or our existing items are less than
// or equal to a full page // or equal to a full page
return !existing || !existing.items || existing.items.length < LIMIT return existingThings?.length < LIMIT
} }
} }
@ -26,9 +26,9 @@ export default function getApolloClient () {
Query: { Query: {
fields: { fields: {
moreItems: { moreItems: {
keyArgs: ['sort', 'userId', 'within'], keyArgs: ['sort', 'name', 'within'],
merge (existing, incoming) { merge (existing, incoming) {
if (isFirstPage(incoming.cursor, existing)) { if (isFirstPage(incoming.cursor, existing?.items)) {
return incoming return incoming
} }
@ -39,9 +39,9 @@ export default function getApolloClient () {
} }
}, },
moreFlatComments: { moreFlatComments: {
keyArgs: ['userId'], keyArgs: ['name'],
merge (existing, incoming) { merge (existing, incoming) {
if (isFirstPage(incoming.cursor, existing)) { if (isFirstPage(incoming.cursor, existing?.comments)) {
return incoming return incoming
} }
@ -54,7 +54,7 @@ export default function getApolloClient () {
notifications: { notifications: {
keyArgs: false, keyArgs: false,
merge (existing, incoming) { merge (existing, incoming) {
if (isFirstPage(incoming.cursor, existing)) { if (isFirstPage(incoming.cursor, existing?.notifications)) {
return incoming return incoming
} }
@ -72,11 +72,11 @@ export default function getApolloClient () {
defaultOptions: { defaultOptions: {
// cache-and-network allows us to refresh pages on navigation // cache-and-network allows us to refresh pages on navigation
watchQuery: { watchQuery: {
fetchPolicy: 'cache-and-network', fetchPolicy: 'cache-only',
nextFetchPolicy: 'cache-first' nextFetchPolicy: 'cache-first'
}, },
query: { query: {
fetchPolicy: 'cache-and-network', fetchPolicy: 'cache-only',
nextFetchPolicy: 'cache-first' nextFetchPolicy: 'cache-first'
} }
} }

30
pages/[name]/comments.js Normal file
View File

@ -0,0 +1,30 @@
import Layout from '../../components/layout'
import { useQuery } from '@apollo/client'
import UserHeader from '../../components/user-header'
import CommentsFlat from '../../components/comments-flat'
import Seo from '../../components/seo'
import { USER_WITH_COMMENTS } from '../../fragments/users'
import { getGetServerSideProps } from '../../api/ssrApollo'
export const getServerSideProps = getGetServerSideProps(USER_WITH_COMMENTS)
export default function UserComments (
{ data: { user, moreFlatComments: { comments, cursor } } }) {
const { data } = useQuery(
USER_WITH_COMMENTS, { variables: { name: user.name } })
if (data) {
({ user, moreFlatComments: { comments, cursor } } = data)
}
return (
<Layout noSeo>
<Seo user={user} />
<UserHeader user={user} />
<CommentsFlat
comments={comments} cursor={cursor}
variables={{ name: user.name }} includeParent noReply
/>
</Layout>
)
}

View File

@ -12,28 +12,10 @@ import ActionTooltip from '../../components/action-tooltip'
import TextareaAutosize from 'react-textarea-autosize' import TextareaAutosize from 'react-textarea-autosize'
import { useMe } from '../../components/me' import { useMe } from '../../components/me'
import { USER_FULL } from '../../fragments/users' import { USER_FULL } from '../../fragments/users'
import { useRouter } from 'next/router'
import { ITEM_FIELDS } from '../../fragments/items' import { ITEM_FIELDS } from '../../fragments/items'
import getSSRApolloClient from '../../api/ssrApollo' import { getGetServerSideProps } from '../../api/ssrApollo'
export async function getServerSideProps ({ req, params: { username } }) { export const getServerSideProps = getGetServerSideProps(USER_FULL)
const client = await getSSRApolloClient(req)
const { error, data } = await client.query({
query: USER_FULL(username)
})
if (error || !data?.user) {
return {
notFound: true
}
}
return {
props: {
user: data.user
}
}
}
const BioSchema = Yup.object({ const BioSchema = Yup.object({
bio: Yup.string().required('required').trim() bio: Yup.string().required('required').trim()
@ -93,15 +75,12 @@ export function BioForm ({ handleSuccess, bio }) {
) )
} }
export default function User ({ user }) { export default function User ({ data: { user } }) {
const [create, setCreate] = useState(false) const [create, setCreate] = useState(false)
const [edit, setEdit] = useState(false) const [edit, setEdit] = useState(false)
const me = useMe() const me = useMe()
const router = useRouter()
const { data } = useQuery(USER_FULL(user.name), { const { data } = useQuery(USER_FULL, { variables: { name: user.name } })
fetchPolicy: router.query.cache ? 'cache-first' : undefined
})
if (data) { if (data) {
({ user } = data) ({ user } = data)

29
pages/[name]/posts.js Normal file
View File

@ -0,0 +1,29 @@
import Layout from '../../components/layout'
import { useQuery } from '@apollo/client'
import UserHeader from '../../components/user-header'
import Seo from '../../components/seo'
import Items from '../../components/items'
import { USER_WITH_POSTS } from '../../fragments/users'
import { getGetServerSideProps } from '../../api/ssrApollo'
export const getServerSideProps = getGetServerSideProps(USER_WITH_POSTS, { sort: 'user' })
export default function UserPosts ({ data: { user, moreItems: { items, cursor } } }) {
const { data } = useQuery(USER_WITH_POSTS,
{ variables: { name: user.name, sort: 'user' } })
if (data) {
({ user, moreItems: { items, cursor } } = data)
}
return (
<Layout noSeo>
<Seo user={user} />
<UserHeader user={user} />
<Items
items={items} cursor={cursor}
variables={{ sort: 'user', name: user.name }}
/>
</Layout>
)
}

View File

@ -1,64 +0,0 @@
import Layout from '../../components/layout'
import { useQuery } from '@apollo/client'
import UserHeader from '../../components/user-header'
import CommentsFlat from '../../components/comments-flat'
import Seo from '../../components/seo'
import { USER_FULL } from '../../fragments/users'
import { useRouter } from 'next/router'
import { MORE_FLAT_COMMENTS } from '../../fragments/comments'
import { getServerSideProps as headerProps } from './index'
import getSSRApolloClient from '../../api/ssrApollo'
export async function getServerSideProps ({ req, params: { username } }) {
const { notFound, props } = await headerProps({ req, params: { username } })
if (notFound) {
return {
notFound
}
}
const { user } = props
const client = await getSSRApolloClient(req)
const { data } = await client.query({
query: MORE_FLAT_COMMENTS,
variables: { userId: user.id }
})
let comments, cursor
if (data) {
({ moreFlatComments: { comments, cursor } } = data)
}
return {
props: {
...props,
comments,
cursor
}
}
}
export default function UserComments ({ user, comments, cursor }) {
const router = useRouter()
const { data } = useQuery(
USER_FULL(user.name), {
fetchPolicy: router.query.cache ? 'cache-first' : undefined
})
if (data) {
({ user } = data)
}
return (
<Layout noSeo>
<Seo user={user} />
<UserHeader user={user} />
<CommentsFlat
comments={comments} cursor={cursor}
variables={{ userId: user.id }} includeParent noReply
/>
</Layout>
)
}

View File

@ -1,64 +0,0 @@
import Layout from '../../components/layout'
import { useQuery } from '@apollo/client'
import UserHeader from '../../components/user-header'
import Seo from '../../components/seo'
import Items from '../../components/items'
import { useRouter } from 'next/router'
import { USER_FULL } from '../../fragments/users'
import { getServerSideProps as headerProps } from './index'
import getSSRApolloClient from '../../api/ssrApollo'
import { MORE_ITEMS } from '../../fragments/items'
export async function getServerSideProps ({ req, params: { username } }) {
const { notFound, props } = await headerProps({ req, params: { username } })
if (notFound) {
return {
notFound
}
}
const { user } = props
const client = await getSSRApolloClient(req)
const { data } = await client.query({
query: MORE_ITEMS,
variables: { sort: 'user', userId: user.id }
})
let items, cursor
if (data) {
({ moreItems: { items, cursor } } = data)
}
return {
props: {
...props,
items,
cursor
}
}
}
export default function UserPosts ({ user, items, cursor }) {
const router = useRouter()
const { data } = useQuery(
USER_FULL(user.name), {
fetchPolicy: router.query.cache ? 'cache-first' : undefined
})
if (data) {
({ user } = data)
}
return (
<Layout noSeo>
<Seo user={user} />
<UserHeader user={user} />
<Items
items={items} cursor={cursor}
variables={{ sort: 'user', userId: user.id }}
/>
</Layout>
)
}

View File

@ -1,33 +1,34 @@
import '../styles/globals.scss' import '../styles/globals.scss'
import { ApolloProvider } from '@apollo/client' import { ApolloProvider, gql } from '@apollo/client'
import { Provider } from 'next-auth/client' import { Provider } from 'next-auth/client'
import { FundErrorModal, FundErrorProvider } from '../components/fund-error' import { FundErrorModal, FundErrorProvider } from '../components/fund-error'
import { MeProvider } from '../components/me' import { MeProvider } from '../components/me'
import PlausibleProvider from 'next-plausible' import PlausibleProvider from 'next-plausible'
import { LightningProvider } from '../components/lightning' import { LightningProvider } from '../components/lightning'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { ItemActModal, ItemActProvider } from '../components/item-act' import { ItemActModal, ItemActProvider } from '../components/item-act'
import getApolloClient from '../lib/apollo' import getApolloClient from '../lib/apollo'
function MyApp ({ Component, pageProps: { session, ...props } }) { function MyApp ({ Component, pageProps: { session, ...props } }) {
const router = useRouter() const client = getApolloClient()
/*
useEffect(() => { If we are on the client, we populate the apollo cache with the
router.beforePopState(({ url, as, options }) => { ssr data
// we need to tell the next page to use a cache-first fetch policy ... */
// so that scroll position can be maintained if (typeof window !== 'undefined') {
const fullurl = new URL(url, 'https://stacker.news') const { apollo, data } = props
fullurl.searchParams.set('cache', true) if (apollo) {
router.push(`${fullurl.pathname}${fullurl.search}`, as, options) client.writeQuery({
return false query: gql`${apollo.query}`,
data: data,
variables: apollo.variables
}) })
}, []) }
}
return ( return (
<PlausibleProvider domain='stacker.news' trackOutboundLinks> <PlausibleProvider domain='stacker.news' trackOutboundLinks>
<Provider session={session}> <Provider session={session}>
<ApolloProvider client={getApolloClient()}> <ApolloProvider client={client}>
<MeProvider> <MeProvider>
<LightningProvider> <LightningProvider>
<FundErrorProvider> <FundErrorProvider>

View File

@ -1,36 +1,17 @@
import Layout from '../components/layout' import Layout from '../components/layout'
import Items from '../components/items' import Items from '../components/items'
import { useRouter } from 'next/router' import { getGetServerSideProps } from '../api/ssrApollo'
import getSSRApolloClient from '../api/ssrApollo'
import { MORE_ITEMS } from '../fragments/items' import { MORE_ITEMS } from '../fragments/items'
export async function getServerSideProps ({ req }) { const variables = { sort: 'hot' }
const client = await getSSRApolloClient(req) export const getServerSideProps = getGetServerSideProps(MORE_ITEMS, variables)
const { data } = await client.query({
query: MORE_ITEMS,
variables: { sort: 'hot' }
})
let items, cursor export default function Index ({ data: { moreItems: { items, cursor } } }) {
if (data) {
({ moreItems: { items, cursor } } = data)
}
return {
props: {
items,
cursor
}
}
}
export default function Index ({ items, cursor }) {
const router = useRouter()
return ( return (
<Layout> <Layout>
<Items <Items
items={items} cursor={cursor} items={items} cursor={cursor}
variables={{ sort: 'hot' }} rank key={router.query.key} variables={variables} rank
/> />
</Layout> </Layout>
) )

View File

@ -148,7 +148,7 @@ export default function Invites () {
...InviteFields ...InviteFields
} }
} }
`) `, { fetchPolicy: 'cache-and-network' })
const [active, inactive] = data && data.invites const [active, inactive] = data && data.invites
? data.invites.reduce((result, invite) => { ? data.invites.reduce((result, invite) => {

View File

@ -1,38 +1,24 @@
import { useQuery } from '@apollo/client' import { useQuery } from '@apollo/client'
import gql from 'graphql-tag'
import { Invoice } from '../../components/invoice' import { Invoice } from '../../components/invoice'
import { LnQRSkeleton } from '../../components/lnqr' import { LnQRSkeleton } from '../../components/lnqr'
import LayoutCenter from '../../components/layout-center' import LayoutCenter from '../../components/layout-center'
import { useRouter } from 'next/router'
import { INVOICE } from '../../fragments/wallet'
export async function getServerSideProps ({ params: { id } }) { export default function FullInvoice () {
return {
props: {
id
}
}
}
export default function FullInvoice ({ id }) {
const query = gql`
{
invoice(id: ${id}) {
id
bolt11
msatsReceived
cancelled
confirmedAt
expiresAt
}
}`
return ( return (
<LayoutCenter> <LayoutCenter>
<LoadInvoice query={query} /> <LoadInvoice />
</LayoutCenter> </LayoutCenter>
) )
} }
function LoadInvoice ({ query }) { function LoadInvoice () {
const { loading, error, data } = useQuery(query, { pollInterval: 1000 }) const router = useRouter()
const { loading, error, data } = useQuery(INVOICE, {
pollInterval: 1000,
variables: { id: router.query.id }
})
if (error) return <div>error</div> if (error) return <div>error</div>
if (!data || loading) { if (!data || loading) {
return <LnQRSkeleton status='loading' /> return <LnQRSkeleton status='loading' />

View File

@ -1,38 +1,12 @@
import { ITEM_FIELDS } from '../../../fragments/items' import { ITEM } from '../../../fragments/items'
import { gql } from '@apollo/client' import { getGetServerSideProps } from '../../../api/ssrApollo'
import getSSRApolloClient from '../../../api/ssrApollo'
import { DiscussionForm } from '../../../components/discussion-form' import { DiscussionForm } from '../../../components/discussion-form'
import { LinkForm } from '../../../components/link-form' import { LinkForm } from '../../../components/link-form'
import LayoutCenter from '../../../components/layout-center' import LayoutCenter from '../../../components/layout-center'
export async function getServerSideProps ({ req, params: { id } }) { export const getServerSideProps = getGetServerSideProps(ITEM)
const client = await getSSRApolloClient(req)
const { error, data: { item } } = await client.query({
query:
gql`
${ITEM_FIELDS}
{
item(id: ${id}) {
...ItemFields
text
}
}`
})
if (error || !item) { export default function PostEdit ({ data: { item } }) {
return {
notFound: true
}
}
return {
props: {
item
}
}
}
export default function PostEdit ({ item }) {
const editThreshold = new Date(item.createdAt).getTime() + 10 * 60000 const editThreshold = new Date(item.createdAt).getTime() + 10 * 60000
return ( return (

View File

@ -2,39 +2,14 @@ import Layout from '../../../components/layout'
import { ITEM_FULL } from '../../../fragments/items' import { ITEM_FULL } from '../../../fragments/items'
import Seo from '../../../components/seo' import Seo from '../../../components/seo'
import ItemFull from '../../../components/item-full' import ItemFull from '../../../components/item-full'
import getSSRApolloClient from '../../../api/ssrApollo' import { getGetServerSideProps } from '../../../api/ssrApollo'
import { useQuery } from '@apollo/client' import { useQuery } from '@apollo/client'
import { useRouter } from 'next/router'
export async function getServerSideProps ({ req, params: { id } }) { export const getServerSideProps = getGetServerSideProps(ITEM_FULL)
if (isNaN(id)) {
return {
notFound: true
}
}
const client = await getSSRApolloClient(req) export default function AnItem ({ data: { item } }) {
const { error, data } = await client.query({ const { data } = useQuery(ITEM_FULL, {
query: ITEM_FULL(id) variables: { id: item.id }
})
if (error || !data?.item) {
return {
notFound: true
}
}
return {
props: {
item: data.item
}
}
}
export default function AnItem ({ item }) {
const router = useRouter()
const { data } = useQuery(ITEM_FULL(item.id), {
fetchPolicy: router.query.cache ? 'cache-first' : undefined
}) })
if (data) { if (data) {
({ item } = data) ({ item } = data)

View File

@ -1,36 +1,16 @@
import { useRouter } from 'next/router' import { getGetServerSideProps } from '../api/ssrApollo'
import getSSRApolloClient from '../api/ssrApollo'
import Layout from '../components/layout' import Layout from '../components/layout'
import Notifications from '../components/notifications' import Notifications from '../components/notifications'
import { NOTIFICATIONS } from '../fragments/notifications' import { NOTIFICATIONS } from '../fragments/notifications'
export async function getServerSideProps ({ req }) { export const getServerSideProps = getGetServerSideProps(NOTIFICATIONS)
const client = await getSSRApolloClient(req)
const { data } = await client.query({
query: NOTIFICATIONS
})
let notifications, cursor, lastChecked export default function NotificationPage ({ data: { notifications: { notifications, cursor, lastChecked } } }) {
if (data) {
({ notifications: { notifications, cursor, lastChecked } } = data)
}
return {
props: {
notifications,
cursor,
lastChecked
}
}
}
export default function NotificationPage ({ notifications, cursor, lastChecked }) {
const router = useRouter()
return ( return (
<Layout> <Layout>
<Notifications <Notifications
notifications={notifications} cursor={cursor} notifications={notifications} cursor={cursor}
lastChecked={lastChecked} key={router.query.key} lastChecked={lastChecked}
/> />
</Layout> </Layout>
) )

View File

@ -6,12 +6,6 @@ import { useMe } from '../components/me'
import { DiscussionForm } from '../components/discussion-form' import { DiscussionForm } from '../components/discussion-form'
import { LinkForm } from '../components/link-form' import { LinkForm } from '../components/link-form'
export async function getServerSideProps () {
return {
props: {}
}
}
export function PostForm () { export function PostForm () {
const router = useRouter() const router = useRouter()
const me = useMe() const me = useMe()

View File

@ -1,36 +1,17 @@
import Layout from '../components/layout' import Layout from '../components/layout'
import Items from '../components/items' import Items from '../components/items'
import { useRouter } from 'next/router' import { getGetServerSideProps } from '../api/ssrApollo'
import getSSRApolloClient from '../api/ssrApollo'
import { MORE_ITEMS } from '../fragments/items' import { MORE_ITEMS } from '../fragments/items'
export async function getServerSideProps ({ req }) { const variables = { sort: 'recent' }
const client = await getSSRApolloClient(req) export const getServerSideProps = getGetServerSideProps(MORE_ITEMS, { sort: 'recent' })
const { data } = await client.query({
query: MORE_ITEMS,
variables: { sort: 'recent' }
})
let items, cursor export default function Index ({ data: { moreItems: { items, cursor } } }) {
if (data) {
({ moreItems: { items, cursor } } = data)
}
return {
props: {
items,
cursor
}
}
}
export default function Index ({ items, cursor }) {
const router = useRouter()
return ( return (
<Layout> <Layout>
<Items <Items
items={items} cursor={cursor} items={items} cursor={cursor}
variables={{ sort: 'recent' }} rank key={router.query.key} variables={variables} rank
/> />
</Layout> </Layout>
) )

View File

@ -1,34 +1,15 @@
import Layout from '../../components/layout' 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 getSSRApolloClient from '../../api/ssrApollo' import { getGetServerSideProps } from '../../api/ssrApollo'
import { MORE_ITEMS } from '../../fragments/items' import { MORE_ITEMS } from '../../fragments/items'
import { Nav, Navbar } from 'react-bootstrap' import { Nav, Navbar } from 'react-bootstrap'
import styles from '../../components/header.module.css' import styles from '../../components/header.module.css'
import Link from 'next/link' import Link from 'next/link'
export async function getServerSideProps ({ req, params: { within } }) { export const getServerSideProps = getGetServerSideProps(MORE_ITEMS, { sort: 'top'})
const client = await getSSRApolloClient(req)
console.log('called')
const { data } = await client.query({
query: MORE_ITEMS,
variables: { sort: 'top', within: within?.pop() }
})
let items, cursor export default function Index ({ data: { moreItems: { items, cursor } } }) {
if (data) {
({ moreItems: { items, cursor } } = data)
}
return {
props: {
items,
cursor
}
}
}
export default function Index ({ items, cursor }) {
const router = useRouter() const router = useRouter()
const path = router.asPath.split('?')[0] const path = router.asPath.split('?')[0]
@ -88,7 +69,7 @@ export default function Index ({ items, cursor }) {
</Navbar> </Navbar>
<Items <Items
items={items} cursor={cursor} items={items} cursor={cursor}
variables={{ sort: 'top', within: router.query?.within?.pop() }} rank key={router.query.key} variables={{ sort: 'top', within: router.query?.within }} rank
/> />
</Layout> </Layout>
) )

View File

@ -13,12 +13,6 @@ import { useEffect, useState } from 'react'
import { requestProvider } from 'webln' import { requestProvider } from 'webln'
import { Alert } from 'react-bootstrap' import { Alert } from 'react-bootstrap'
export async function getServerSideProps () {
return {
props: {}
}
}
export default function Wallet () { export default function Wallet () {
return ( return (
<LayoutCenter> <LayoutCenter>

View File

@ -1,32 +1,15 @@
import { useQuery } from '@apollo/client' import { useQuery } from '@apollo/client'
import gql from 'graphql-tag'
import LayoutCenter from '../../components/layout-center' import LayoutCenter from '../../components/layout-center'
import { CopyInput, Input, InputSkeleton } from '../../components/form' import { CopyInput, Input, InputSkeleton } from '../../components/form'
import InputGroup from 'react-bootstrap/InputGroup' import InputGroup from 'react-bootstrap/InputGroup'
import InvoiceStatus from '../../components/invoice-status' import InvoiceStatus from '../../components/invoice-status'
import { useRouter } from 'next/router'
import { WITHDRAWL } from '../../fragments/wallet'
export async function getServerSideProps ({ params: { id } }) { export default function Withdrawl () {
return {
props: {
id
}
}
}
export default function Withdrawl ({ id }) {
const query = gql`
{
withdrawl(id: ${id}) {
bolt11
satsPaid
satsFeePaying
satsFeePaid
status
}
}`
return ( return (
<LayoutCenter> <LayoutCenter>
<LoadWithdrawl query={query} /> <LoadWithdrawl />
</LayoutCenter> </LayoutCenter>
) )
} }
@ -45,8 +28,12 @@ export function WithdrawlSkeleton ({ status }) {
) )
} }
function LoadWithdrawl ({ query }) { function LoadWithdrawl () {
const { loading, error, data } = useQuery(query, { pollInterval: 1000 }) const router = useRouter()
const { loading, error, data } = useQuery(WITHDRAWL, {
variables: { id: router.query.id },
pollInterval: 1000
})
if (error) return <div>error</div> if (error) return <div>error</div>
if (!data || loading) { if (!data || loading) {
return <WithdrawlSkeleton status='loading' /> return <WithdrawlSkeleton status='loading' />