mirror of
				https://github.com/DerTyp7/explainegy-nextjs.git
				synced 2025-10-29 21:02:13 +01:00 
			
		
		
		
	asd
This commit is contained in:
		| @@ -1,33 +1,30 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import React from "react"; | ||||
| 
 | ||||
| import { useState, useRef, useEffect } from "react"; | ||||
| import styles from "../../../../styles/modules/AdminArticlesCreate.module.scss"; | ||||
| import { PostArticle } from "../../../../types/postData"; | ||||
| import Markdown from "../../../Markdown"; | ||||
| import { Article, Category, Prisma } from "@prisma/client"; | ||||
| import "../../../../styles/inputs.scss"; | ||||
| import "../../../../styles/buttons.scss"; | ||||
| import Select, { GroupBase, OptionsOrGroups } from "react-select"; | ||||
| import { apiUrl } from "../../../global"; | ||||
| import urlJoin from "url-join"; | ||||
| import { formatTextToUrlName } from "../../../../utils"; | ||||
| import { isValidText } from "../../../../validators"; | ||||
| import styles from "../../../../../styles/modules/ArticleEditor.module.scss"; | ||||
| import { Prisma } from "@prisma/client"; | ||||
| import "../../../../../styles/inputs.scss"; | ||||
| import "../../../../../styles/buttons.scss"; | ||||
| import Select from "react-select"; | ||||
| import { useRouter } from "next/navigation"; | ||||
| import ContentTable from "../../../articles/[categoryName]/[articleName]/ContentTable"; | ||||
| import { IContentTableEntry } from "../../../../types/contentTable"; | ||||
| import urlJoin from "url-join"; | ||||
| import { IContentTableEntry } from "../../../../../types/contentTable"; | ||||
| import { CreateArticle, UpdateArticle } from "../../../../../types/api"; | ||||
| import { formatTextToUrlName } from "../../../../../utils"; | ||||
| import { isValidText } from "../../../../../validators"; | ||||
| import { apiUrl } from "../../../../global"; | ||||
| import Markdown from "../../../../Markdown"; | ||||
| 
 | ||||
| type ArticleWithCategory = Prisma.ArticleGetPayload<{ include: { category: true } }>; | ||||
| 
 | ||||
