diff --git a/src/app/(posts)/post/[id]/components/header/post-buttons/index.tsx b/src/app/(posts)/post/[id]/components/header/post-buttons/index.tsx index d52b0fd9..bdf80fcf 100644 --- a/src/app/(posts)/post/[id]/components/header/post-buttons/index.tsx +++ b/src/app/(posts)/post/[id]/components/header/post-buttons/index.tsx @@ -59,7 +59,7 @@ export const PostButtons = ({ > Edit a Copy - {viewParentClick && ( + {parentId && ( diff --git a/src/app/(posts)/post/[id]/components/header/title/index.tsx b/src/app/(posts)/post/[id]/components/header/title/index.tsx index a6b91d3d..26f4e461 100644 --- a/src/app/(posts)/post/[id]/components/header/title/index.tsx +++ b/src/app/(posts)/post/[id]/components/header/title/index.tsx @@ -44,9 +44,11 @@ export const PostTitle = ({ )} {loading && ( - - - +
+ + + +
)} diff --git a/src/app/(posts)/post/[id]/components/post-page/index.tsx b/src/app/(posts)/post/[id]/components/post-page/index.tsx index ede83220..bd4bc0f9 100644 --- a/src/app/(posts)/post/[id]/components/post-page/index.tsx +++ b/src/app/(posts)/post/[id]/components/post-page/index.tsx @@ -6,6 +6,7 @@ import { useEffect, useState } from "react" import { useRouter } from "next/navigation" import PasswordModalPage from "./password-modal-wrapper" import { File, PostWithFilesAndAuthor } from "@lib/server/prisma" +import { useSession } from "next-auth/react" type Props = { post: string | PostWithFilesAndAuthor @@ -13,11 +14,16 @@ type Props = { isAuthor?: boolean } -const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => { +const PostPage = ({ post: initialPost, isProtected, isAuthor: isAuthorFromServer }: Props) => { + const { data: session } = useSession() const [post, setPost] = useState( typeof initialPost === "string" ? JSON.parse(initialPost) : initialPost ) + // We generate public and unlisted posts at build time, so we can't use + // the session to determine if the user is the author on the server. We need to check + // the post's authorId against the session's user id. + const isAuthor = isAuthorFromServer ? true : session?.user?.id === post?.authorId; const router = useRouter() useEffect(() => { diff --git a/src/app/(posts)/post/[id]/page.tsx b/src/app/(posts)/post/[id]/page.tsx index 10478e27..d20308f7 100644 --- a/src/app/(posts)/post/[id]/page.tsx +++ b/src/app/(posts)/post/[id]/page.tsx @@ -1,6 +1,6 @@ import PostPage from "./components/post-page" import { notFound, redirect } from "next/navigation" -import { getPostById, Post, PostWithFilesAndAuthor } from "@lib/server/prisma" +import { getAllPosts, getPostById, Post, PostWithFilesAndAuthor } from "@lib/server/prisma" import { getCurrentUser } from "@lib/server/session" import ScrollToTop from "@components/scroll-to-top" import { title } from "process" @@ -14,19 +14,21 @@ export type PostProps = { isProtected?: boolean } -// export async function generateStaticParams() { -// const posts = await getAllPosts({ -// where: { -// visibility: { -// equals: "public" -// } -// } -// }) +export async function generateStaticParams() { + const posts = await getAllPosts({ + where: { + visibility: { + equals: "public" + } + } + }) -// return posts.map((post) => ({ -// id: post.id -// })) -// } + return posts.map((post) => ({ + id: post.id + })) +} + +export const dynamic = 'error'; const fetchOptions = { withFiles: true, @@ -40,19 +42,16 @@ const getPost = async (id: string) => { return notFound() } + if (post.visibility === "public" || post.visibility === "unlisted") { + return { post } + } + const user = await getCurrentUser() const isAuthorOrAdmin = user?.id === post?.authorId || user?.role === "admin" - if (post.visibility === "public") { - return { post, isAuthor: isAuthorOrAdmin } - } if (post.visibility === "private" && !isAuthorOrAdmin) { - return notFound() - } - - if (post.visibility === "private" && !isAuthorOrAdmin) { - return notFound() + return redirect("/signin") } if (post.visibility === "protected" && !isAuthorOrAdmin) { @@ -64,8 +63,8 @@ const getPost = async (id: string) => { files: [], parentId: "", title: "", - createdAt: new Date("1970-01-01"), - expiresAt: new Date("1970-01-01"), + createdAt: "", + expiresAt: "", author: { displayName: "" }, @@ -112,7 +111,8 @@ const PostView = async ({ title={post.title} createdAt={post.createdAt.toString()} expiresAt={post.expiresAt?.toString()} - displayName={post.author?.displayName || ""} + // displayName is an optional param + displayName={post.author?.displayName || undefined} visibility={post.visibility} authorId={post.authorId} /> diff --git a/src/app/components/badges/badge.tsx b/src/app/components/badges/badge.tsx index 241de983..a24e0868 100644 --- a/src/app/components/badges/badge.tsx +++ b/src/app/components/badges/badge.tsx @@ -1,17 +1,22 @@ +import React from "react" import styles from "./badge.module.css" type BadgeProps = { type: "primary" | "secondary" | "error" | "warning" children: React.ReactNode } -const Badge = ({ type, children }: BadgeProps) => { - return ( -
-
- {children} +const Badge = React.forwardRef( + ({ type, children }: BadgeProps, ref) => { + return ( +
+
+ {children} +
-
- ) -} + ) + } +) + +Badge.displayName = "Badge" export default Badge diff --git a/src/app/components/post-list/index.tsx b/src/app/components/post-list/index.tsx index 2fabd677..4470e2d9 100644 --- a/src/app/components/post-list/index.tsx +++ b/src/app/components/post-list/index.tsx @@ -2,11 +2,7 @@ import styles from "./post-list.module.css" import ListItem from "./list-item" -import { - ChangeEvent, - useCallback, - useState -} from "react" +import { ChangeEvent, useCallback, useState } from "react" import Link from "@components/link" import type { PostWithFiles } from "@lib/server/prisma" import Input from "@components/input" @@ -60,31 +56,42 @@ const PostList = ({ [posts, hasMorePosts] ) - const onSearch = (query: string) => { - setSearching(true) - async function fetchPosts() { - const res = await fetch( - `/api/post/search?q=${encodeURIComponent(query)}&userId=${userId}`, - { - method: "GET", - headers: { - "Content-Type": "application/json" + // eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: address this + const onSearch = useCallback( + debounce((query: string) => { + if (!query) { + setPosts(initialPosts) + setSearching(false) + return + } + + setSearching(true) + async function fetchPosts() { + const res = await fetch( + `/api/post/search?q=${encodeURIComponent(query)}&userId=${userId}`, + { + method: "GET", + headers: { + "Content-Type": "application/json" + } } - } - ) - const json = await res.json() - setPosts(json) - setSearching(false) - } - fetchPosts() - } + ) + const json = await res.json() + setPosts(json) + setSearching(false) + } + fetchPosts() + }, 300), + [userId] + ) - const debouncedSearch = debounce(onSearch, 500) - - const handleSearchChange = (e: ChangeEvent) => { - setSearchValue(e.target.value) - debouncedSearch(e.target.value) - } + const onSearchChange = useCallback( + (e: ChangeEvent) => { + setSearchValue(e.target.value) + onSearch(e.target.value) + }, + [onSearch] + ) const deletePost = useCallback( (postId: string) => async () => { @@ -108,16 +115,18 @@ const PostList = ({ return (
- {!hideSearch &&
- -
} + {!hideSearch && ( +
+ +
+ )} {!posts &&

Failed to load.

} {searching && (
    @@ -125,7 +134,7 @@ const PostList = ({
)} - {posts?.length === 0 && posts && ( + {!searching && posts?.length === 0 && posts && (

No posts found. Create one{" "} @@ -134,7 +143,7 @@ const PostList = ({ .

)} - {posts?.length > 0 && ( + {!searching && posts?.length > 0 && (
    {posts.map((post) => { @@ -149,7 +158,7 @@ const PostList = ({
)} - {hasMorePosts && !setSearchValue && ( + {!searching && hasMorePosts && !setSearchValue && (