Add basic /author/{id} page
This commit is contained in:
parent
56eefc8419
commit
7eeadbe065
7 changed files with 86 additions and 26 deletions
|
@ -1,8 +1,9 @@
|
||||||
import CreatedAgoBadge from '@components/badges/created-ago-badge'
|
import CreatedAgoBadge from "@components/badges/created-ago-badge"
|
||||||
import ExpirationBadge from '@components/badges/expiration-badge'
|
import ExpirationBadge from "@components/badges/expiration-badge"
|
||||||
import VisibilityBadge from '@components/badges/visibility-badge'
|
import VisibilityBadge from "@components/badges/visibility-badge"
|
||||||
import Skeleton from '@components/skeleton'
|
import Link from "@components/link"
|
||||||
import styles from './title.module.css'
|
import Skeleton from "@components/skeleton"
|
||||||
|
import styles from "./title.module.css"
|
||||||
|
|
||||||
type TitleProps = {
|
type TitleProps = {
|
||||||
title: string
|
title: string
|
||||||
|
@ -11,22 +12,25 @@ type TitleProps = {
|
||||||
visibility?: string
|
visibility?: string
|
||||||
createdAt?: Date
|
createdAt?: Date
|
||||||
expiresAt?: Date
|
expiresAt?: Date
|
||||||
|
authorId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PostTitle = ({
|
export const PostTitle = ({
|
||||||
title,
|
title,
|
||||||
displayName,
|
displayName,
|
||||||
visibility,
|
visibility,
|
||||||
createdAt,
|
createdAt,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
loading
|
loading,
|
||||||
|
authorId
|
||||||
}: TitleProps) => {
|
}: TitleProps) => {
|
||||||
return (
|
return (
|
||||||
<span className={styles.title}>
|
<span className={styles.title}>
|
||||||
<h3>
|
<h3>
|
||||||
{title}{" "}
|
{title}{" "}
|
||||||
<span style={{ color: "var(--gray)" }}>
|
<span style={{ color: "var(--gray)" }}>
|
||||||
by {displayName || "anonymous"}
|
by{" "}
|
||||||
|
<Link href={`/author/${authorId}`}>{displayName || "anonymous"}</Link>
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
{!loading && (
|
{!loading && (
|
||||||
|
|
|
@ -65,9 +65,10 @@ const getPost = async (id: string) => {
|
||||||
title: "",
|
title: "",
|
||||||
createdAt: new Date("1970-01-01"),
|
createdAt: new Date("1970-01-01"),
|
||||||
author: {
|
author: {
|
||||||
displayName: ""
|
displayName: "",
|
||||||
},
|
},
|
||||||
description: ""
|
description: "",
|
||||||
|
authorId: "",
|
||||||
},
|
},
|
||||||
isProtected: true,
|
isProtected: true,
|
||||||
isAuthor: isAuthorOrAdmin
|
isAuthor: isAuthorOrAdmin
|
||||||
|
@ -110,6 +111,7 @@ const PostView = async ({
|
||||||
createdAt={post.createdAt}
|
createdAt={post.createdAt}
|
||||||
displayName={post.author?.displayName || ""}
|
displayName={post.author?.displayName || ""}
|
||||||
visibility={post.visibility}
|
visibility={post.visibility}
|
||||||
|
authorId={post.authorId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{post.description && (
|
{post.description && (
|
||||||
|
|
38
client/app/author/[username]/page.tsx
Normal file
38
client/app/author/[username]/page.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import PostList from "@components/post-list"
|
||||||
|
import { getPostsByUser, getUserById } from "@lib/server/prisma"
|
||||||
|
import { Suspense } from "react"
|
||||||
|
|
||||||
|
async function PostListWrapper({
|
||||||
|
posts,
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
posts: ReturnType<typeof getPostsByUser>
|
||||||
|
userId: string
|
||||||
|
}) {
|
||||||
|
const data = (await posts).filter((post) => post.visibility === "public")
|
||||||
|
return <PostList morePosts={false} userId={userId} initialPosts={JSON.stringify(data)} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function UserPage({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: {
|
||||||
|
username: string
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
// TODO: the route should be user.name, not id
|
||||||
|
const id = params.username
|
||||||
|
const user = await getUserById(id)
|
||||||
|
|
||||||
|
const posts = getPostsByUser(id, true)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>{user?.displayName}'s public posts</h1>
|
||||||
|
<Suspense fallback={<PostList initialPosts={JSON.stringify({})} />}>
|
||||||
|
{/* @ts-ignore because TS async JSX support is iffy */}
|
||||||
|
<PostListWrapper posts={posts} userId={id} />
|
||||||
|
</Suspense>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import styles from "./post-list.module.css"
|
import styles from "./post-list.module.css"
|
||||||
import { ListItemSkeleton } from "./list-item-skeleton"
|
|
||||||
import ListItem from "./list-item"
|
import ListItem from "./list-item"
|
||||||
import { ChangeEvent, useCallback, useEffect, useState } from "react"
|
import { ChangeEvent, useCallback, useEffect, useState } from "react"
|
||||||
import useDebounce from "@lib/hooks/use-debounce"
|
import useDebounce from "@lib/hooks/use-debounce"
|
||||||
|
@ -10,10 +9,11 @@ import type { PostWithFiles } from "@lib/server/prisma"
|
||||||
import Input from "@components/input"
|
import Input from "@components/input"
|
||||||
import Button from "@components/button"
|
import Button from "@components/button"
|
||||||
import { useToasts } from "@components/toasts"
|
import { useToasts } from "@components/toasts"
|
||||||
|
import { ListItemSkeleton } from "./list-item-skeleton"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialPosts: string | PostWithFiles[]
|
initialPosts: string | PostWithFiles[]
|
||||||
morePosts: boolean
|
morePosts?: boolean
|
||||||
userId?: string
|
userId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,12 +120,12 @@ const PostList = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!posts && <p style={{ color: "var(--warning)" }}>Failed to load.</p>}
|
{!posts && <p style={{ color: "var(--warning)" }}>Failed to load.</p>}
|
||||||
{/* {!posts?.length && (
|
{!posts?.length && (
|
||||||
<ul>
|
<ul>
|
||||||
<ListItemSkeleton />
|
<ListItemSkeleton />
|
||||||
<ListItemSkeleton />
|
<ListItemSkeleton />
|
||||||
</ul>
|
</ul>
|
||||||
)} */}
|
)}
|
||||||
{posts?.length === 0 && posts && (
|
{posts?.length === 0 && posts && (
|
||||||
<p>
|
<p>
|
||||||
No posts found. Create one{" "}
|
No posts found. Create one{" "}
|
||||||
|
|
|
@ -218,10 +218,12 @@ export const searchPosts = async (
|
||||||
query: string,
|
query: string,
|
||||||
{
|
{
|
||||||
withFiles = false,
|
withFiles = false,
|
||||||
userId
|
userId,
|
||||||
|
publicOnly
|
||||||
}: {
|
}: {
|
||||||
withFiles?: boolean
|
withFiles?: boolean
|
||||||
userId?: User["id"]
|
userId?: User["id"]
|
||||||
|
publicOnly?: boolean
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<PostWithFiles[]> => {
|
): Promise<PostWithFiles[]> => {
|
||||||
const posts = await prisma.post.findMany({
|
const posts = await prisma.post.findMany({
|
||||||
|
@ -231,7 +233,8 @@ export const searchPosts = async (
|
||||||
title: {
|
title: {
|
||||||
search: query
|
search: query
|
||||||
},
|
},
|
||||||
authorId: userId
|
authorId: userId,
|
||||||
|
visibility: publicOnly ? "public" : undefined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: {
|
files: {
|
||||||
|
@ -241,7 +244,8 @@ export const searchPosts = async (
|
||||||
},
|
},
|
||||||
userId: userId
|
userId: userId
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
visibility: publicOnly ? "public" : undefined
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import { withMethods } from "@lib/api-middleware/with-methods"
|
import { withMethods } from "@lib/api-middleware/with-methods"
|
||||||
import { parseQueryParam } from "@lib/server/parse-query-param"
|
import { parseQueryParam } from "@lib/server/parse-query-param"
|
||||||
import { searchPosts } from "@lib/server/prisma"
|
import { PostWithFiles, searchPosts } from "@lib/server/prisma"
|
||||||
import { NextApiRequest, NextApiResponse } from "next"
|
import { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
import { getSession } from "next-auth/react"
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const { q, userId } = req.query
|
const { q, userId } = req.query
|
||||||
|
|
||||||
|
const session = await getSession()
|
||||||
|
|
||||||
const query = parseQueryParam(q)
|
const query = parseQueryParam(q)
|
||||||
if (!query) {
|
if (!query) {
|
||||||
res.status(400).json({ error: "Invalid query" })
|
res.status(400).json({ error: "Invalid query" })
|
||||||
|
@ -13,9 +16,18 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const posts = await searchPosts(query, {
|
let posts: PostWithFiles[]
|
||||||
userId: parseQueryParam(userId),
|
if (session?.user.id === userId || session?.user.role === "admin") {
|
||||||
})
|
posts = await searchPosts(query, {
|
||||||
|
userId: parseQueryParam(userId),
|
||||||
|
publicOnly: true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
posts = await searchPosts(query, {
|
||||||
|
userId: parseQueryParam(userId),
|
||||||
|
publicOnly: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
res.status(200).json(posts)
|
res.status(200).json(posts)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
Loading…
Reference in a new issue