Add public post listing to home page
This commit is contained in:
parent
5918b13867
commit
7ef45c28f0
13 changed files with 115 additions and 70 deletions
|
@ -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) => {
|
||||||
|
|
|
@ -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} />}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default async function UserPage({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>{user?.displayName}'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} />
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
.textarea {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: "/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue