Add public post listing to home page

This commit is contained in:
Max Leiter 2022-12-04 14:49:18 -08:00
parent 5918b13867
commit 7ef45c28f0
13 changed files with 115 additions and 70 deletions

View file

@ -19,7 +19,17 @@ const FileDropdown = ({
loading?: boolean
}) => {
if (loading) {
return <Spinner />
return (
<Popover>
<Popover.Trigger className={buttonStyles.button}>
<div
style={{ minWidth: 125 }}
>
<Spinner />
</div>
</Popover.Trigger>
</Popover>
)
}
const items = files.map((file) => {

View file

@ -26,13 +26,15 @@ export const PostTitle = ({
}: TitleProps) => {
return (
<span className={styles.title}>
<h3>
<h1 style={{
fontSize: "1.175rem"
}}>
{title}{" "}
<span style={{ color: "var(--gray)" }}>
by{" "}
<Link href={`/author/${authorId}`}>{displayName || "anonymous"}</Link>
</span>
</h3>
</h1>
{!loading && (
<span className={styles.badges}>
{visibility && <VisibilityBadge visibility={visibility} />}

View file

@ -87,12 +87,14 @@ const Document = ({
height={"2rem"}
style={{ borderRadius: 0 }}
value={title || "Untitled"}
onChange={() => {}}
disabled
aria-label="Document title"
/>
</Link>
<div className={styles.descriptionContainer}>
<DownloadButtons rawLink={`/api/file/raw/${id}`} />
{/* Not /api/ because of rewrites defined in next.config.mjs */}
<DownloadButtons rawLink={`/file/raw/${id}`} />
<DocumentTabs
defaultTab={initialTab}
preview={preview}

View file

@ -100,7 +100,7 @@ const PostView = async ({
const stringifiedPost = JSON.stringify(post)
return (
<>
<div className={styles.root}>
<div className={styles.header}>
<PostButtons
parentId={post.parentId || undefined}
@ -133,7 +133,7 @@ const PostView = async ({
</span>
)}
<ScrollToTop />
</>
</div>
)
}

View file

@ -1,3 +1,10 @@
.root {
padding-bottom: 200px;
display: flex;
flex-direction: column;
gap: var(--gap);
}
@media screen and (max-width: 900px) {
.header {
flex-direction: column;

View file

@ -34,7 +34,7 @@ export default async function UserPage({
return (
<>
<h1>{user?.displayName}&apos;s public posts</h1>
<h1>Public posts by {user?.displayName || "Anonymous"}</h1>
<Suspense fallback={<PostList initialPosts={JSON.stringify({})} />}>
{/* @ts-ignore because TS async JSX support is iffy */}
<PostListWrapper posts={posts} userId={id} />

View file

@ -1,3 +0,0 @@
.textarea {
height: 100%;
}

View file

@ -1,40 +0,0 @@
import Image from "next/image"
import Card from "./card"
import DocumentTabs from "app/(posts)/components/tabs"
const Home = ({
introTitle,
introContent,
rendered
}: {
introTitle: string
introContent: string
rendered: string
}) => {
return (
<>
<div
style={{ display: "flex", flexDirection: "row", alignItems: "center" }}
>
<Image
src={"/assets/logo-optimized.svg"}
width={48}
height={48}
alt=""
priority
/>
<h1 style={{ marginLeft: "var(--gap)" }}>{introTitle}</h1>
</div>
<Card>
<DocumentTabs
defaultTab="preview"
isEditing={false}
content={introContent}
preview={rendered}
title={introTitle}
/>
</Card>
</>
)
}
export default Home

View file

@ -5,8 +5,6 @@ import ListItem from "./list-item"
import {
ChangeEvent,
useCallback,
useDeferredValue,
useEffect,
useState
} from "react"
import Link from "@components/link"
@ -21,12 +19,14 @@ type Props = {
initialPosts: string | PostWithFiles[]
morePosts?: boolean
userId?: string
hideSearch?: boolean
}
const PostList = ({
morePosts,
initialPosts: initialPostsMaybeJSON,
userId
userId,
hideSearch
}: Props) => {
const initialPosts =
typeof initialPostsMaybeJSON === "string"
@ -108,7 +108,7 @@ const PostList = ({
return (
<div className={styles.container}>
<div className={styles.searchContainer}>
{!hideSearch && <div className={styles.searchContainer}>
<Input
placeholder="Search..."
onChange={handleSearchChange}
@ -117,7 +117,7 @@ const PostList = ({
aria-label="Search"
value={search}
/>
</div>
</div>}
{!posts && <p style={{ color: "var(--warning)" }}>Failed to load.</p>}
{searching && (
<ul>

View file

@ -1,5 +1,9 @@
import Image from "next/image"
import Card from "@components/card"
import { getWelcomeContent } from "pages/api/welcome"
import Home from "./components/home"
import DocumentTabs from "./(posts)/components/tabs"
import { getAllPosts, Post } from "@lib/server/prisma"
import PostList from "@components/post-list"
const getWelcomeData = async () => {
const welcomeContent = await getWelcomeContent()
@ -8,12 +12,58 @@ const getWelcomeData = async () => {
export default async function Page() {
const { content, rendered, title } = await getWelcomeData()
const getPostsPromise = getAllPosts({
where: { visibility: "public" }
})
return (
<Home
rendered={rendered as string}
introContent={content}
introTitle={title}
<div
style={{ display: "flex", flexDirection: "column", gap: "var(--gap)" }}
>
<div
style={{ display: "flex", flexDirection: "row", alignItems: "center" }}
>
<Image
src={"/assets/logo-optimized.svg"}
width={48}
height={48}
alt=""
priority
/>
<h1 style={{ marginLeft: "var(--gap)" }}>{title}</h1>
</div>
<Card>
<DocumentTabs
defaultTab="preview"
isEditing={false}
content={content}
preview={rendered as string}
title={title}
/>
</Card>
<div>
<h2>Recent public posts</h2>
{/* @ts-ignore because of async RSC */}
<PublicPostList getPostsPromise={getPostsPromise} />
</div>
</div>
)
}
async function PublicPostList({
getPostsPromise
}: {
getPostsPromise: Promise<Post[]>
}) {
const posts = await getPostsPromise
return (
<PostList
userId={undefined}
morePosts={false}
initialPosts={JSON.stringify(posts)}
hideSearch
/>
)
}
export const revalidate = 60

View file

@ -4,10 +4,22 @@ import bundleAnalyzer from "@next/bundle-analyzer"
const nextConfig = {
reactStrictMode: true,
experimental: {
// outputStandalone: true,
// esmExternals: true,
appDir: true
},
output: "standalone",
async rewrites() {
return [
{
source: "/file/raw/:id",
destination: `/api/raw/:id`
},
{
source: "/home",
destination: "/"
}
]
}
}

View file

@ -40,7 +40,7 @@
"ts-jest": "^29.0.3"
},
"devDependencies": {
"@next/bundle-analyzer": "12.1.6",
"@next/bundle-analyzer": "13.0.7-canary.1",
"@types/bcrypt": "^5.0.0",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "17.0.23",

View file

@ -2,7 +2,7 @@ lockfileVersion: 5.4
specifiers:
'@next-auth/prisma-adapter': ^1.0.5
'@next/bundle-analyzer': 12.1.6
'@next/bundle-analyzer': 13.0.7-canary.1
'@next/eslint-plugin-next': 13.0.5-canary.3
'@prisma/client': ^4.7.1
'@radix-ui/react-dialog': ^1.0.2
@ -75,7 +75,7 @@ optionalDependencies:
sharp: 0.31.2
devDependencies:
'@next/bundle-analyzer': 12.1.6
'@next/bundle-analyzer': 13.0.7-canary.1
'@types/bcrypt': 5.0.0
'@types/lodash.debounce': 4.0.7
'@types/node': 17.0.23
@ -799,10 +799,10 @@ packages:
next-auth: 4.18.0_ihvxcpofhpc4k2aqfys2drrlkq
dev: false
/@next/bundle-analyzer/12.1.6:
resolution: {integrity: sha512-WLydwytAeHoC/neXsiIgK+a6Me12PuSpwopnsZgX5JFNwXQ9MlwPeMGS3aTZkYsv8QmSm0Ns9Yh9FkgLKYaUuQ==}
/@next/bundle-analyzer/13.0.7-canary.1:
resolution: {integrity: sha512-3CKGOK1Fp5mhCQ001h/GIj/ceZa4IfljWAkxRkX4uAOUAyyQ9MNNLNUX9H95/+oO7k2YS/Z71wXRk/xDaxM3Jw==}
dependencies:
webpack-bundle-analyzer: 4.3.0
webpack-bundle-analyzer: 4.7.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@ -2302,6 +2302,11 @@ packages:
engines: {node: '>= 6'}
dev: true
/commander/7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
dev: true
/commander/8.3.0:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
@ -7181,15 +7186,15 @@ packages:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false
/webpack-bundle-analyzer/4.3.0:
resolution: {integrity: sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA==}
/webpack-bundle-analyzer/4.7.0:
resolution: {integrity: sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==}
engines: {node: '>= 10.13.0'}
hasBin: true
dependencies:
acorn: 8.8.1
acorn-walk: 8.2.0
chalk: 4.1.2
commander: 6.2.1
commander: 7.2.0
gzip-size: 6.0.0
lodash: 4.17.21
opener: 1.5.2