finish implementing draft features in blog
This commit is contained in:
		| @@ -3,8 +3,15 @@ | ||||
| import { useState } from "react"; | ||||
| import { useRouter } from "next/navigation"; | ||||
| import { Summary } from "../types"; | ||||
| import { publishArticle, unpublishArticle } from "../action"; | ||||
|  | ||||
| export default function PostSummary({ metadata }: { metadata: Summary }) { | ||||
| export default function PostSummary({ | ||||
|   metadata, | ||||
|   loggedIn, | ||||
| }: { | ||||
|   metadata: Summary; | ||||
|   loggedIn: boolean; | ||||
| }) { | ||||
|   const [elementColor, setElementColor] = useState("#999"); | ||||
|   const router = useRouter(); | ||||
|  | ||||
| @@ -77,9 +84,62 @@ export default function PostSummary({ metadata }: { metadata: Summary }) { | ||||
|             gap: 4, | ||||
|           }} | ||||
|         > | ||||
|           <span>Published: </span> | ||||
|           {metadata.is_draft && ( | ||||
|             <> | ||||
|               <span>draft - </span> | ||||
|               <div | ||||
|                 style={{ | ||||
|                   cursor: "pointer", | ||||
|                   color: "#999", | ||||
|                   transition: "color 0.3s linear", | ||||
|                 }} | ||||
|                 onMouseOver={(e) => { | ||||
|                   e.currentTarget.style.color = "#eee"; | ||||
|                 }} | ||||
|                 onMouseLeave={(e) => { | ||||
|                   e.currentTarget.style.color = "#999"; | ||||
|                 }} | ||||
|                 onClick={async () => { | ||||
|                   await publishArticle(metadata.slug); | ||||
|                   router.refresh(); | ||||
|                 }} | ||||
|               > | ||||
|                 move to published | ||||
|               </div> | ||||
|             </> | ||||
|           )} | ||||
|           {!metadata.is_draft && ( | ||||
|             <> | ||||
|               <div> | ||||
|                 <div> | ||||
|                   <span>published: </span> | ||||
|                   <span>{metadata.publishedDate.toLocaleDateString()}</span> | ||||
|                 </div> | ||||
|                 {loggedIn && ( | ||||
|                   <div | ||||
|                     style={{ | ||||
|                       cursor: "pointer", | ||||
|                       color: "#999", | ||||
|                       transition: "color 0.3s linear", | ||||
|                     }} | ||||
|                     onMouseOver={(e) => { | ||||
|                       e.currentTarget.style.color = "#eee"; | ||||
|                     }} | ||||
|                     onMouseLeave={(e) => { | ||||
|                       e.currentTarget.style.color = "#999"; | ||||
|                     }} | ||||
|                     onClick={async () => { | ||||
|                       await unpublishArticle(metadata.slug); | ||||
|                       router.refresh(); | ||||
|                     }} | ||||
|                   > | ||||
|                     move to drafts | ||||
|                   </div> | ||||
|                 )} | ||||
|               </div> | ||||
|             </> | ||||
|           )} | ||||
|         </div> | ||||
|       </div> | ||||
|       <div | ||||
|         style={{ | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| "use server"; | ||||
|  | ||||
| import { getSummaries, getTags } from "../action"; | ||||
| import { titleFont } from "@/components/fonts"; | ||||
| import { notFound } from "next/navigation"; | ||||
| @@ -5,17 +7,7 @@ import React from "react"; | ||||
| import PostSummary from "./PostSummary"; | ||||
| import Pagination from "./Pagination"; | ||||
| import TagOverview from "./TagOverview"; | ||||
|  | ||||
| async function getPageNumber( | ||||
|   searchParams: Promise<{ page: string | undefined }> | ||||
| ) { | ||||
|   const { page } = await searchParams; | ||||
|   const result = page ? parseInt(page) : 1; | ||||
|   if (isNaN(result) || result < 1) { | ||||
|     notFound(); | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| import { isLoggedIn } from "@/components/auth"; | ||||
|  | ||||
| export default async function Blog({ | ||||
|   params, | ||||
| @@ -38,7 +30,8 @@ export default async function Blog({ | ||||
|   if (pageNumber > numberOfPages) { | ||||
|     notFound(); | ||||
|   } | ||||
|   const tags = await getTags(); | ||||
|   const loggedIn = await isLoggedIn(); | ||||
|   const tags = await getTags(loggedIn); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
| @@ -53,9 +46,23 @@ export default async function Blog({ | ||||
|         <span style={{ fontSize: 12 }}>...occasionally</span> | ||||
|       </div> | ||||
|       <TagOverview tags={tags} currentTag={currentTag} /> | ||||
|       {metadata.length > 0 && | ||||
|         metadata.map((m) => <PostSummary metadata={m} key={m.slug} />)} | ||||
|       {metadata | ||||
|         .filter((m) => loggedIn || !m.is_draft) | ||||
|         .map((m) => ( | ||||
|           <PostSummary metadata={m} key={m.slug} loggedIn={loggedIn} /> | ||||
|         ))} | ||||
|       <Pagination numberOfPages={numberOfPages} pageNumber={pageNumber} /> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| async function getPageNumber( | ||||
|   searchParams: Promise<{ page: string | undefined }> | ||||
| ) { | ||||
|   const { page } = await searchParams; | ||||
|   const result = page ? parseInt(page) : 1; | ||||
|   if (isNaN(result) || result < 1) { | ||||
|     notFound(); | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
|   | ||||
| @@ -4,11 +4,17 @@ import { notFound } from "next/navigation"; | ||||
| import { Post, Summary } from "./types"; | ||||
| import { PrismaClient } from "@prisma/client"; | ||||
|  | ||||
| export async function getTags(): Promise<string[]> { | ||||
| export async function getTags(loggedIn: boolean): Promise<string[]> { | ||||
|   const prisma = new PrismaClient(); | ||||
|   const tags = (await prisma.tag.findMany({ select: { name: true } })).map( | ||||
|     (tag) => tag.name | ||||
|   ); | ||||
|   const filter = loggedIn | ||||
|     ? undefined | ||||
|     : { posts: { some: { is_draft: false } } }; | ||||
|   const tags = ( | ||||
|     await prisma.tag.findMany({ | ||||
|       select: { name: true }, | ||||
|       where: filter, | ||||
|     }) | ||||
|   ).map((tag) => tag.name); | ||||
|   return tags; | ||||
| } | ||||
|  | ||||
| @@ -51,7 +57,7 @@ export async function getSummaries( | ||||
|       ...filter, | ||||
|       omit: { contentMarkdown: true, contentRendered: true }, | ||||
|       include: { tags: { select: { name: true } } }, | ||||
|       orderBy: { publishedDate: "desc" }, | ||||
|       orderBy: { publishedDate: "asc" }, | ||||
|       skip: PAGE_SIZE * (pageNumber - 1), | ||||
|       take: PAGE_SIZE, | ||||
|     }) | ||||
| @@ -60,3 +66,16 @@ export async function getSummaries( | ||||
|   }); | ||||
|   return { metadata: posts, numberOfPages: numberOfPages }; | ||||
| } | ||||
|  | ||||
| export async function publishArticle(slug: string) { | ||||
|   const prisma = new PrismaClient(); | ||||
|   await prisma.post.update({ | ||||
|     where: { slug }, | ||||
|     data: { is_draft: false, publishedDate: new Date() }, | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export async function unpublishArticle(slug: string) { | ||||
|   const prisma = new PrismaClient(); | ||||
|   await prisma.post.update({ where: { slug }, data: { is_draft: true } }); | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  | ||||
| import { PrismaClient } from "@prisma/client"; | ||||
| import argon2 from "argon2"; | ||||
| import { setSession } from "../write/auth"; | ||||
| import { setSession } from "@/components/auth"; | ||||
| import { redirect, RedirectType } from "next/navigation"; | ||||
|  | ||||
| export async function handleLogin(data: FormData) { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| "use server"; | ||||
|  | ||||
| import { redirect } from "next/navigation"; | ||||
| import { isLoggedIn } from "../write/auth"; | ||||
| import { isLoggedIn } from "@/components/auth"; | ||||
| import FormComponent from "./Form"; | ||||
|  | ||||
| export default async function Login() { | ||||
|   | ||||
| @@ -3,8 +3,15 @@ | ||||
| import { useRouter } from "next/navigation"; | ||||
| import * as Types from "../../types"; | ||||
| import "highlight.js/styles/github-dark.css"; | ||||
| import { publishArticle, unpublishArticle } from "../../action"; | ||||
|  | ||||
| export default function PostDisplay({ post }: { post: Types.Post }) { | ||||
| export default function PostDisplay({ | ||||
|   post, | ||||
|   loggedIn, | ||||
| }: { | ||||
|   post: Types.Post; | ||||
|   loggedIn: boolean; | ||||
| }) { | ||||
|   const router = useRouter(); | ||||
|   return ( | ||||
|     <> | ||||
| @@ -60,9 +67,62 @@ export default function PostDisplay({ post }: { post: Types.Post }) { | ||||
|               gap: 4, | ||||
|             }} | ||||
|           > | ||||
|             <span>Published: </span> | ||||
|             {post.is_draft && ( | ||||
|               <> | ||||
|                 <span>draft - </span> | ||||
|                 <div | ||||
|                   style={{ | ||||
|                     cursor: "pointer", | ||||
|                     color: "#999", | ||||
|                     transition: "color 0.3s linear", | ||||
|                   }} | ||||
|                   onMouseOver={(e) => { | ||||
|                     e.currentTarget.style.color = "#eee"; | ||||
|                   }} | ||||
|                   onMouseLeave={(e) => { | ||||
|                     e.currentTarget.style.color = "#999"; | ||||
|                   }} | ||||
|                   onClick={async () => { | ||||
|                     await publishArticle(post.slug); | ||||
|                     router.refresh(); | ||||
|                   }} | ||||
|                 > | ||||
|                   move to published | ||||
|                 </div> | ||||
|               </> | ||||
|             )} | ||||
|             {!post.is_draft && ( | ||||
|               <> | ||||
|                 <div> | ||||
|                   <div> | ||||
|                     <span>published: </span> | ||||
|                     <span>{post.publishedDate.toLocaleDateString()}</span> | ||||
|                   </div> | ||||
|                   {loggedIn && ( | ||||
|                     <div | ||||
|                       style={{ | ||||
|                         cursor: "pointer", | ||||
|                         color: "#999", | ||||
|                         transition: "color 0.3s linear", | ||||
|                       }} | ||||
|                       onMouseOver={(e) => { | ||||
|                         e.currentTarget.style.color = "#eee"; | ||||
|                       }} | ||||
|                       onMouseLeave={(e) => { | ||||
|                         e.currentTarget.style.color = "#999"; | ||||
|                       }} | ||||
|                       onClick={async () => { | ||||
|                         await unpublishArticle(post.slug); | ||||
|                         router.refresh(); | ||||
|                       }} | ||||
|                     > | ||||
|                       move to drafts | ||||
|                     </div> | ||||
|                   )} | ||||
|                 </div> | ||||
|               </> | ||||
|             )} | ||||
|           </div> | ||||
|         </div> | ||||
|         <div | ||||
|           style={{ | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { notFound } from "next/navigation"; | ||||
| import { getPost } from "../../action"; | ||||
| import PostDisplay from "./PostDisplay"; | ||||
| import { isLoggedIn } from "@/components/auth"; | ||||
|  | ||||
| export default async function Post({ | ||||
|   params, | ||||
| @@ -12,9 +13,10 @@ export default async function Post({ | ||||
|   if (!post) { | ||||
|     notFound(); | ||||
|   } | ||||
|   const loggedIn = await isLoggedIn(); | ||||
|   return ( | ||||
|     <> | ||||
|       <PostDisplay post={post} /> | ||||
|       <PostDisplay post={post} loggedIn={loggedIn} /> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import { notFound, redirect } from "next/navigation"; | ||||
| import { getPost } from "../../action"; | ||||
| import { Post } from "../../types"; | ||||
| import Write from "../Write"; | ||||
| import { isLoggedIn } from "../auth"; | ||||
| import { isLoggedIn } from "@/components/auth"; | ||||
|  | ||||
| export default async function WritePage({ | ||||
|   params, | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| /* eslint-disable react/no-unescaped-entities */ | ||||
|  | ||||
| "use client"; | ||||
|  | ||||
| import SubmitContact from "@/components/contact"; | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| /* eslint-disable react/no-unescaped-entities */ | ||||
|  | ||||
| "use client"; | ||||
|  | ||||
| import { SiGitea } from "@icons-pack/react-simple-icons"; | ||||
|   | ||||
| @@ -28,17 +28,16 @@ export async function decrypt( | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export async function isLoggedIn() { | ||||
| export async function isLoggedIn(): Promise<boolean> { | ||||
|   const cookieStore = (await cookies()).get("session")?.value; | ||||
|   const session = await decrypt(cookieStore); | ||||
|   if (session != null && session.admin) { | ||||
|     setSession(); | ||||
|     return true; | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| export async function setSession() { | ||||
| export async function setSession(): Promise<void> { | ||||
|   const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); | ||||
|   (await cookies()).set("session", await encrypt({ admin: true }), { | ||||
|     httpOnly: true, | ||||
		Reference in New Issue
	
	Block a user