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 loading?: boolean
}) => { }) => {
if (loading) { 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) => { const items = files.map((file) => {

View file

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

View file

@ -87,12 +87,14 @@ const Document = ({
height={"2rem"} height={"2rem"}
style={{ borderRadius: 0 }} style={{ borderRadius: 0 }}
value={title || "Untitled"} value={title || "Untitled"}
onChange={() => {}}
disabled disabled
aria-label="Document title" aria-label="Document title"
/> />
</Link> </Link>
<div className={styles.descriptionContainer}> <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 <DocumentTabs
defaultTab={initialTab} defaultTab={initialTab}
preview={preview} preview={preview}

View file

@ -100,7 +100,7 @@ const PostView = async ({
const stringifiedPost = JSON.stringify(post) const stringifiedPost = JSON.stringify(post)
return ( return (
<> <div className={styles.root}>
<div className={styles.header}> <div className={styles.header}>
<PostButtons <PostButtons
parentId={post.parentId || undefined} parentId={post.parentId || undefined}
@ -133,7 +133,7 @@ const PostView = async ({
</span> </span>
)} )}
<ScrollToTop /> <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) { @media screen and (max-width: 900px) {
.header { .header {
flex-direction: column; flex-direction: column;

View file

@ -34,7 +34,7 @@ export default async function UserPage({
return ( return (
<> <>
<h1>{user?.displayName}&apos;s public posts</h1> <h1>Public posts by {user?.displayName || "Anonymous"}</h1>
<Suspense fallback={<PostList initialPosts={JSON.stringify({})} />}> <Suspense fallback={<PostList initialPosts={JSON.stringify({})} />}>
{/* @ts-ignore because TS async JSX support is iffy */} {/* @ts-ignore because TS async JSX support is iffy */}
<PostListWrapper posts={posts} userId={id} /> <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 { import {
ChangeEvent, ChangeEvent,
useCallback, useCallback,
useDeferredValue,
useEffect,
useState useState
} from "react" } from "react"
import Link from "@components/link" import Link from "@components/link"
@ -21,12 +19,14 @@ type Props = {
initialPosts: string | PostWithFiles[] initialPosts: string | PostWithFiles[]
morePosts?: boolean morePosts?: boolean
userId?: string userId?: string
hideSearch?: boolean
} }
const PostList = ({ const PostList = ({
morePosts, morePosts,
initialPosts: initialPostsMaybeJSON, initialPosts: initialPostsMaybeJSON,
userId userId,
hideSearch
}: Props) => { }: Props) => {
const initialPosts = const initialPosts =
typeof initialPostsMaybeJSON === "string" typeof initialPostsMaybeJSON === "string"
@ -108,7 +108,7 @@ const PostList = ({
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.searchContainer}> {!hideSearch && <div className={styles.searchContainer}>
<Input <Input
placeholder="Search..." placeholder="Search..."
onChange={handleSearchChange} onChange={handleSearchChange}
@ -117,7 +117,7 @@ const PostList = ({
aria-label="Search" aria-label="Search"
value={search} value={search}
/> />
</div> </div>}
{!posts && <p style={{ color: "var(--warning)" }}>Failed to load.</p>} {!posts && <p style={{ color: "var(--warning)" }}>Failed to load.</p>}
{searching && ( {searching && (
<ul> <ul>

View file

@ -1,5 +1,9 @@
import Image from "next/image"
import Card from "@components/card"
import { getWelcomeContent } from "pages/api/welcome" 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 getWelcomeData = async () => {
const welcomeContent = await getWelcomeContent() const welcomeContent = await getWelcomeContent()
@ -8,12 +12,58 @@ const getWelcomeData = async () => {
export default async function Page() { export default async function Page() {
const { content, rendered, title } = await getWelcomeData() const { content, rendered, title } = await getWelcomeData()
const getPostsPromise = getAllPosts({
where: { visibility: "public" }
})
return ( return (
<Home <div
rendered={rendered as string} style={{ display: "flex", flexDirection: "column", gap: "var(--gap)" }}
introContent={content} >
introTitle={title} <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 = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
experimental: { experimental: {
// outputStandalone: true,
// esmExternals: true, // esmExternals: true,
appDir: 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" "ts-jest": "^29.0.3"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "12.1.6", "@next/bundle-analyzer": "13.0.7-canary.1",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/lodash.debounce": "^4.0.7", "@types/lodash.debounce": "^4.0.7",
"@types/node": "17.0.23", "@types/node": "17.0.23",

View file

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