mirror of
https://github.com/DerTyp7/explainegy-nextjs.git
synced 2025-10-29 21:02:13 +01:00
refactor
This commit is contained in:
@@ -1,97 +0,0 @@
|
||||
"use client";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import styles from "../styles/modules/AdminControl.module.scss";
|
||||
import "../styles/buttons.scss";
|
||||
|
||||
export default function AdminControl() {
|
||||
const router = useRouter();
|
||||
const [isArticle, setIsArticle] = useState(false);
|
||||
const [isCategory, setIsCategory] = useState(false);
|
||||
const [articleOrCategoryName, setArticleOrCategoryName] = useState("");
|
||||
const [articleOrCategoryId, setArticleOrCategoryId] = useState();
|
||||
const pathname = usePathname();
|
||||
|
||||
async function fetchDelete() {
|
||||
const response = await fetch(
|
||||
`/api/${isArticle ? "articles" : isCategory ? "categories" : ""}/${articleOrCategoryId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
cache: "no-cache",
|
||||
}
|
||||
);
|
||||
router.push("/articles/");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function checkArticlePage() {
|
||||
let path = pathname.split("/");
|
||||
if (path.length == 4) {
|
||||
if (path[1] == "articles") {
|
||||
setIsArticle(true);
|
||||
setArticleOrCategoryName(path[3]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setIsArticle(false);
|
||||
}
|
||||
|
||||
function checkCategoryPage() {
|
||||
let path = pathname.split("/");
|
||||
if (path.length == 3) {
|
||||
if (path[1] == "articles") {
|
||||
setIsCategory(true);
|
||||
setArticleOrCategoryName(path[2]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setIsCategory(false);
|
||||
}
|
||||
checkArticlePage();
|
||||
checkCategoryPage();
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
async function getArticleOrCategoryId() {
|
||||
const response = await fetch(
|
||||
`/api/${isArticle ? "articles" : isCategory ? "categories" : ""}/name/${articleOrCategoryName}`,
|
||||
{
|
||||
method: "GET",
|
||||
cache: "no-cache",
|
||||
}
|
||||
);
|
||||
const json = await response.json();
|
||||
|
||||
setArticleOrCategoryId(json.id);
|
||||
}
|
||||
getArticleOrCategoryId();
|
||||
}, [articleOrCategoryName]);
|
||||
return (
|
||||
<div className={styles.adminControl}>
|
||||
{isArticle || isCategory ? (
|
||||
<>
|
||||
<button
|
||||
className="danger"
|
||||
onClick={() => {
|
||||
fetchDelete();
|
||||
}}
|
||||
>
|
||||
Delete this {isArticle ? "article" : "category"}
|
||||
</button>
|
||||
<button
|
||||
className="warning"
|
||||
onClick={() => {
|
||||
router.push(`/admin/${isArticle ? "articles" : "categories"}/editor/${articleOrCategoryId}`);
|
||||
}}
|
||||
>
|
||||
Edit this {isArticle ? "article" : "category"}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import styles from "../styles/modules/AdminNav.module.scss";
|
||||
import styles from "@/styles/modules/AdminNav.module.scss";
|
||||
|
||||
function AdminNav() {
|
||||
return (
|
||||
<div className={styles.adminNav}>
|
||||
<Link href={"/admin"}>Admin</Link>
|
||||
<Link href={"/admin/articles/editor/0"}>New article</Link>
|
||||
<Link href={"/admin/categories/editor/0"}>New category</Link>
|
||||
<Link href={"/admin/editor/article/0"}>New article</Link>
|
||||
<Link href={"/admin/editor/category/0"}>New category</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
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";
|
||||
|
||||
export default function ContentTable({ contentTableData }: { contentTableData: any }) {
|
||||
return (
|
||||
<div className={styles.articleContentTable}>
|
||||
<div className={styles.stickyContainer}>
|
||||
<div className={styles.list}>
|
||||
<h2>Contents</h2>
|
||||
{contentTableData?.map((e, i) => {
|
||||
return (
|
||||
<a key={i} href={"#" + e.anchor}>
|
||||
{e.title}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{contentTableData?.length < 15 ? <div className={styles.adContainer}>Future advertisement</div> : ""}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export default function ContentTable({
|
||||
contentTableData,
|
||||
}: {
|
||||
contentTableData: any;
|
||||
}) {
|
||||
return (
|
||||
<div className={styles.articleContentTable}>
|
||||
<div className={styles.stickyContainer}>
|
||||
<div className={styles.list}>
|
||||
<h2>Contents</h2>
|
||||
{contentTableData?.map((e: IContentTableEntry, i: number) => {
|
||||
return (
|
||||
<a key={i} href={"#" + e.anchor}>
|
||||
{e.title}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{contentTableData?.length < 15 ? (
|
||||
<div className={styles.adContainer}>Future advertisement</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import styles from "../styles/modules/Footer.module.scss";
|
||||
import styles from "@/styles/modules/Footer.module.scss";
|
||||
import Image from "next/image";
|
||||
export default function Footer() {
|
||||
return (
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Gallery as ReactGridGallery, Image as ImageType, ThumbnailImageProps } from "react-grid-gallery";
|
||||
import Image from "next/image";
|
||||
const ImageComponent = (props: ThumbnailImageProps) => {
|
||||
const { src, alt, style, title } = props.imageProps;
|
||||
const { width, height } = props.item;
|
||||
|
||||
return (
|
||||
<Image
|
||||
alt={alt}
|
||||
src={src}
|
||||
title={title || ""}
|
||||
width={width}
|
||||
height={height}
|
||||
onClick={() => {
|
||||
window.open(src);
|
||||
}}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Gallery({ images }: { images: ImageType[] }) {
|
||||
return <ReactGridGallery images={images} enableImageSelection={false} thumbnailImageComponent={ImageComponent} />;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
"use client";
|
||||
import React, { useState } from "react";
|
||||
import { useRef } from "react";
|
||||
export default function ImageUpload() {
|
||||
const [selectedImage, setSelectedImage] = useState(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleImageChange = (event) => {
|
||||
setSelectedImage(URL.createObjectURL(event.target.files[0]));
|
||||
};
|
||||
|
||||
async function uploadImage() {
|
||||
if (selectedImage) {
|
||||
const formData = new FormData();
|
||||
formData.append("image", selectedImage);
|
||||
const response = await fetch("/api/images/", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
console.log(await response.json());
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input onChange={handleImageChange} ref={inputRef} type="file" name="image" accept="image/*" />
|
||||
{selectedImage && <img src={selectedImage} alt="Selected" />}
|
||||
<button onClick={uploadImage}>Upload</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
import PropTypes from "prop-types";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import oneDark from "react-syntax-highlighter/dist/esm/styles/prism/one-dark";
|
||||
import oneLight from "react-syntax-highlighter/dist/esm/styles/prism/one-light";
|
||||
import oneDark from "react-syntax-highlighter/dist/cjs/styles/prism/one-dark";
|
||||
import oneLight from "react-syntax-highlighter/dist/cjs/styles/prism/one-light";
|
||||
import styles from "../styles/modules/markdown.module.scss";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkGemoji from "remark-gemoji";
|
||||
@@ -13,11 +13,11 @@ import React from "react";
|
||||
|
||||
import { formatTextToUrlName } from "../utils";
|
||||
|
||||
function flatten(text, child) {
|
||||
function flatten(text: string, child: any): any {
|
||||
return typeof child === "string" ? text + child : React.Children.toArray(child.props.children).reduce(flatten, text);
|
||||
}
|
||||
|
||||
function HeadingRenderer({ children, level }) {
|
||||
function HeadingRenderer({ children, level }: { children: any; level: any }) {
|
||||
children = React.Children.toArray(children);
|
||||
const text = children.reduce(flatten, "");
|
||||
return React.createElement("h" + level, { id: formatTextToUrlName(text) }, children);
|
||||
@@ -55,7 +55,13 @@ export default function Markdown({ value }: { value: any }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SyntaxHighlighter style={oneDark} language={match ? match[1] : ""} PreTag="div" {...props}>
|
||||
<SyntaxHighlighter
|
||||
// @ts-ignore
|
||||
style={oneDark}
|
||||
language={match ? match[1] : ""}
|
||||
PreTag="div"
|
||||
{...props}
|
||||
>
|
||||
{String(children).replace(/\n$/, "")}
|
||||
</SyntaxHighlighter>
|
||||
</>
|
||||
|
||||
@@ -1,135 +1,149 @@
|
||||
"use client";
|
||||
import styles from "../styles/modules/Nav.module.scss";
|
||||
import styles from "@/styles/modules/Nav.module.scss";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Category } from "@prisma/client";
|
||||
|
||||
function switchTheme(theme) {
|
||||
const bodyElement = document.getElementsByTagName("body")[0];
|
||||
function switchTheme(theme: string) {
|
||||
const bodyElement = document.getElementsByTagName("body")[0];
|
||||
|
||||
if (theme == "dark") {
|
||||
bodyElement.classList.remove("theme-light");
|
||||
} else {
|
||||
bodyElement.classList.add("theme-light");
|
||||
}
|
||||
if (theme == "dark") {
|
||||
bodyElement.classList.remove("theme-light");
|
||||
} else {
|
||||
bodyElement.classList.add("theme-light");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
const svgElement = document.getElementById("themeSwitchSvg");
|
||||
const svgElement = document.getElementById("themeSwitchSvg");
|
||||
|
||||
if (localStorage.getItem("theme") == "light") {
|
||||
svgElement.style.animationDirection = "normal";
|
||||
svgElement.style.animationName = styles.spinThemeSwitch;
|
||||
} else {
|
||||
svgElement.style.animationDirection = "reverse";
|
||||
svgElement.style.animationName = styles.spinThemeSwitch;
|
||||
}
|
||||
if (svgElement) {
|
||||
if (localStorage.getItem("theme") == "light") {
|
||||
svgElement.style.animationDirection = "normal";
|
||||
svgElement.style.animationName = styles.spinThemeSwitch;
|
||||
} else {
|
||||
svgElement.style.animationDirection = "reverse";
|
||||
svgElement.style.animationName = styles.spinThemeSwitch;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (localStorage.getItem("theme") == "light") {
|
||||
localStorage.setItem("theme", "dark");
|
||||
switchTheme("dark");
|
||||
} else {
|
||||
localStorage.setItem("theme", "light");
|
||||
switchTheme("light");
|
||||
}
|
||||
svgElement.style.animationName = "";
|
||||
}, 150);
|
||||
setTimeout(() => {
|
||||
if (localStorage.getItem("theme") == "light") {
|
||||
localStorage.setItem("theme", "dark");
|
||||
switchTheme("dark");
|
||||
} else {
|
||||
localStorage.setItem("theme", "light");
|
||||
switchTheme("light");
|
||||
}
|
||||
svgElement.style.animationName = "";
|
||||
}, 150);
|
||||
}
|
||||
}
|
||||
|
||||
export default function Nav({ categories }: { categories: Category[] }) {
|
||||
const [searchResults, setSearchResults] = useState([]);
|
||||
const [searchResults, setSearchResults] = useState<
|
||||
{ name: string; title: string }[]
|
||||
>([]);
|
||||
|
||||
async function handleSearchInput(event) {
|
||||
const query = event.target.value;
|
||||
let result = await fetch(`/api/search?q=${query}`);
|
||||
let json = await result.json();
|
||||
async function handleSearchInput(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const query = event.target.value;
|
||||
let result = await fetch(`/api/search?q=${query}`);
|
||||
let json = await result.json();
|
||||
|
||||
if (json.length == 0 && query.length > 0) {
|
||||
setSearchResults([{ name: "", title: "No article found..." }]);
|
||||
} else {
|
||||
setSearchResults(json);
|
||||
}
|
||||
}
|
||||
if (json.length == 0 && query.length > 0) {
|
||||
setSearchResults([{ name: "", title: "No article found..." }]);
|
||||
} else {
|
||||
setSearchResults(json);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem("theme") == "dark") {
|
||||
switchTheme("dark");
|
||||
} else {
|
||||
switchTheme("light");
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem("theme") == "dark") {
|
||||
switchTheme("dark");
|
||||
} else {
|
||||
switchTheme("light");
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(searchResults);
|
||||
}, [searchResults]);
|
||||
useEffect(() => {
|
||||
console.log(searchResults);
|
||||
}, [searchResults]);
|
||||
|
||||
return (
|
||||
<nav className={styles.nav}>
|
||||
<div className={styles.containerLeft}>
|
||||
<Image
|
||||
src={"/images/logo.svg"}
|
||||
height={40}
|
||||
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>
|
||||
return (
|
||||
<nav className={styles.nav}>
|
||||
<div className={styles.containerLeft}>
|
||||
<Image
|
||||
src={"/images/logo.svg"}
|
||||
height={40}
|
||||
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) => {
|
||||
{
|
||||
return (
|
||||
<Link key={i} href={`/articles/${cat.name.toLowerCase()}`}>
|
||||
{cat.title}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.containerCenter}>
|
||||
<div className={styles.searchBar}>
|
||||
<div className={styles.icon}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<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>
|
||||
</div>
|
||||
<input onInput={handleSearchInput} type="text" name="" id="" />
|
||||
</div>
|
||||
<div className={styles.searchResults}>
|
||||
<div className={styles.content}>
|
||||
{searchResults.map((s) => {
|
||||
{
|
||||
return <Link href={`/articles/${s.name.toLowerCase()}`}>{s.title}</Link>;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.containerRight}>
|
||||
<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>
|
||||
);
|
||||
{categories?.map((cat, i) => {
|
||||
{
|
||||
return (
|
||||
<Link
|
||||
key={i}
|
||||
href={`/articles/${cat.name.toLowerCase()}`}
|
||||
>
|
||||
{cat.title}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.containerCenter}>
|
||||
<div className={styles.searchBar}>
|
||||
<div className={styles.icon}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<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>
|
||||
</div>
|
||||
<input onInput={handleSearchInput} type="text" name="" id="" />
|
||||
</div>
|
||||
<div className={styles.searchResults}>
|
||||
<div className={styles.content}>
|
||||
{searchResults.map((s) => {
|
||||
{
|
||||
return (
|
||||
<Link href={`/articles/${s.name.toLowerCase()}`}>
|
||||
{s.title}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.containerRight}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import React from "react";
|
||||
import styles from "../styles/modules/Sidebar.module.scss";
|
||||
import styles from "@/styles/modules/Sidebar.module.scss";
|
||||
export default function Sidebar() {
|
||||
return (
|
||||
<div className={styles.sidebar}>
|
||||
<div className={styles.stickyContainer}>
|
||||
<div className={styles.sidebarContainer}>
|
||||
<h3>Popular</h3>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
</div>
|
||||
return (
|
||||
<div className={styles.sidebar}>
|
||||
<div className={styles.stickyContainer}>
|
||||
<div className={styles.sidebarContainer}>
|
||||
<h3>Popular</h3>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
</div>
|
||||
|
||||
<div className={styles.sidebarContainer}>
|
||||
<h3>Related</h3>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
</div>
|
||||
<div className={styles.adContainer}>Future advertisement</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className={styles.sidebarContainer}>
|
||||
<h3>Related</h3>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
</div>
|
||||
<div className={styles.adContainer}>Future advertisement</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user