| export default function AdminArticlesCreate() { | ||||
| export default function ArticleEditor({ params }: { params: { articleId: string } }) { | ||||
|   const router = useRouter(); | ||||
| 
 | ||||
|   const [title, setTitle] = useState<string>(""); | ||||
|   const [selectCategoriesOptions, setSelectCategoriesOptions] = useState<any>([]); | ||||
|   const [introduction, setIntroduction] = useState<string>(""); | ||||
|   const [markdown, setMarkdown] = useState<string>(""); | ||||
|   const [contentTable, setContentTable] = useState<IContentTableEntry[]>([]); | ||||
|   const [contentTable, setContentTable] = useState<any>([]); | ||||
| 
 | ||||
|   const titleRef = useRef<HTMLInputElement>(null); | ||||
|   const categorySelectRef = useRef(null); | ||||
| @@ -52,40 +49,102 @@ export default function AdminArticlesCreate() { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function removeEntry(index: number) { | ||||
|     let newArray = [...contentTable]; | ||||
|     newArray.splice(index, 1); | ||||
|     setContentTable(newArray); | ||||
|   } | ||||
| 
 | ||||
|   function handleFormChange() { | ||||
|     setMarkdown(markdownTextAreaRef.current.value); | ||||
|     setTitle(titleRef.current.value); | ||||
|     setIntroduction(introductionRef.current.value); | ||||
|   } | ||||
| 
 | ||||
|   async function postData() { | ||||
|     const formData: PostArticle = { | ||||
|   // Create or update article
 | ||||
|   async function handleResponse(res: Response) { | ||||
|     const json = await res.json(); | ||||
|     errorTextRef.current.innerText = json.error ?? ""; | ||||
|     if (json.success) { | ||||
|       const newArticle: ArticleWithCategory = json.data; | ||||
|       router.push(urlJoin(`/articles/`, newArticle.category.name, newArticle.name)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async function updateArticle() { | ||||
|     console.log("Update article"); | ||||
|     const payload: UpdateArticle = { | ||||
|       id: params.articleId, | ||||
|       title: titleRef.current.value, | ||||
|       introduction: introductionRef.current.value, | ||||
|       markdown: markdown, | ||||
|       categoryId: Number(categorySelectRef?.current?.getValue()[0]?.value), | ||||
|       contentTable: contentTable, | ||||
|     }; | ||||
|     console.log(formData); | ||||
|     const result = await fetch("/api/articles/create", { | ||||
|     console.log(payload); | ||||
| 
 | ||||
|     await fetch("/api/articles/", { | ||||
|       method: "PUT", | ||||
|       headers: { | ||||
|         Accept: "application/json", | ||||
|         "Content-Type": "application/json", | ||||
|       }, | ||||
|       cache: "no-cache", | ||||
|       body: JSON.stringify(payload), | ||||
|     }) | ||||
|       .then(handleResponse) | ||||
|       .catch(console.error); | ||||
|   } | ||||
| 
 | ||||
|   async function createArticle() { | ||||
|     console.log("Create article"); | ||||
|     const payload: CreateArticle = { | ||||
|       title: titleRef.current.value, | ||||
|       introduction: introductionRef.current.value, | ||||
|       markdown: markdown, | ||||
|       categoryId: Number(categorySelectRef?.current?.getValue()[0]?.value), | ||||
|       contentTable: contentTable, | ||||
|     }; | ||||
|     console.log(payload); | ||||
| 
 | ||||
|     await fetch("/api/articles/", { | ||||
|       method: "POST", | ||||
|       headers: { | ||||
|         Accept: "application/json", | ||||
|         "Content-Type": "application/json", | ||||
|       }, | ||||
|       body: JSON.stringify(formData), | ||||
|       cache: "no-cache", | ||||
|       body: JSON.stringify(payload), | ||||
|     }) | ||||
|       .then(handleResponse) | ||||
|       .catch(console.error); | ||||
|   } | ||||
| 
 | ||||
|   // App
 | ||||
|   useEffect(() => { | ||||
|     const fetchExistingArticle = async () => { | ||||
|       const result: Response = await fetch(urlJoin(apiUrl, `articles/${params.articleId}`), { | ||||
|         cache: "no-cache", | ||||
|         next: { revalidate: 60 * 1 }, | ||||
|       }); | ||||
| 
 | ||||
|     const response = await result.json(); | ||||
|     console.log(response); | ||||
|     errorTextRef.current.innerText = response.error ?? ""; | ||||
|     if (response.success) { | ||||
|       const newArticle: ArticleWithCategory = response.data; | ||||
|       router.push(urlJoin(`/articles/`, newArticle.category.name, newArticle.name)); | ||||
|     } | ||||
|   } | ||||
|       const article = await result.json(); | ||||
|       console.log(article); | ||||
|       if (article.code == "404") { | ||||
|         router.push(urlJoin(`/admin/articles/editor/0`)); | ||||
|       } else { | ||||
|         titleRef.current.value = article.title; | ||||
|         introductionRef.current.value = article.introduction; | ||||
|         markdownTextAreaRef.current.value = article.markdown; | ||||
|         categorySelectRef.current.setValue({ value: article.category.id, label: article.category.title }); | ||||
| 
 | ||||
|         setTitle(article.title); | ||||
|         setIntroduction(article.introduction); | ||||
|         setMarkdown(article.markdown); | ||||
|         setContentTable(article.contentTable); | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const fetchCategoryOptions = async () => { | ||||
|       const result: Response = await fetch(urlJoin(apiUrl, `categories`), { | ||||
|         cache: "no-cache", | ||||
| @@ -100,23 +159,34 @@ export default function AdminArticlesCreate() { | ||||
|       }); | ||||
|       setSelectCategoriesOptions(newSelectCategoriesOptions); | ||||
|     }; | ||||
| 
 | ||||
|     fetchCategoryOptions().catch((err) => { | ||||
|       console.log(err); | ||||
|     }); | ||||
| 
 | ||||
|     if (params.articleId != "0") { | ||||
|       fetchExistingArticle().catch((err) => { | ||||
|         console.log(err); | ||||
|       }); | ||||
|     } | ||||
|   }, []); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className={styles.adminArticlesCreate}> | ||||
|       <h1>Create a new article</h1> | ||||
|       <h1>{params.articleId == "0" ? "Create new article" : "Update article"}</h1> | ||||
|       <div className={styles.formControl}> | ||||
|         <p className="text-error" ref={errorTextRef}></p> | ||||
|         <button | ||||
|           type="button" | ||||
|           onClick={() => { | ||||
|             postData(); | ||||
|             if (params.articleId != "0") { | ||||
|               updateArticle(); | ||||
|             } else { | ||||
|               createArticle(); | ||||
|             } | ||||
|           }} | ||||
|         > | ||||
|           Create Article | ||||
|           {params.articleId == "0" ? "Create article" : "Update article"} | ||||
|         </button> | ||||
|       </div> | ||||
| 
 | ||||
| @@ -177,7 +247,7 @@ export default function AdminArticlesCreate() { | ||||
|             <label htmlFor="">Table of contents</label> | ||||
|             <div className={styles.contentTableEditor}> | ||||
|               <div className={styles.entries}> | ||||
|                 {contentTable.map((entry: IContentTableEntry, i: number) => { | ||||
|                 {contentTable?.map((entry: IContentTableEntry, i: number) => { | ||||
|                   return ( | ||||
|                     <div key={i}> | ||||
|                       <input | ||||
| @@ -186,6 +256,7 @@ export default function AdminArticlesCreate() { | ||||
|                         }} | ||||
|                         type="text" | ||||
|                         placeholder={"Anchor"} | ||||
|                         defaultValue={entry.anchor} | ||||
|                       /> | ||||
|                       <input | ||||
|                         onChange={(e) => { | ||||
| @@ -193,14 +264,22 @@ export default function AdminArticlesCreate() { | ||||
|                         }} | ||||
|                         type="text" | ||||
|                         placeholder={"Title"} | ||||
|                       /> | ||||
|                         defaultValue={entry.title} | ||||
|                       />{" "} | ||||
|                       <button | ||||
|                         onClick={() => { | ||||
|                           removeEntry(i); | ||||
|                         }} | ||||
|                       > | ||||
|                         Remove | ||||
|                       </button> | ||||
|                     </div> | ||||
|                   ); | ||||
|                 })} | ||||
| 
 | ||||
|                 <button | ||||
|                   onClick={() => { | ||||
|                     setContentTable([...contentTable, { title: "Title", anchor: "Anchor" }]); | ||||
|                     setContentTable([...contentTable, { title: "", anchor: "" }]); | ||||
|                   }} | ||||
|                 > | ||||
|                   Add | ||||
| @@ -14,7 +14,7 @@ type ArticleWithIncludes = Prisma.ArticleGetPayload<{ | ||||
| }>; | ||||
|  | ||||
| export async function GetArticle(articleName: string): Promise<any> { | ||||
|   const result: Response = await fetch(urlJoin(apiUrl, `articles/${articleName ?? ""}`), { | ||||
|   const result: Response = await fetch(urlJoin(apiUrl, `articles/name/${articleName ?? ""}`), { | ||||
|     cache: "force-cache", | ||||
|     next: { revalidate: 60 * 10 }, | ||||
|   }); | ||||
|   | ||||
							
								
								
									
										38
									
								
								pages/api/articles/[articleId].ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								pages/api/articles/[articleId].ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| import { Prisma, Article } from "@prisma/client"; | ||||
| import { Request, Response } from "express"; | ||||
| import { ResponseError } from "../../../types/responseErrors"; | ||||
| import { formatTextToUrlName } from "../../../utils"; | ||||
| import prisma from "../../../lib/prisma"; | ||||
|  | ||||
|  | ||||
| type ArticleWithIncludes = Prisma.ArticleGetPayload<{ include: { contentTableEntries: true, category: true, image: true } }> | ||||
|  | ||||
|  | ||||
|  | ||||
| export default async function handler(req: Request, res: Response) { | ||||
|   res.setHeader("Content-Type", "application/json"); | ||||
|  | ||||
|   const articleId: string = formatTextToUrlName(req.query.articleId.toString()) | ||||
|  | ||||
|   await prisma.article | ||||
|     .findUnique({ where: { id: articleId }, include: { category: true, image: true } }) | ||||
|     .then((result: ArticleWithIncludes) => { | ||||
|       if (result !== null) { | ||||
|         res.end(JSON.stringify(result)); | ||||
|       } else { | ||||
|         const error: ResponseError = { | ||||
|           code: "404", | ||||
|           message: "No article with this name found!", | ||||
|         }; | ||||
|         res.status(404).send(JSON.stringify(error)); | ||||
|       } | ||||
|     }) | ||||
|     .catch((err) => { | ||||
|  | ||||
|       const error: ResponseError = { | ||||
|         code: "500", | ||||
|         message: err, | ||||
|       }; | ||||
|       res.status(500).send(JSON.stringify(error)); | ||||
|     }); | ||||
| } | ||||
| @@ -1,48 +0,0 @@ | ||||
| import { Request, Response } from "express"; | ||||
| import prisma from "../../../lib/prisma"; | ||||
| import { Prisma } from "@prisma/client"; | ||||
| import { Article, Category } from "@prisma/client"; | ||||
| import { ResponseError } from "../../../types/responseErrors"; | ||||
| import { PostArticle } from "../../../types/postData"; | ||||
| import { isValidText } from "../../../validators"; | ||||
| import { formatTextToUrlName } from "../../../utils"; | ||||
| import { json } from "stream/consumers"; | ||||
|  | ||||
| export default async function handler(req: Request, res: Response) { | ||||
|   res.setHeader("Content-Type", "application/json"); | ||||
|  | ||||
|   const postData: any = req.body; | ||||
|   console.log(postData); | ||||
|   if (!isValidText(postData.title)) { | ||||
|     res.send(JSON.stringify({ target: "title", error: "Not a valid title" })); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!isValidText(postData.introduction)) { | ||||
|     res.send(JSON.stringify({ target: "introduction", error: "Not a valid introduction" })); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!postData.categoryId) { | ||||
|     res.send(JSON.stringify({ target: "category", error: "Category is required" })); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   postData.name = formatTextToUrlName(postData.title); | ||||
|   prisma.article | ||||
|     .create({ data: postData, include: { category: true } }) | ||||
|     .then( | ||||
|       (data) => { | ||||
|         res.send(JSON.stringify({ success: true, data: data })); | ||||
|       }, | ||||
|       (errorReason) => { | ||||
|         if (errorReason.code === "P2002") { | ||||
|           res.send(JSON.stringify({ target: errorReason.meta.target[0], error: "Already exists" })); | ||||
|         } | ||||
|       } | ||||
|     ) | ||||
|     .catch((err) => { | ||||
|       console.error(err); | ||||
|       res.sendStatus(500).end(); | ||||
|     }); | ||||
| } | ||||
| @@ -3,10 +3,15 @@ import prisma from "../../../lib/prisma"; | ||||
| import { Prisma } from "@prisma/client"; | ||||
| import { Article, Category } from "@prisma/client"; | ||||
| import { ResponseError } from "../../../types/responseErrors"; | ||||
| import { formatTextToUrlName } from "../../../utils"; | ||||
| import { isValidText } from "../../../validators"; | ||||
| import { title } from 'process'; | ||||
| import { UpdateArticle } from "../../../types/api"; | ||||
|  | ||||
| export default async function handler(req: Request, res: Response) { | ||||
|   res.setHeader("Content-Type", "application/json"); | ||||
|  | ||||
|   if (req.method == "GET") { | ||||
|     const categoryName: string = req.query.categoryName?.toString() ?? ""; | ||||
|     const limit: number = req.query.limit ? Number(req.query.limit) : undefined; | ||||
|     const orderBy: string = req.query.orderBy?.toString() ?? ""; | ||||
| @@ -49,4 +54,84 @@ export default async function handler(req: Request, res: Response) { | ||||
|         }; | ||||
|         res.status(500).send(JSON.stringify(error)); | ||||
|       }); | ||||
|   } else if (req.method == "POST") { | ||||
|     const data: any = req.body; | ||||
|  | ||||
|     if (!isValidText(data.title)) { | ||||
|       res.send(JSON.stringify({ target: "title", error: "Not a valid title" })); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (!isValidText(data.introduction)) { | ||||
|       res.send(JSON.stringify({ target: "introduction", error: "Not a valid introduction" })); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (!data.categoryId) { | ||||
|       res.send(JSON.stringify({ target: "category", error: "Category is required" })); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     data.name = formatTextToUrlName(data.title); | ||||
|     prisma.article | ||||
|       .create({ data: data, include: { category: true } }) | ||||
|       .then( | ||||
|         (data) => { | ||||
|           res.send(JSON.stringify({ success: true, data: data })); | ||||
|         }, | ||||
|         (errorReason) => { | ||||
|           if (errorReason.code === "P2002") { | ||||
|             res.send(JSON.stringify({ target: errorReason.meta.target[0], error: "Already exists" })); | ||||
|           } | ||||
|         } | ||||
|       ) | ||||
|       .catch((err) => { | ||||
|         console.error(err); | ||||
|         res.sendStatus(500).end(); | ||||
|       }); | ||||
|   } else if (req.method == "PUT") { | ||||
|     const data: UpdateArticle = req.body; | ||||
|  | ||||
|     if (!isValidText(data.title)) { | ||||
|       res.send(JSON.stringify({ target: "title", error: "Not a valid title" })); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (!isValidText(data.introduction)) { | ||||
|       res.send(JSON.stringify({ target: "introduction", error: "Not a valid introduction" })); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (!data.categoryId) { | ||||
|       res.send(JSON.stringify({ target: "category", error: "Category is required" })); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const newArticle: Prisma.ArticleUncheckedUpdateInput = { | ||||
|       title: data.title, | ||||
|       name: formatTextToUrlName(data.title), | ||||
|       introduction: data.introduction, | ||||
|       categoryId: data.categoryId, | ||||
|       contentTable: data.contentTable, | ||||
|       markdown: data.markdown, | ||||
|       imageId: data.imageId, | ||||
|     } | ||||
|  | ||||
|     await prisma.article.update({ data: newArticle, where: { id: data.id }, include: { category: true } }) | ||||
|       .then( | ||||
|         (data) => { | ||||
|           res.send(JSON.stringify({ success: true, data: data })); | ||||
|         }, | ||||
|         (errorReason) => { | ||||
|           if (errorReason.code === "P2002") { | ||||
|             res.send(JSON.stringify({ target: errorReason.meta.target[0], error: "Already exists" })); | ||||
|           } | ||||
|         } | ||||
|       ) | ||||
|       .catch((err) => { | ||||
|         console.error(err); | ||||
|         res.sendStatus(500).end(); | ||||
|       }); | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import { Request, Response } from "express"; | ||||
| import prisma from "../../../lib/prisma"; | ||||
| import prisma from "../../../../lib/prisma"; | ||||
| import { Prisma } from '@prisma/client'; | ||||
| import { ResponseError } from "../../../types/responseErrors"; | ||||
| import { formatTextToUrlName } from "../../../utils"; | ||||
| import { ResponseError } from "../../../../types/responseErrors"; | ||||
| import { formatTextToUrlName } from "../../../../utils"; | ||||
| 
 | ||||
| type ArticleWithIncludes = Prisma.ArticleGetPayload<{ include: { contentTableEntries: true, category: true, image: true } }> | ||||
| 
 | ||||
| @@ -27,6 +27,7 @@ export default async function handler(req: Request, res: Response) { | ||||
|       } | ||||
|     }) | ||||
|     .catch((err) => { | ||||
| 
 | ||||
|       const error: ResponseError = { | ||||
|         code: "500", | ||||
|         message: err, | ||||
| @@ -8,7 +8,7 @@ datasource db { | ||||
| } | ||||
|  | ||||
| model Article { | ||||
|   id           Int      @id @default(autoincrement()) | ||||
|   id           String   @id @default(uuid()) | ||||
|   name         String   @unique | ||||
|   title        String   @unique | ||||
|   introduction String   @default("") | ||||
|   | ||||
| @@ -57,6 +57,7 @@ | ||||
|             border: 2px solid #3b3b3b80; | ||||
|             background-color: transparent; | ||||
|             min-height: 700px; | ||||
|             max-height: 1500px; | ||||
|             resize: none; | ||||
|             display: block; | ||||
|             border-radius: 0px; | ||||
| @@ -68,7 +69,9 @@ | ||||
| 
 | ||||
|           & > div { | ||||
|             max-width: 1000px; | ||||
|             max-height: 1500px; | ||||
|             border: 2px solid #3b3b3b80; | ||||
|             overflow: auto; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| @@ -80,8 +83,9 @@ | ||||
|           gap: 10px 10px; | ||||
|           & > div { | ||||
|             border: 2px solid #3b3b3b80; | ||||
| 
 | ||||
|             overflow: auto; | ||||
|             max-width: 1000px; | ||||
|             max-height: 1000px; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
							
								
								
									
										21
									
								
								types/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								types/api.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| import { IContentTableEntry } from "./contentTable"; | ||||
| import { Prisma } from '@prisma/client'; | ||||
|  | ||||
| export interface CreateArticle { | ||||
|   title: string; | ||||
|   markdown: string; | ||||
|   introduction: string; | ||||
|   categoryId: number; | ||||
|   contentTable: Prisma.JsonArray | ||||
|   imageId?: number; | ||||
| } | ||||
|  | ||||
| export interface UpdateArticle { | ||||
|   id: string; | ||||
|   title?: string; | ||||
|   markdown?: string; | ||||
|   introduction?: string; | ||||
|   categoryId?: number; | ||||
|   contentTable?: Prisma.JsonArray | ||||
|   imageId?: number; | ||||
| } | ||||
| @@ -1,11 +0,0 @@ | ||||
| import { IContentTableEntry } from "./contentTable"; | ||||
|  | ||||
| export interface PostArticle { | ||||
|   title: string; | ||||
|   name?: string; | ||||
|   markdown: string; | ||||
|   introduction: string; | ||||
|   categoryId: number; | ||||
|   contentTable: IContentTableEntry[] | ||||
|   imageId?: number; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Janis
					Janis