This commit is contained in:
Janis
2023-02-07 15:42:40 +01:00
parent d2ff34d3b6
commit 29f359610f
16 changed files with 421 additions and 264 deletions

View File

@@ -5,7 +5,6 @@ import styles from "@/styles/modules/AdminNav.module.scss";
function AdminNav() { function AdminNav() {
return ( return (
<div className={styles.adminNav}> <div className={styles.adminNav}>
<Link href={"/admin"}>Admin</Link>
<Link href={"/admin/editor/article/0"}>New article</Link> <Link href={"/admin/editor/article/0"}>New article</Link>
<Link href={"/admin/editor/category/0"}>New category</Link> <Link href={"/admin/editor/category/0"}>New category</Link>
</div> </div>

View File

@@ -0,0 +1,31 @@
import React from "react";
import { apiUrl } from "@/global";
import urlJoin from "url-join";
import { useRouter } from "next/navigation";
export default function ArticleControl({ articleId }: { articleId: string }) {
const router = useRouter();
async function deleteArticle() {
await fetch(urlJoin(apiUrl, `articles/${articleId}`), { method: "DELETE" })
.then((response) => response.json())
.then((result) => {
console.log(result);
});
router.push("/articles");
}
function editArticle() {
router.push("/admin/editor/article/" + articleId);
}
return (
<div>
<button className="danger" onClick={deleteArticle}>
Delete
</button>
<button className="warning" onClick={editArticle}>
Edit
</button>
</div>
);
}

View File

@@ -0,0 +1,31 @@
import React from "react";
import { apiUrl } from "@/global";
import urlJoin from "url-join";
import { useRouter } from "next/navigation";
export default function CategoryControl({ categoryId }: { categoryId: string }) {
const router = useRouter();
async function deleteCategory() {
await fetch(urlJoin(apiUrl, `categories/${categoryId}`), { method: "DELETE" })
.then((response) => response.json())
.then((result) => {
console.log(result);
});
router.push("/articles");
}
function editCategory() {
router.push("/admin/editor/category/" + categoryId);
}
return (
<div>
<button className="danger" onClick={deleteCategory}>
Delete
</button>
<button className="warning" onClick={editCategory}>
Edit
</button>
</div>
);
}

View File

@@ -1,31 +1,25 @@
import React from "react"; import React from "react";
import styles from "@/styles/modules/ArticleContentTable.module.scss"; import styles from "@/styles/modules/ArticleContentTable.module.scss";
import { IContentTableEntry } from "../types/contentTable"; import { IContentTableEntry } from "../types/contentTable";
import { CLIENT_RENEG_LIMIT } from "tls";
export default function ContentTable({ export default function ContentTable({ contentTableData }: { contentTableData: IContentTableEntry[] }) {
contentTableData, console.log(contentTableData);
}: { return (
contentTableData: any; <div className={styles.articleContentTable}>
}) { <div className={styles.stickyContainer}>
return ( <div className={styles.list}>
<div className={styles.articleContentTable}> <h2>Contents</h2>
<div className={styles.stickyContainer}> {contentTableData.map((e: IContentTableEntry, i: number) => {
<div className={styles.list}> return (
<h2>Contents</h2> <a key={i} href={"#" + e.anchor}>
{contentTableData?.map((e: IContentTableEntry, i: number) => { {e.title}
return ( </a>
<a key={i} href={"#" + e.anchor}> );
{e.title} })}
</a> </div>
); {contentTableData?.length < 15 ? <div className={styles.adContainer}>Future advertisement</div> : ""}
})} </div>
</div> </div>
{contentTableData?.length < 15 ? ( );
<div className={styles.adContainer}>Future advertisement</div>
) : (
""
)}
</div>
</div>
);
} }

View File

@@ -3,147 +3,168 @@ import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Category } from "@prisma/client"; import { Category } from "@prisma/client";
import prisma, { CategoryWithIncludes } from "@/lib/prisma";
import { CLIENT_RENEG_LIMIT } from "tls";
import { apiUrl } from "@/global";
import urlJoin from "url-join";
function switchTheme(theme: string) { function switchTheme(theme: string) {
const bodyElement = document.getElementsByTagName("body")[0]; const bodyElement = document.getElementsByTagName("body")[0];
if (theme == "dark") { if (theme == "dark") {
bodyElement.classList.remove("theme-light"); bodyElement.classList.remove("theme-light");
} else { } else {
bodyElement.classList.add("theme-light"); bodyElement.classList.add("theme-light");
} }
} }
function toggleTheme() { function toggleTheme() {
const svgElement = document.getElementById("themeSwitchSvg"); const svgElement = document.getElementById("themeSwitchSvg");
if (svgElement) { if (svgElement) {
if (localStorage.getItem("theme") == "light") { if (localStorage.getItem("theme") == "light") {
svgElement.style.animationDirection = "normal"; svgElement.style.animationDirection = "normal";
svgElement.style.animationName = styles.spinThemeSwitch; svgElement.style.animationName = styles.spinThemeSwitch;
} else { } else {
svgElement.style.animationDirection = "reverse"; svgElement.style.animationDirection = "reverse";
svgElement.style.animationName = styles.spinThemeSwitch; svgElement.style.animationName = styles.spinThemeSwitch;
} }
setTimeout(() => { setTimeout(() => {
if (localStorage.getItem("theme") == "light") { if (localStorage.getItem("theme") == "light") {
localStorage.setItem("theme", "dark"); localStorage.setItem("theme", "dark");
switchTheme("dark"); switchTheme("dark");
} else { } else {
localStorage.setItem("theme", "light"); localStorage.setItem("theme", "light");
switchTheme("light"); switchTheme("light");
} }
svgElement.style.animationName = ""; svgElement.style.animationName = "";
}, 150); }, 150);
} }
} }
export default function Nav({ categories }: { categories: Category[] }) { export default function Nav() {
const [searchResults, setSearchResults] = useState< const [searchResults, setSearchResults] = useState<{ name: string; title: string }[]>([]);
{ name: string; title: string }[] const [categories, setCategories] = useState<Category[]>([]);
>([]);
async function handleSearchInput(event: React.ChangeEvent<HTMLInputElement>) { async function handleSearchInput(event: React.ChangeEvent<HTMLInputElement>) {
const query = event.target.value; const query = event.target.value;
let result = await fetch(`/api/search?q=${query}`); let result = await fetch(`/api/search?q=${query}`);
let json = await result.json(); let json = await result.json();
if (json.length == 0 && query.length > 0) { if (json.length == 0 && query.length > 0) {
setSearchResults([{ name: "", title: "No article found..." }]); setSearchResults([{ name: "", title: "No article found..." }]);
} else { } else {
setSearchResults(json); setSearchResults(json);
} }
} }
useEffect(() => { useEffect(() => {
if (localStorage.getItem("theme") == "dark") { if (localStorage.getItem("theme") == "dark") {
switchTheme("dark"); switchTheme("dark");
} else { } else {
switchTheme("light"); switchTheme("light");
} }
}, []); }, []);
useEffect(() => { useEffect(() => {
console.log(searchResults); console.log(searchResults);
}, [searchResults]); }, [searchResults]);
return ( useEffect(() => {
<nav className={styles.nav}> async function getCategories() {
<div className={styles.containerLeft}> await fetch(urlJoin(apiUrl, "categories"))
<Image .then((response) => response.json())
src={"/images/logo.svg"} .then((result: Category[]) => {
height={40} setCategories(result);
width={160} });
alt="Nav bar logo" }
onClick={() => {
window.open("/", "_self");
}}
className={styles.logo}
/>
<div className={styles.links}>
<div className={styles.dropDown}>
<Link href="/articles">Categories</Link>
<div className={styles.dropDownContainer}>
<div className={styles.content}>
<Link href={"/articles"}>All</Link>
{categories?.map((cat, i) => { getCategories();
{ }, []);
return ( return (
<Link <nav className={styles.nav}>
key={i} <div className={styles.containerLeft}>
href={`/articles/${cat.name.toLowerCase()}`} <Image
> src={"/images/logo.svg"}
{cat.title} height={40}
</Link> width={160}
); alt="Nav bar logo"
} onClick={() => {
})} window.open("/", "_self");
</div> }}
</div> className={styles.logo}
</div> />
</div> <div className={styles.links}>
</div> <div className={styles.dropDown}>
<div className={styles.containerCenter}> <Link href="/articles">Categories</Link>
<div className={styles.searchBar}> <div className={styles.dropDownContainer}>
<div className={styles.icon}> <div className={styles.content}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <Link href={"/articles"}>All</Link>
<path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352c79.5 0 144-64.5 144-144s-64.5-144-144-144S64 128.5 64 208s64.5 144 144 144z" />
</svg> {categories?.map((cat, i) => {
</div> {
<input onInput={handleSearchInput} type="text" name="" id="" /> return (
</div> <Link key={i} href={`/articles/${cat.name.toLowerCase()}`}>
<div className={styles.searchResults}> {cat.title}
<div className={styles.content}> </Link>
{searchResults.map((s) => { );
{ }
return ( })}
<Link href={`/articles/${s.name.toLowerCase()}`}> </div>
{s.title} </div>
</Link> </div>
); </div>
} </div>
})} <div className={styles.containerCenter}>
</div> <div className={styles.searchBar}>
</div> <div className={styles.icon}>
</div> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<div className={styles.containerRight}> <path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352c79.5 0 144-64.5 144-144s-64.5-144-144-144S64 128.5 64 208s64.5 144 144 144z" />
<div </svg>
className={styles.themeSwitch} </div>
onClick={() => { <input onInput={handleSearchInput} type="text" name="" id="" />
toggleTheme(); </div>
}} <div className={styles.searchResults}>
> <div className={styles.content}>
<svg {searchResults.map((s) => {
id="themeSwitchSvg" {
xmlns="http://www.w3.org/2000/svg" return <Link href={`/articles/${s.name.toLowerCase()}`}>{s.title}</Link>;
viewBox="0 0 512 512" }
> })}
<path d="M448 256c0-106-86-192-192-192V448c106 0 192-86 192-192zm64 0c0 141.4-114.6 256-256 256S0 397.4 0 256S114.6 0 256 0S512 114.6 512 256z" /> </div>
</svg> </div>
</div> </div>
</div> <div className={styles.containerRight}>
</nav> <div
); className={styles.themeSwitch}
onClick={() => {
toggleTheme();
}}
>
<svg id="themeSwitchSvg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M448 256c0-106-86-192-192-192V448c106 0 192-86 192-192zm64 0c0 141.4-114.6 256-256 256S0 397.4 0 256S114.6 0 256 0S512 114.6 512 256z" />
</svg>
</div>
</div>
</nav>
);
}
export async function getServerSideProps() {
let categories: Category[] = [];
await prisma.category.findMany({ include: { articles: true, svg: true } }).then(
(result: Category[]) => {
if (result) {
categories = JSON.parse(JSON.stringify(result));
}
},
(reason: any) => {
console.log(reason);
}
);
return {
props: { categories: categories }, // will be passed to the page component as props
};
} }

View File

@@ -6,12 +6,14 @@ import type { AppProps } from "next/app";
import AdminNav from "@/components/AdminNav"; import AdminNav from "@/components/AdminNav";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import Nav from "@/components/Nav"; import Nav from "@/components/Nav";
import { Category } from "@prisma/client";
import prisma from "@/lib/prisma";
export default function App({ Component, pageProps }: AppProps) { export default function App({ Component, pageProps }: AppProps) {
return ( return (
<> <>
<header> <header>
<Nav categories={[]} /> <Nav />
<AdminNav /> <AdminNav />
</header> </header>
<Component {...pageProps} /> <Component {...pageProps} />

View File

@@ -2,11 +2,10 @@ import { Html, Head, Main, NextScript } from "next/document";
export default function Document() { export default function Document() {
return ( return (
<Html lang="en"> <Html lang="en" style={{ scrollBehavior: "smooth" }}>
<Head /> <Head />
<body> <body>
<Main /> <Main />
<NextScript /> <NextScript />
</body> </body>
</Html> </Html>

View File

@@ -44,7 +44,7 @@ export default function AdminArticlesEditorPage({ article, categories }: { artic
function changeContentTableEntryTitle(index: number, newTitle: string) { function changeContentTableEntryTitle(index: number, newTitle: string) {
setContentTable((prevArray: any) => { setContentTable((prevArray: any) => {
let newArray = [...prevArray]; let newArray = [...prevArray];
newArray[index].anchor = newTitle; newArray[index].title = newTitle;
return newArray; return newArray;
}); });
} }

View File

@@ -1,19 +1,19 @@
import { Prisma, Article } from "@prisma/client"; import { Prisma, Article } from "@prisma/client";
import { ResponseError } from "../../../types/responseErrors"; import { ResponseError } from "../../../types/responseErrors";
import { formatTextToUrlName } from "../../../utils"; import { formatTextToUrlName } from "../../../utils";
import prisma from "../../../lib/prisma"; import prisma, { ArticleWithIncludes } from "../../../lib/prisma";
import type { NextApiRequest, NextApiResponse } from 'next' import type { NextApiRequest, NextApiResponse } from 'next'
import { UpdateArticle } from "../../../types/api"; import { UpdateArticle } from "../../../types/api";
import { isValidText } from "../../../validators"; import { isValidText } from "../../../validators";
type ArticleWithIncludes = Prisma.ArticleGetPayload<{ include: { contentTableEntries: true, category: true, image: true } }>
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const articleId: string = formatTextToUrlName(req.query?.articleId?.toString() ?? "")
if (req.method == "PUT") {//* PUT if (req.method == "PUT") {//* PUT
console.log("PUT") console.log("PUT")
const articleId: string = formatTextToUrlName(req.query?.articleId?.toString() ?? "")
console.log(`API articleId: ${articleId}`) console.log(`API articleId: ${articleId}`)
const articleData: UpdateArticle = req.body; const articleData: UpdateArticle = req.body;
@@ -55,6 +55,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
console.error(err); console.error(err);
res.status(500).end(); res.status(500).end();
}); });
} else if (req.method == "DELETE") {
console.log("DELETE article")
prisma.article.delete({ where: { id: articleId }, include: { category: true } }).then((result: ArticleWithIncludes | null) => {
if (result) {
res.status(200).json(result)
} else {
res.status(500).json({ error: true, message: "No article found" })
}
}, (err) => {
console.log(err)
res.status(500).end(err)
})
} }
} }

View File

@@ -1,4 +1,4 @@
import prisma from "../../../lib/prisma"; import prisma, { CategoryWithIncludes } from "../../../lib/prisma";
import { Category } from "@prisma/client"; import { Category } from "@prisma/client";
import { ResponseError } from "../../../types/responseErrors"; import { ResponseError } from "../../../types/responseErrors";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
@@ -9,10 +9,10 @@ import { isValidText } from "../../../validators";
import { UpdateCategory } from '../../../types/api'; import { UpdateCategory } from '../../../types/api';
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const categoryId: string = req.query.categoryId?.toString() ?? "";
if (req.method == "PUT") { if (req.method == "PUT") {
const categoryId: string = req.query.categoryId?.toString() ?? "";
const categoryData: UpdateCategory = req.body; const categoryData: UpdateCategory = req.body;
if (categoryData.title && !isValidText(categoryData.title)) { if (categoryData.title && !isValidText(categoryData.title)) {
@@ -66,5 +66,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
console.error(err); console.error(err);
res.status(500).end(); res.status(500).end();
}); });
} else if (req.method == "DELETE") {
console.log("DELETE category")
prisma.category.delete({ where: { id: categoryId }, include: { articles: true, svg: true } }).then((result: CategoryWithIncludes | null) => {
if (result) {
res.status(200).json(result)
} else {
res.status(500).json({ error: true, message: "No category found" })
}
}, (err) => {
console.log(err)
res.status(500).end(err)
})
} }
} }

View File

@@ -10,66 +10,77 @@ import type { NextApiRequest, NextApiResponse } from 'next'
import { CreateCategory } from "@/types/api"; import { CreateCategory } from "@/types/api";
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method == "POST") { if (req.method == "GET") {
console.log("API new category") console.log("API get categories")
const categoryData: CreateCategory = req.body; await prisma.category.findMany().then((result: Category[]) => {
console.log(categoryData) if (result) {
res.status(200).json(result)
}
}, (errorReason) => {
console.log(errorReason);
res.status(500).end(errorReason);
})
} else
if (req.method == "POST") {
console.log("API new category")
const categoryData: CreateCategory = req.body;
console.log(categoryData)
if (!isValidText(categoryData.title)) { if (!isValidText(categoryData.title)) {
res.json({ target: "title", error: "Not a valid title" }); res.json({ target: "title", error: "Not a valid title" });
return; return;
} }
categoryData.svg.viewbox = categoryData.svg.viewbox.length > 1 ? categoryData.svg.viewbox : ""; categoryData.svg.viewbox = categoryData.svg.viewbox.length > 1 ? categoryData.svg.viewbox : "";
const newSvg: Prisma.SvgUncheckedCreateInput = { const newSvg: Prisma.SvgUncheckedCreateInput = {
viewbox: categoryData.svg.viewbox, viewbox: categoryData.svg.viewbox,
path: categoryData.svg.path path: categoryData.svg.path
} }
await prisma.svg await prisma.svg
.create({ data: newSvg }) .create({ data: newSvg })
.then( .then(
async (createdSvg: Svg) => { async (createdSvg: Svg) => {
const newCategory: Prisma.CategoryUncheckedCreateInput = { const newCategory: Prisma.CategoryUncheckedCreateInput = {
title: categoryData.title, title: categoryData.title,
name: formatTextToUrlName(categoryData.title), name: formatTextToUrlName(categoryData.title),
color: categoryData.color ?? "teal", color: categoryData.color ?? "teal",
svgId: createdSvg.id, svgId: createdSvg.id,
}
await prisma.category
.create({
data: newCategory,
include: { svg: true, articles: true },
})
.then(
(createdCategory: CategoryWithIncludes | null) => {
if (createdCategory) {
res.json({ success: true, data: createdCategory });
} else {
res.json({ error: true, message: "Could not create category" });
}
},
(errorReason) => {
console.log(errorReason)
if (errorReason.code === "P2002") {
res.json({ target: errorReason.meta.target[0], error: "Already exists" });
}
}
)
.catch((err) => {
console.error(err);
res.status(500).end();
});
},
(errorReason) => {
res.status(500).end(errorReason);
} }
)
await prisma.category .catch((err) => {
.create({ console.error(err);
data: newCategory, res.status(500).end();
include: { svg: true, articles: true }, });
}) }
.then(
(createdCategory: CategoryWithIncludes | null) => {
if (createdCategory) {
res.json({ success: true, data: createdCategory });
} else {
res.json({ error: true, message: "Could not create category" });
}
},
(errorReason) => {
console.log(errorReason)
if (errorReason.code === "P2002") {
res.json({ target: errorReason.meta.target[0], error: "Already exists" });
}
}
)
.catch((err) => {
console.error(err);
res.status(500).end();
});
},
(errorReason) => {
res.status(500).end(errorReason);
}
)
.catch((err) => {
console.error(err);
res.status(500).end();
});
}
} }

