ssr everything

This commit is contained in:
keyan 2021-09-30 10:46:58 -05:00
parent 4ba0bcd416
commit ca1a95094c
28 changed files with 3215 additions and 2840 deletions

View File

@ -1,9 +1,7 @@
import { PrismaClient } from '@prisma/client' import { PrismaClient } from '@prisma/client'
const prisma = global.prisma || new PrismaClient({ global.prisma ||= new PrismaClient({
log: ['warn', 'error'] log: ['warn', 'error']
}) })
global.prisma = prisma export default global.prisma
export default prisma

View File

@ -6,11 +6,10 @@ import resolvers from './resolvers'
import typeDefs from './typeDefs' import typeDefs from './typeDefs'
import models from './models' import models from './models'
export default async function serverSideClient (req) { export default async function getSSRApolloClient (req) {
const session = req && await getSession({ req }) const session = req && await getSession({ req })
return new ApolloClient({ return new ApolloClient({
ssrMode: true, ssrMode: true,
// Instead of "createHttpLink" use SchemaLink here
link: new SchemaLink({ link: new SchemaLink({
schema: mergeSchemas({ schema: mergeSchemas({
schemas: typeDefs, schemas: typeDefs,

View File

@ -1,22 +1,25 @@
import { useQuery } from '@apollo/client' import { useQuery } from '@apollo/client'
import Button from 'react-bootstrap/Button'
import { MORE_FLAT_COMMENTS } from '../fragments/comments' import { MORE_FLAT_COMMENTS } from '../fragments/comments'
import { useState } from 'react'
import Comment, { CommentSkeleton } from './comment' import Comment, { CommentSkeleton } from './comment'
import styles from './notifications.module.css' import styles from './notifications.module.css'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import MoreFooter from './more-footer'
export default function CommentsFlat ({ variables, ...props }) { export default function CommentsFlat ({ variables, comments, cursor, ...props }) {
const router = useRouter() const router = useRouter()
const { error, data, fetchMore } = useQuery(MORE_FLAT_COMMENTS, { const { data, fetchMore } = useQuery(MORE_FLAT_COMMENTS, {
variables, variables,
fetchPolicy: router.query.cache ? 'cache-first' : undefined fetchPolicy: router.query.cache ? 'cache-first' : undefined
}) })
if (error) return <div>Failed to load!</div>
if (!data) { if (!data && !comments) {
return <CommentsFlatSkeleton /> return <CommentsFlatSkeleton />
} }
const { moreFlatComments: { comments, cursor } } = data
if (data) {
({ moreFlatComments: { comments, cursor } } = data)
}
return ( return (
<> <>
{comments.map(item => ( {comments.map(item => (
@ -33,7 +36,7 @@ export default function CommentsFlat ({ variables, ...props }) {
<Comment item={item} {...props} /> <Comment item={item} {...props} />
</div> </div>
))} ))}
<MoreFooter cursor={cursor} fetchMore={fetchMore} /> <MoreFooter cursor={cursor} fetchMore={fetchMore} Skeleton={CommentsFlatSkeleton} />
</> </>
) )
} }
@ -48,37 +51,3 @@ function CommentsFlatSkeleton () {
</div> </div>
) )
} }
function MoreFooter ({ cursor, fetchMore }) {
const [loading, setLoading] = useState(false)
if (loading) {
return <div><CommentsFlatSkeleton /></div>
}
let Footer
if (cursor) {
Footer = () => (
<Button
variant='primary'
size='md'
onClick={async () => {
setLoading(true)
await fetchMore({
variables: {
cursor
}
})
setLoading(false)
}}
>more
</Button>
)
} else {
Footer = () => (
<div className='text-muted' style={{ fontFamily: 'lightning', fontSize: '2rem', opacity: '0.6' }}>GENISIS</div>
)
}
return <div className='d-flex justify-content-center mt-4 mb-2'><Footer /></div>
}

View File

@ -47,32 +47,6 @@ function ItemText ({ item }) {
} }
export default function ItemFull ({ item, bio, ...props }) { export default function ItemFull ({ item, bio, ...props }) {
const query = gql`
${ITEM_FIELDS}
${COMMENTS}
{
item(id: ${item.id}) {
...ItemFields
text
comments {
...CommentsRecursive
}
}
}`
const router = useRouter()
const { error, data } = useQuery(query, {
fetchPolicy: router.query.cache ? 'cache-first' : undefined
})
if (error) {
return <div>Failed to load!</div>
}
// XXX replace item with cache version
if (data) {
({ item } = data)
}
return ( return (
<> <>
{item.parentId {item.parentId

View File

@ -1,23 +1,25 @@
import { useQuery } from '@apollo/client' import { useQuery } from '@apollo/client'
import Button from 'react-bootstrap/Button'
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 { useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import MoreFooter from './more-footer'
export default function Items ({ variables, rank }) { export default function Items ({ variables, rank, items, cursor }) {
const router = useRouter() const router = useRouter()
const { error, data, fetchMore } = useQuery(MORE_ITEMS, { const { data, fetchMore } = useQuery(MORE_ITEMS, {
variables, variables,
fetchPolicy: router.query.cache ? 'cache-first' : undefined fetchPolicy: router.query.cache ? 'cache-first' : undefined
}) })
if (error) return <div>Failed to load!</div>
if (!data) { if (!data && !items) {
return <ItemsSkeleton rank={rank} /> return <ItemsSkeleton rank={rank} />
} }
const { moreItems: { items, cursor } } = data if (data) {
({ moreItems: { items, cursor } } = data)
}
return ( return (
<> <>
<div className={styles.grid}> <div className={styles.grid}>
@ -25,7 +27,10 @@ export default function Items ({ variables, rank }) {
<Item item={item} rank={rank && i + 1} key={item.id} /> <Item item={item} rank={rank && i + 1} key={item.id} />
))} ))}
</div> </div>
<MoreFooter cursor={cursor} fetchMore={fetchMore} offset={items.length} rank={rank} /> <MoreFooter
cursor={cursor} fetchMore={fetchMore}
Skeleton={() => <ItemsSkeleton rank={rank} startRank={items.length} />}
/>
</> </>
) )
} }
@ -41,37 +46,3 @@ function ItemsSkeleton ({ rank, startRank = 0 }) {
</div> </div>
) )
} }
function MoreFooter ({ cursor, fetchMore, rank, offset }) {
const [loading, setLoading] = useState(false)
if (loading) {
return <ItemsSkeleton rank={rank} startRank={offset} />
}
let Footer
if (cursor) {
Footer = () => (
<Button
variant='primary'
size='md'
onClick={async () => {
setLoading(true)
await fetchMore({
variables: {
cursor
}
})
setLoading(false)
}}
>more
</Button>
)
} else {
Footer = () => (
<div className='text-muted' style={{ fontFamily: 'lightning', fontSize: '2rem', opacity: '0.6' }}>GENISIS</div>
)
}
return <div className='d-flex justify-content-center mt-4 mb-2'><Footer /></div>
}

View File

@ -1,18 +0,0 @@
import { HeaderPreview } from './header'
import Head from 'next/head'
import Container from 'react-bootstrap/Container'
export default function LayoutPreview ({ children }) {
return (
<>
<Head>
<meta name='viewport' content='initial-scale=1.0, width=device-width' />
</Head>
<HeaderPreview />
<Container className='mt-1 px-sm-0'>
{children}
</Container>
</>
)
}

36
components/more-footer.js Normal file
View File

@ -0,0 +1,36 @@
import { Button } from 'react-bootstrap'
import { useState } from 'react'
export default function MoreFooter ({ cursor, fetchMore, Skeleton }) {
const [loading, setLoading] = useState(false)
if (loading) {
return <div><Skeleton /></div>
}
let Footer
if (cursor) {
Footer = () => (
<Button
variant='primary'
size='md'
onClick={async () => {
setLoading(true)
await fetchMore({
variables: {
cursor
}
})
setLoading(false)
}}
>more
</Button>
)
} else {
Footer = () => (
<div className='text-muted' style={{ fontFamily: 'lightning', fontSize: '2rem', opacity: '0.6' }}>GENISIS</div>
)
}
return <div className='d-flex justify-content-center mt-4 mb-2'><Footer /></div>
}

View File

@ -1,11 +1,10 @@
import { useQuery } from '@apollo/client' import { useQuery } from '@apollo/client'
import Button from 'react-bootstrap/Button'
import { useState } from 'react'
import Comment, { CommentSkeleton } from './comment' import Comment, { CommentSkeleton } from './comment'
import Item from './item' import Item from './item'
import { NOTIFICATIONS } from '../fragments/notifications' import { NOTIFICATIONS } from '../fragments/notifications'
import styles from './notifications.module.css' import styles from './notifications.module.css'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import MoreFooter from './more-footer'
function Notification ({ n }) { function Notification ({ n }) {
const router = useRouter() const router = useRouter()
@ -44,18 +43,16 @@ function Notification ({ n }) {
) )
} }
export default function Notifications ({ variables }) { export default function Notifications ({ notifications, cursor, lastChecked, variables }) {
const router = useRouter() const router = useRouter()
const { error, data, fetchMore } = useQuery(NOTIFICATIONS, { const { data, fetchMore } = useQuery(NOTIFICATIONS, {
variables, variables,
fetchPolicy: router.query.cache ? 'cache-first' : undefined fetchPolicy: router.query.cache ? 'cache-first' : undefined
}) })
if (error) return <div>Failed to load!</div>
if (!data) {
return <CommentsFlatSkeleton />
}
const { notifications: { notifications, cursor, lastChecked } } = data if (data) {
({ notifications: { notifications, cursor } } = data)
}
const [fresh, old] = const [fresh, old] =
notifications.reduce((result, n) => { notifications.reduce((result, n) => {
@ -75,7 +72,7 @@ export default function Notifications ({ variables }) {
{old.map((n, i) => ( {old.map((n, i) => (
<Notification n={n} key={i} /> <Notification n={n} key={i} />
))} ))}
<MoreFooter cursor={cursor} fetchMore={fetchMore} /> <MoreFooter cursor={cursor} fetchMore={fetchMore} Skeleton={CommentsFlatSkeleton} />
</> </>
) )
} }
@ -90,37 +87,3 @@ function CommentsFlatSkeleton () {
</div> </div>
) )
} }
function MoreFooter ({ cursor, fetchMore }) {
const [loading, setLoading] = useState(false)
if (loading) {
return <div><CommentsFlatSkeleton /></div>
}
let Footer
if (cursor) {
Footer = () => (
<Button
variant='primary'
size='md'
onClick={async () => {
setLoading(true)
await fetchMore({
variables: {
cursor
}
})
setLoading(false)
}}
>more
</Button>
)
} else {
Footer = () => (
<div className='text-muted' style={{ fontFamily: 'lightning', fontSize: '2rem', opacity: '0.6' }}>GENISIS</div>
)
}
return <div className='d-flex justify-content-center mt-4 mb-2'><Footer /></div>
}

View File

@ -42,7 +42,7 @@ export default function UserHeader ({ user }) {
name: 'name', name: 'name',
test: async name => { test: async name => {
if (!name || !name.length) return false if (!name || !name.length) return false
const { data } = await client.query({ query: NAME_QUERY, variables: { name } }) const { data } = await client.query({ query: NAME_QUERY, variables: { name }, fetchPolicy: 'network-only' })
return data.nameAvailable return data.nameAvailable
}, },
message: 'taken' message: 'taken'
@ -69,7 +69,10 @@ export default function UserHeader ({ user }) {
if (error) { if (error) {
throw new Error({ message: error.toString() }) throw new Error({ message: error.toString() })
} }
router.replace(`/${name}`) router.replace({
pathname: router.pathname,
query: { ...router.query, username: name }
})
client.writeFragment({ client.writeFragment({
id: `User:${user.id}`, id: `User:${user.id}`,

View File

@ -1,4 +1,5 @@
import { gql } from '@apollo/client' import { gql } from '@apollo/client'
import { COMMENTS } from './comments'
export const ITEM_FIELDS = gql` export const ITEM_FIELDS = gql`
fragment ItemFields on Item { fragment ItemFields on Item {
@ -36,20 +37,26 @@ export const MORE_ITEMS = gql`
} }
} ` } `
export const ITEMS_FEED = gql` export const ITEM_FULL = id => gql`
${ITEM_FIELDS} ${ITEM_FIELDS}
${COMMENTS}
{ {
items { item(id: ${id}) {
...ItemFields ...ItemFields
text
comments {
...CommentsRecursive
}
} }
}` }`
export const ITEMS_RECENT = gql` export const ITEM_WITH_COMMENTS = gql`
${ITEM_FIELDS} ${ITEM_FIELDS}
${COMMENTS}
{ fragment ItemWithComments on Item {
items: recent {
...ItemFields ...ItemFields
text
comments {
...CommentsRecursive
} }
}` }`

View File

@ -1,5 +1,5 @@
import { gql } from '@apollo/client' import { gql } from '@apollo/client'
import { ITEM_FIELDS } from './items' import { ITEM_FIELDS, ITEM_WITH_COMMENTS } from './items'
export const USER_FIELDS = gql` export const USER_FIELDS = gql`
${ITEM_FIELDS} ${ITEM_FIELDS}
@ -16,3 +16,15 @@ export const USER_FIELDS = gql`
text text
} }
}` }`
export const USER_FULL = name => gql`
${USER_FIELDS}
${ITEM_WITH_COMMENTS}
{
user(name: "${name}") {
...UserFields
bio {
...ItemWithComments
}
}
}`

View File

@ -13,13 +13,13 @@ function isFirstPage (cursor, existing) {
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 TODO test for off by one // or equal to a full page
return !existing || !existing.items || existing.items.length < LIMIT return !existing || !existing.items || existing.items.length < LIMIT
} }
} }
export default new ApolloClient({ export default function getApolloClient () {
uri: '/api/graphql', global.apolloClient ||= new ApolloClient({
link: additiveLink, link: additiveLink,
cache: new InMemoryCache({ cache: new InMemoryCache({
typePolicies: { typePolicies: {
@ -74,6 +74,13 @@ export default new ApolloClient({
watchQuery: { watchQuery: {
fetchPolicy: 'cache-and-network', fetchPolicy: 'cache-and-network',
nextFetchPolicy: 'cache-first' nextFetchPolicy: 'cache-first'
},
query: {
fetchPolicy: 'cache-and-network',
nextFetchPolicy: 'cache-first'
} }
} }
}) })
return global.apolloClient
}

5240
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
"start": "NODE_OPTIONS='--trace-warnings' next start -p $PORT" "start": "NODE_OPTIONS='--trace-warnings' next start -p $PORT"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.3.13", "@apollo/client": "^3.4.15",
"@prisma/client": "^2.23.0", "@prisma/client": "^2.23.0",
"apollo-server-micro": "^2.21.2", "apollo-server-micro": "^2.21.2",
"async-retry": "^1.3.1", "async-retry": "^1.3.1",
@ -20,20 +20,20 @@
"domino": "^2.1.6", "domino": "^2.1.6",
"formik": "^2.2.6", "formik": "^2.2.6",
"graphql": "^15.5.0", "graphql": "^15.5.0",
"ln-service": "^51.7.0", "ln-service": "^52.8.0",
"mdast-util-find-and-replace": "^1.1.1", "mdast-util-find-and-replace": "^1.1.1",
"next": "^10.2.3", "next": "^11.1.2",
"next-auth": "^3.13.3", "next-auth": "^3.13.3",
"next-plausible": "^2.0.0", "next-plausible": "^2.1.3",
"next-seo": "^4.24.0", "next-seo": "^4.24.0",
"page-metadata-parser": "^1.1.4", "page-metadata-parser": "^1.1.4",
"pageres": "^6.2.3", "pageres": "^6.2.3",
"prisma": "^2.25.0", "prisma": "^2.25.0",
"qrcode.react": "^1.0.1", "qrcode.react": "^1.0.1",
"react": "17.0.1", "react": "^17.0.1",
"react-bootstrap": "^1.5.2", "react-bootstrap": "^1.5.2",
"react-countdown": "^2.3.2", "react-countdown": "^2.3.2",
"react-dom": "17.0.1", "react-dom": "^17.0.2",
"react-markdown": "^6.0.2", "react-markdown": "^6.0.2",
"react-syntax-highlighter": "^15.4.3", "react-syntax-highlighter": "^15.4.3",
"react-textarea-autosize": "^8.3.3", "react-textarea-autosize": "^8.3.3",

View File

@ -1,45 +1,64 @@
import Layout from '../../components/layout' import Layout from '../../components/layout'
import { gql } from '@apollo/client' import { useQuery } from '@apollo/client'
import ApolloClient from '../../api/client'
import UserHeader from '../../components/user-header' import UserHeader from '../../components/user-header'
import CommentsFlat from '../../components/comments-flat' import CommentsFlat from '../../components/comments-flat'
import Seo from '../../components/seo' 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 }) { export async function getServerSideProps ({ req, params: { username } }) {
const { error, data: { user } } = await (await ApolloClient(req)).query({ const { notFound, props } = await headerProps({ req, params: { username } })
query:
gql`{ if (notFound) {
user(name: "${params.username}") { return {
id notFound
createdAt
name
nitems
ncomments
stacked
sats
} }
}` }
const { user } = props
const client = await getSSRApolloClient(req)
const { data } = await client.query({
query: MORE_FLAT_COMMENTS,
variables: { userId: user.id }
}) })
if (!user || error) { let comments, cursor
return { if (data) {
notFound: true ({ moreFlatComments: { comments, cursor } } = data)
}
} }
return { return {
props: { props: {
user ...props,
comments,
cursor
} }
} }
} }
export default function UserComments ({ user }) { 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 ( return (
<Layout noSeo> <Layout noSeo>
<Seo user={user} /> <Seo user={user} />
<UserHeader user={user} /> <UserHeader user={user} />
<CommentsFlat variables={{ userId: user.id }} includeParent noReply /> <CommentsFlat
comments={comments} cursor={cursor}
variables={{ userId: user.id }} includeParent noReply
/>
</Layout> </Layout>
) )
} }

View File

@ -1,34 +1,28 @@
import Layout from '../components/layout' import Layout from '../../components/layout'
import { gql, useMutation, useQuery } from '@apollo/client' import { gql, useMutation, useQuery } from '@apollo/client'
import ApolloClient from '../api/client' import UserHeader from '../../components/user-header'
import UserHeader from '../components/user-header' import Seo from '../../components/seo'
import Seo from '../components/seo'
import { Button } from 'react-bootstrap' import { Button } from 'react-bootstrap'
import styles from '../styles/user.module.css' import styles from '../../styles/user.module.css'
import { useState } from 'react' import { useState } from 'react'
import ItemFull from '../components/item-full' import ItemFull from '../../components/item-full'
import * as Yup from 'yup' import * as Yup from 'yup'
import { Form, MarkdownInput, SubmitButton } from '../components/form' import { Form, MarkdownInput, SubmitButton } from '../../components/form'
import ActionTooltip from '../components/action-tooltip' 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_FIELDS } from '../fragments/users' import { USER_FULL } from '../../fragments/users'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { ITEM_FIELDS } from '../fragments/items' import { ITEM_FIELDS } from '../../fragments/items'
import getSSRApolloClient from '../../api/ssrApollo'
export async function getServerSideProps ({ req, params }) { export async function getServerSideProps ({ req, params: { username } }) {
const { error, data: { user } } = await (await ApolloClient(req)).query({ const client = await getSSRApolloClient(req)
query: const { error, data } = await client.query({
gql` query: USER_FULL(username)
${USER_FIELDS}
{
user(name: "${params.username}") {
...UserFields
}
}`
}) })
if (!user || error) { if (error || !data?.user) {
return { return {
notFound: true notFound: true
} }
@ -36,7 +30,7 @@ export async function getServerSideProps ({ req, params }) {
return { return {
props: { props: {
user user: data.user
} }
} }
} }
@ -103,24 +97,12 @@ export default function User ({ 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 query = gql`
${USER_FIELDS}
{
user(name: "${user.name}") {
...UserFields
}
}`
const router = useRouter() const router = useRouter()
const { error, data } = useQuery(query, {
const { data } = useQuery(USER_FULL(user.name), {
fetchPolicy: router.query.cache ? 'cache-first' : undefined fetchPolicy: router.query.cache ? 'cache-first' : undefined
}) })
if (error) {
return <div>Failed to load!</div>
}
// XXX replace item with cache version
if (data) { if (data) {
({ user } = data) ({ user } = data)
} }

View File

@ -1,45 +1,64 @@
import Layout from '../../components/layout' import Layout from '../../components/layout'
import { gql } from '@apollo/client' import { useQuery } from '@apollo/client'
import ApolloClient from '../../api/client'
import UserHeader from '../../components/user-header' import UserHeader from '../../components/user-header'
import Seo from '../../components/seo' import Seo from '../../components/seo'
import Items from '../../components/items' 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 }) { export async function getServerSideProps ({ req, params: { username } }) {
const { error, data: { user } } = await (await ApolloClient(req)).query({ const { notFound, props } = await headerProps({ req, params: { username } })
query:
gql`{ if (notFound) {
user(name: "${params.username}") { return {
id notFound
createdAt
name
nitems
ncomments
stacked
sats
} }
}` }
const { user } = props
const client = await getSSRApolloClient(req)
const { data } = await client.query({
query: MORE_ITEMS,
variables: { sort: 'user', userId: user.id }
}) })
if (!user || error) { let items, cursor
return { if (data) {
notFound: true ({ moreItems: { items, cursor } } = data)
}
} }
return { return {
props: { props: {
user ...props,
items,
cursor
} }
} }
} }
export default function UserPosts ({ user }) { 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 ( return (
<Layout noSeo> <Layout noSeo>
<Seo user={user} /> <Seo user={user} />
<UserHeader user={user} /> <UserHeader user={user} />
<Items variables={{ sort: 'user', userId: user.id }} /> <Items
items={items} cursor={cursor}
variables={{ sort: 'user', userId: user.id }}
/>
</Layout> </Layout>
) )
} }

View File

@ -5,13 +5,14 @@ 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 apolloClient from '../lib/apollo'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useEffect } from 'react' import { useEffect } from 'react'
import { ItemActModal, ItemActProvider } from '../components/item-act' import { ItemActModal, ItemActProvider } from '../components/item-act'
import getApolloClient from '../lib/apollo'
function MyApp ({ Component, pageProps }) { function MyApp ({ Component, pageProps: { session, ...props } }) {
const router = useRouter() const router = useRouter()
useEffect(() => { useEffect(() => {
router.beforePopState(({ url, as, options }) => { router.beforePopState(({ url, as, options }) => {
// we need to tell the next page to use a cache-first fetch policy ... // we need to tell the next page to use a cache-first fetch policy ...
@ -25,15 +26,15 @@ function MyApp ({ Component, pageProps }) {
return ( return (
<PlausibleProvider domain='stacker.news' trackOutboundLinks> <PlausibleProvider domain='stacker.news' trackOutboundLinks>
<Provider session={pageProps.session}> <Provider session={session}>
<ApolloProvider client={apolloClient}> <ApolloProvider client={getApolloClient()}>
<MeProvider> <MeProvider>
<LightningProvider> <LightningProvider>
<FundErrorProvider> <FundErrorProvider>
<FundErrorModal /> <FundErrorModal />
<ItemActProvider> <ItemActProvider>
<ItemActModal /> <ItemActModal />
<Component {...pageProps} /> <Component {...props} />
</ItemActProvider> </ItemActProvider>
</FundErrorProvider> </FundErrorProvider>
</LightningProvider> </LightningProvider>

View File

@ -6,7 +6,7 @@ export default async function handler (req, res) {
res.setHeader('Content-Type', 'image/png') res.setHeader('Content-Type', 'image/png')
try { try {
const streams = await new Pageres({ crop: true }) const streams = await new Pageres({ crop: true })
.src(url, ['600x300']) .src(url, ['600x314'])
.run() .run()
res.status(200).end(streams[0]) res.status(200).end(streams[0])
} catch(e) { } catch(e) {

View File

@ -5,7 +5,7 @@ import lnd from '../../api/lnd'
import typeDefs from '../../api/typeDefs' import typeDefs from '../../api/typeDefs'
import { getSession } from 'next-auth/client' import { getSession } from 'next-auth/client'
const apolloServer = new ApolloServer({ global.apolloServer ||= new ApolloServer({
typeDefs, typeDefs,
resolvers, resolvers,
tracing: true, tracing: true,
@ -25,4 +25,4 @@ export const config = {
} }
} }
export default apolloServer.createHandler({ path: '/api/graphql' }) export default global.apolloServer.createHandler({ path: '/api/graphql' })

View File

@ -1,12 +1,37 @@
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 { MORE_ITEMS } from '../fragments/items'
export default function Index () { export async function getServerSideProps ({ req }) {
const client = await getSSRApolloClient(req)
const { data } = await client.query({
query: MORE_ITEMS,
variables: { sort: 'hot' }
})
let items, cursor
if (data) {
({ moreItems: { items, cursor } } = data)
}
return {
props: {
items,
cursor
}
}
}
export default function Index ({ items, cursor }) {
const router = useRouter() const router = useRouter()
return ( return (
<Layout> <Layout>
<Items variables={{ sort: 'hot' }} rank key={router.query.key} /> <Items
items={items} cursor={cursor}
variables={{ sort: 'hot' }} rank key={router.query.key}
/>
</Layout> </Layout>
) )
} }

View File

@ -1,48 +0,0 @@
import Layout from '../../components/layout'
import { ITEM_FIELDS } from '../../fragments/items'
import { gql } from '@apollo/client'
import Seo from '../../components/seo'
import ApolloClient from '../../api/client'
import ItemFull from '../../components/item-full'
// ssr the item without comments so that we can populate metatags
export async function getServerSideProps ({ req, params: { id } }) {
if (isNaN(id)) {
return {
notFound: true
}
}
const { error, data: { item } } = await (await ApolloClient(req)).query({
query:
gql`
${ITEM_FIELDS}
{
item(id: ${id}) {
...ItemFields
text
}
}`
})
if (!item || error) {
return {
notFound: true
}
}
return {
props: {
item
}
}
}
export default function AnItem ({ item }) {
return (
<Layout noSeo>
<Seo item={item} />
<ItemFull item={item} />
</Layout>
)
}

View File

@ -18,7 +18,7 @@ export async function getServerSideProps ({ req, params: { id } }) {
}` }`
}) })
if (!item || error) { if (error || !item) {
return { return {
notFound: true notFound: true
} }

49
pages/items/[id]/index.js Normal file
View File

@ -0,0 +1,49 @@
import Layout from '../../../components/layout'
import { ITEM_FULL } from '../../../fragments/items'
import Seo from '../../../components/seo'
import ItemFull from '../../../components/item-full'
import getSSRApolloClient from '../../../api/ssrApollo'
import { useQuery } from '@apollo/client'
import { useRouter } from 'next/router'
export async function getServerSideProps ({ req, params: { id } }) {
if (isNaN(id)) {
return {
notFound: true
}
}
const client = await getSSRApolloClient(req)
const { error, data } = await client.query({
query: ITEM_FULL(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) {
({ item } = data)
}
return (
<Layout noSeo>
<Seo item={item} />
<ItemFull item={item} />
</Layout>
)
}

View File

@ -1,12 +1,37 @@
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
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'
export default function NotificationPage () { export async function getServerSideProps ({ req }) {
const client = await getSSRApolloClient(req)
const { data } = await client.query({
query: NOTIFICATIONS
})
let 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() const router = useRouter()
return ( return (
<Layout> <Layout>
<Notifications key={router.query.key} /> <Notifications
notifications={notifications} cursor={cursor}
lastChecked={lastChecked} key={router.query.key}
/>
</Layout> </Layout>
) )
} }

View File

@ -6,6 +6,13 @@ 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,12 +1,37 @@
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 { MORE_ITEMS } from '../fragments/items'
export default function Index () { export async function getServerSideProps ({ req }) {
const client = await getSSRApolloClient(req)
const { data } = await client.query({
query: MORE_ITEMS,
variables: { sort: 'recent' }
})
let items, cursor
if (data) {
({ moreItems: { items, cursor } } = data)
}
return {
props: {
items,
cursor
}
}
}
export default function Index ({ items, cursor }) {
const router = useRouter() const router = useRouter()
return ( return (
<Layout> <Layout>
<Items variables={{ sort: 'recent' }} rank key={router.query.key} /> <Items
items={items} cursor={cursor}
variables={{ sort: 'recent' }} rank key={router.query.key}
/>
</Layout> </Layout>
) )
} }

View File

@ -12,6 +12,12 @@ import { useMe } from '../components/me'
import { useEffect } from 'react' import { useEffect } from 'react'
import { requestProvider } from 'webln' import { requestProvider } from 'webln'
export async function getServerSideProps () {
return {
props: {}
}
}
export default function Wallet () { export default function Wallet () {
return ( return (
<LayoutCenter> <LayoutCenter>