30
pages/api/search.ts Normal file
View File

@@ -0,0 +1,30 @@
import prisma from "../../lib/prisma";
import type { NextApiRequest, NextApiResponse } from 'next'
import { formatTextToUrlName } from '../../utils';
import { Prisma } from '@prisma/client';
type SearchArticle = Prisma.ArticleGetPayload<{ select: { title: true, name: true } }>
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
let query: string = req.query.q?.toString() ?? "";
query = formatTextToUrlName(query)
if (query.length > 0) {
await prisma.article.findMany({
select: { title: true, name: true },
take: 5,
}).then((result: SearchArticle[]) => {
let searchResult: SearchArticle[] = []
result.forEach((a: SearchArticle) => {
if (a.name.includes(query)) {
searchResult.push(a);
}
});
res.status(200).json(searchResult);
}, (err: any) => {
console.log(err)
res.status(200).json([]);
});
} else {
res.status(200).json([]);
}
}

View File

@@ -5,7 +5,9 @@ import Image from "next/image";
import Markdown from "@/components/Markdown"; import Markdown from "@/components/Markdown";
import { formatTextToUrlName } from "@/utils"; import { formatTextToUrlName } from "@/utils";
import prisma, { ArticleWithIncludes, CategoryWithIncludes } from "@/lib/prisma"; import prisma, { ArticleWithIncludes, CategoryWithIncludes } from "@/lib/prisma";
import articles from "../.."; import ArticleControl from "../../../../components/ArticleControl";
import { IContentTableEntry } from "@/types/contentTable";
import { Prisma } from "@prisma/client";
//* MAIN //* MAIN
export default function ArticlePage({ article }: { article: ArticleWithIncludes }) { export default function ArticlePage({ article }: { article: ArticleWithIncludes }) {
@@ -14,27 +16,32 @@ export default function ArticlePage({ article }: { article: ArticleWithIncludes
const dateOptions: Intl.DateTimeFormatOptions = { month: "long", day: "numeric", year: "numeric" }; const dateOptions: Intl.DateTimeFormatOptions = { month: "long", day: "numeric", year: "numeric" };
return ( return (
<div className={styles.article}> <>
<ContentTable contentTableData={article?.contentTable ? article?.contentTable : []} /> <ArticleControl articleId={article.id} />
<div className={styles.tutorialContent}> <div className={styles.article}>
<div className={styles.header}> <ContentTable
<p className={`${styles.dates} text-muted`}> contentTableData={article?.contentTable ? Array.from(article.contentTable as Prisma.JsonArray).map((c: any) => ({ anchor: c.anchor, title: c.title })) : []}
{`Published on ${dateCreated.toLocaleDateString("en-US", dateOptions)}`} />
<br /> <div className={styles.tutorialContent}>
{dateUpdated > dateCreated ? `Updated on ${dateUpdated.toLocaleDateString("en-US", dateOptions)}` : ""} <div className={styles.header}>
</p> <p className={`${styles.dates} text-muted`}>
{`Published on ${dateCreated.toLocaleDateString("en-US", dateOptions)}`}
<br />
{dateUpdated > dateCreated ? `Updated on ${dateUpdated.toLocaleDateString("en-US", dateOptions)}` : ""}
</p>
<h1>{article?.title}</h1> <h1>{article?.title}</h1>
<div className={styles.tags}> <div className={styles.tags}>
<a href="#">Docker</a> <a href="#">Setup</a> <a href="#">Ubuntu</a> <a href="#">Docker</a> <a href="#">Setup</a> <a href="#">Ubuntu</a>
</div>
<Image src={""} height={350} width={750} alt={""} quality={100} placeholder="blur" blurDataURL="/images/blur.png" loading="lazy" />
<p>{article?.introduction}</p>
</div> </div>
<Image src={""} height={350} width={750} alt={""} quality={100} placeholder="blur" blurDataURL="/images/blur.png" loading="lazy" /> <Markdown value={article?.markdown ?? ""} />
<p>{article?.introduction}</p>
</div> </div>
<Markdown value={article?.markdown ?? ""} /> <Sidebar />
</div> </div>
<Sidebar /> </>
</div>
); );
} }

View File

@@ -3,15 +3,18 @@ import Link from "next/link";
import { formatTextToUrlName } from "@/utils"; import { formatTextToUrlName } from "@/utils";
import { Article, Category } from "@prisma/client"; import { Article, Category } from "@prisma/client";
import prisma, { CategoryWithIncludes } from "@/lib/prisma"; import prisma, { CategoryWithIncludes } from "@/lib/prisma";
import CategoryControl from "../../../components/CategoryControl";
export default function CategoryPage({ category }: { category: CategoryWithIncludes | null }) { export default function CategoryPage({ category }: { category: CategoryWithIncludes }) {
return ( return (
<div className={styles.category}> <>
<h1>{category?.title}</h1> <CategoryControl categoryId={category.id} />
<div className={styles.content}> <div className={styles.category}>
<div className={`${styles.showcase} ${styles.smallShowcase}`}> <h1>{category?.title}</h1>
<h2>Most popular articles</h2> <div className={styles.content}>
{/* {popularArticles?.map((a, i) => { <div className={`${styles.showcase} ${styles.smallShowcase}`}>
<h2>Most popular articles</h2>
{/* {popularArticles?.map((a, i) => {
{ {
return ( return (
<Link key={i} href={`/articles/${category.name}/${a.name}`}> <Link key={i} href={`/articles/${category.name}/${a.name}`}>
@@ -20,9 +23,9 @@ export default function CategoryPage({ category }: { category: CategoryWithInclu
); );
} }
})} */} })} */}
</div> </div>
{/* <div className={`${styles.showcase} ${styles.smallShowcase}`}> {/* <div className={`${styles.showcase} ${styles.smallShowcase}`}>
<h2>Most recent articles</h2> <h2>Most recent articles</h2>
{recentArticles?.map((a, i) => { {recentArticles?.map((a, i) => {
{ {
@@ -35,22 +38,23 @@ export default function CategoryPage({ category }: { category: CategoryWithInclu
})} })}
</div> */} </div> */}
<div className={styles.showcase}> <div className={styles.showcase}>
<h2>All articles</h2> <h2>All articles</h2>
{category?.articles {category?.articles
? Array.from(category?.articles).map((a: Article, i: number) => { ? Array.from(category?.articles).map((a: Article, i: number) => {
{ {
return ( return (
<Link key={i} href={`/articles/${category.name}/${a.name}`}> <Link key={i} href={`/articles/${category.name}/${a.name}`}>
{a.title} {a.title}
</Link> </Link>
); );
} }
}) })
: ""} : ""}
</div>
</div> </div>
</div> </div>
</div> </>
); );
} }
export async function getServerSideProps(context: any) { export async function getServerSideProps(context: any) {

View File

@@ -0,0 +1,5 @@
-- DropForeignKey
ALTER TABLE "Article" DROP CONSTRAINT "Article_categoryId_fkey";
-- AddForeignKey
ALTER TABLE "Article" ADD CONSTRAINT "Article_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -16,7 +16,7 @@ model Article {
markdown String markdown String
contentTable Json? contentTable Json?
categoryId String categoryId String
category Category @relation(fields: [categoryId], references: [id]) category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
dateCreated DateTime @default(now()) dateCreated DateTime @default(now())
dateUpdated DateTime @default(now()) dateUpdated DateTime @default(now())
} }