CRUD based pages multilogs
This commit is contained in:
parent
e7f1a86172
commit
8056b67072
9 changed files with 258 additions and 297 deletions
|
@ -24,20 +24,7 @@ import {
|
|||
import { Input } from "@/components/ui/input";
|
||||
import { logsSchema } from "@/lib/validations/validation";
|
||||
import { z } from "zod";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import dynamic from "next/dynamic";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
const MarkdownEditor = dynamic(() => import("@uiw/react-md-editor"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
interface LogEntry {
|
||||
_id: string;
|
||||
|
@ -46,54 +33,13 @@ interface LogEntry {
|
|||
bullets: { point: string }[];
|
||||
}
|
||||
|
||||
interface PageEntry {
|
||||
title: string;
|
||||
slug: string;
|
||||
content: string; // Add content to the PageEntry interface
|
||||
}
|
||||
|
||||
type LogsFormValues = z.infer<typeof logsSchema>;
|
||||
|
||||
const AdminLogPage = () => {
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
const [pages, setPages] = useState<PageEntry[]>([]);
|
||||
const [error, setError] = useState("");
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [pageTitle, setPageTitle] = useState("");
|
||||
const [selectedPage, setSelectedPage] = useState<PageEntry | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/mdx/pages")
|
||||
.then((response) => response.json())
|
||||
.then((data) => setPages(data))
|
||||
.catch((error) => console.error("Failed to load pages", error));
|
||||
}, []);
|
||||
|
||||
const createPage = async () => {
|
||||
setLoading(true);
|
||||
const slug = pageTitle.toLowerCase().replace(/\s+/g, "-");
|
||||
const response = await fetch("/api/mdx/pages", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ title: pageTitle, slug, content: "" }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const newPage = await response.json();
|
||||
setPages([...pages, newPage]);
|
||||
setPageTitle("");
|
||||
setOpen(false);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.error("Failed to create page");
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const form = useForm<LogsFormValues>({
|
||||
resolver: zodResolver(logsSchema),
|
||||
|
@ -123,31 +69,6 @@ const AdminLogPage = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const fetchPages = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/mdx/pages", { method: "GET" });
|
||||
if (response.ok) {
|
||||
const data: PageEntry[] = await response.json();
|
||||
setPages(data);
|
||||
} else {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
setError(error.message || "Failed to fetch pages");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchLogs();
|
||||
fetchPages();
|
||||
const interval = setInterval(() => {
|
||||
fetchLogs();
|
||||
fetchPages();
|
||||
}, 10000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const onSubmit: SubmitHandler<LogsFormValues> = async (data) => {
|
||||
setLoading(true);
|
||||
const response = await fetch("/api/uploadlogs", {
|
||||
|
@ -182,6 +103,15 @@ const AdminLogPage = () => {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchLogs();
|
||||
const interval = setInterval(() => {
|
||||
fetchLogs();
|
||||
}, 10000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section id="logs-page" className="wrapper container">
|
||||
<h1 className="text-3xl font-bold py-6">Server Logs Form</h1>
|
||||
|
@ -258,71 +188,6 @@ const AdminLogPage = () => {
|
|||
</form>
|
||||
</Form>
|
||||
|
||||
<section className="container mx-auto p-4">
|
||||
{/* <h1 className="text-3xl font-bold">Changelog Management</h1> */}
|
||||
<section id="create-page" className="py-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-2">
|
||||
Multi Log Page
|
||||
</h2>
|
||||
<Button variant={"secondary"} onClick={() => setOpen(true)}>
|
||||
Create New Page
|
||||
</Button>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Enter Page Title</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
value={pageTitle}
|
||||
onChange={(e) => setPageTitle(e.target.value)}
|
||||
placeholder="Page Title"
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button disabled={loading} onClick={createPage}>
|
||||
Continue
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</section>
|
||||
<section id="pages-list" className="pb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold">Existing Pages</h2>
|
||||
<p className="mb-4">Total Pages: {pages.length}</p>
|
||||
<Table className="w-full mt-4 border-muted">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="border-b px-4 py-2">Slug</TableHead>
|
||||
<TableHead className="border-b px-4 py-2">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{pages.map((page) => (
|
||||
<TableRow key={page.slug}>
|
||||
<TableCell className="border-b px-4 py-2">
|
||||
<a
|
||||
href={`/changelogs/${page.slug}`}
|
||||
className="text-blue-500 underline"
|
||||
>
|
||||
{page.slug}
|
||||
</a>
|
||||
</TableCell>
|
||||
<TableCell className="border-b px-4 py-2">
|
||||
<Button
|
||||
variant={"outline"}
|
||||
onClick={() =>
|
||||
router.push(`/admin/changelogs/${page.slug}`)
|
||||
}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
{/* Section to list and delete logs */}
|
||||
<section id="logs-list" className="py-16 md:py-24">
|
||||
<h2 className="text-3xl md:text-4xl font-bold">Existing Logs</h2>
|
||||
|
@ -360,52 +225,6 @@ const AdminLogPage = () => {
|
|||
</TableBody>
|
||||
</Table>
|
||||
</section>
|
||||
|
||||
{/* Section to edit selected page */}
|
||||
{selectedPage && (
|
||||
<section id="edit-page" className="py-16 md:py-24">
|
||||
<h2 className="text-3xl md:text-4xl font-bold">Edit Page</h2>
|
||||
<MarkdownEditor
|
||||
value={selectedPage.content}
|
||||
onChange={(value) => {
|
||||
if (value !== undefined) {
|
||||
setSelectedPage((prev) => ({
|
||||
...prev!,
|
||||
content: value,
|
||||
}));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className="mt-4"
|
||||
onClick={async () => {
|
||||
if (selectedPage) {
|
||||
const response = await fetch(
|
||||
`/api/mdx/pages/${selectedPage.slug}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
...selectedPage,
|
||||
content: selectedPage.content,
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (response.ok) {
|
||||
toast({ description: "Page successfully updated" });
|
||||
} else {
|
||||
toast({
|
||||
description: "Page update failed",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</section>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -32,18 +32,16 @@ const EditPage = ({ params }: { params: { slug: string } }) => {
|
|||
|
||||
const savePage = async () => {
|
||||
setLoading(true);
|
||||
const response = await fetch(`/api/mdx/pages/${slug}`, {
|
||||
const response = await fetch(`/api/mdx/pages/${slug}?slug=${slug}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ title, content }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setLoading(false);
|
||||
toast({ description: "Page successfully updated" });
|
||||
router.push("/admin/changelogs");
|
||||
router.push("/admin/multi-logs");
|
||||
} else {
|
||||
setLoading(false);
|
||||
toast({ description: "Page update failed", variant: "destructive" });
|
133
app/(auth)/admin/multi-logs/page.tsx
Normal file
133
app/(auth)/admin/multi-logs/page.tsx
Normal file
|
@ -0,0 +1,133 @@
|
|||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
interface PageEntry {
|
||||
title: string;
|
||||
slug: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const AdminLogPage = () => {
|
||||
const [pages, setPages] = useState<PageEntry[]>([]);
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [pageTitle, setPageTitle] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/mdx/pages")
|
||||
.then((response) => response.json())
|
||||
.then((data) => setPages(data))
|
||||
.catch((error) => console.error("Failed to load pages", error));
|
||||
}, []);
|
||||
|
||||
const createPage = async () => {
|
||||
setLoading(true);
|
||||
const slug = pageTitle.toLowerCase().replace(/\s+/g, "-");
|
||||
const response = await fetch("/api/mdx/pages", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ title: pageTitle, slug, content: "" }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const newPage = await response.json();
|
||||
setPages([...pages, newPage]);
|
||||
setPageTitle("");
|
||||
setOpen(false);
|
||||
setLoading(false);
|
||||
toast({ description: "Page created successfully" });
|
||||
} else {
|
||||
console.error("Failed to create page");
|
||||
toast({ description: "Some Error Occured" });
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="logs-page" className="wrapper container">
|
||||
<section id="create-page" className="py-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-2">Create New Page</h2>
|
||||
<Button variant={"secondary"} onClick={() => setOpen(true)}>
|
||||
Create New Page
|
||||
</Button>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Enter Page Title</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
value={pageTitle}
|
||||
onChange={(e) => setPageTitle(e.target.value)}
|
||||
placeholder="Page Title"
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button disabled={loading} onClick={createPage}>
|
||||
Continue
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</section>
|
||||
<section id="pages-list" className="pb-16">
|
||||
<h2 className="text-3xl md:text-4xl font-bold">Existing Pages</h2>
|
||||
<p className="mb-4">Total Pages: {pages.length}</p>
|
||||
<Table className="w-full mt-4 border-muted">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="border-b px-4 py-2">Slug</TableHead>
|
||||
<TableHead className="border-b px-4 py-2">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{pages.map((page) => (
|
||||
<TableRow key={page.slug}>
|
||||
<TableCell className="border-b px-4 py-2">
|
||||
<a
|
||||
href={`/changelogs/${page.slug}`}
|
||||
className="text-blue-500 underline"
|
||||
>
|
||||
{page.slug}
|
||||
</a>
|
||||
</TableCell>
|
||||
<TableCell className="border-b px-4 py-2">
|
||||
<Button
|
||||
variant={"outline"}
|
||||
onClick={() =>
|
||||
router.push(`/admin/multi-logs/${page.slug}`)
|
||||
}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminLogPage;
|
|
@ -11,7 +11,7 @@ const AdminPage = () => {
|
|||
<Card title="Downloads" url="/admin/downloads" />
|
||||
<Card title="Mods" url="/admin/mods" />
|
||||
<Card title="Logs" url="/admin/changelogs" />
|
||||
<Card title="Docs" url="/admin" />
|
||||
<Card title="MultiLogs" url="/admin/multi-logs" />
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
|
|
|
@ -8,15 +8,41 @@ const Page = ({ params }: { params: { slug: string } }) => {
|
|||
const [page, setPage] = useState<{ title: string; content: string } | null>(
|
||||
null
|
||||
);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [notFound, setNotFound] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`/api/mdx/pages/${slug}`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => setPage(data))
|
||||
.catch((error) => console.error("Failed to load page", error));
|
||||
const fetchPage = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/mdx/pages/${slug}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setPage(data);
|
||||
} else {
|
||||
if (response.status === 404) {
|
||||
setNotFound(true);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load page", error);
|
||||
setNotFound(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPage();
|
||||
}, [slug]);
|
||||
|
||||
if (!page) {
|
||||
if (loading) {
|
||||
return (
|
||||
<section className="flex-center flex-col wrapper container">
|
||||
<p className="text-lg">Loading...</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (notFound) {
|
||||
return (
|
||||
<section id="404error" className="flex-center flex-col wrapper container">
|
||||
<h1 className="text-3xl md:text-5xl text-center">
|
||||
|
@ -29,10 +55,18 @@ const Page = ({ params }: { params: { slug: string } }) => {
|
|||
);
|
||||
}
|
||||
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="container mx-auto p-4">
|
||||
<h1 className="text-3xl font-bold py-6">{page.title}</h1>
|
||||
<ReactMarkdown>{page.content}</ReactMarkdown>
|
||||
<section className="wrapper container py-24 md:py-28 gap-4 flex flex-col">
|
||||
<h1 className="text-3xl md:text-5xl font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
|
||||
{page.title}
|
||||
</h1>
|
||||
<ReactMarkdown className="prose max-w-full prose-lg dark:prose-invert">
|
||||
{page.content}
|
||||
</ReactMarkdown>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,54 +1,22 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import clientPromise from "@/lib/db";
|
||||
|
||||
export async function GET(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { slug } = req.query;
|
||||
try {
|
||||
export const GET = async (
|
||||
req: NextRequest,
|
||||
{ params }: { params: { slug: string } }
|
||||
) => {
|
||||
const client = await clientPromise;
|
||||
const db = client.db();
|
||||
const { slug } = params;
|
||||
|
||||
if (!slug) {
|
||||
return NextResponse.json({ message: "Slug is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const page = await db.collection("pages").findOne({ slug });
|
||||
if (page) {
|
||||
res.status(200).json(page);
|
||||
return NextResponse.json(page, { status: 200 });
|
||||
} else {
|
||||
res.status(404).json({ error: "Page not found" });
|
||||
return NextResponse.json({ message: "Page not found" }, { status: 404 });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: "Failed to load page" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { slug } = req.query;
|
||||
const { title, content } = req.body;
|
||||
try {
|
||||
const client = await clientPromise;
|
||||
const db = client.db();
|
||||
const result = await db
|
||||
.collection("pages")
|
||||
.updateOne({ slug }, { $set: { title, content } });
|
||||
if (result.matchedCount > 0) {
|
||||
const updatedPage = await db.collection("pages").findOne({ slug });
|
||||
res.status(200).json(updatedPage);
|
||||
} else {
|
||||
res.status(404).json({ error: "Page not found" });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: "Failed to update page" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { slug } = req.query;
|
||||
try {
|
||||
const client = await clientPromise;
|
||||
const db = client.db();
|
||||
const result = await db.collection("pages").deleteOne({ slug });
|
||||
if (result.deletedCount > 0) {
|
||||
res.status(200).json({ message: "Page successfully deleted" });
|
||||
} else {
|
||||
res.status(404).json({ error: "Page not found" });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: "Failed to delete page" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,46 +1,55 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import clientPromise from "@/lib/db";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
export const GET = async () => {
|
||||
const client = await clientPromise;
|
||||
const db = client.db();
|
||||
const pages = await db.collection("pages").find({}).toArray();
|
||||
const pages = await db.collection("pages").find().toArray();
|
||||
return NextResponse.json(pages, { status: 200 });
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to load pages" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { title, slug, content } = body;
|
||||
};
|
||||
|
||||
export const PUT = async (
|
||||
req: NextRequest,
|
||||
{ params }: { params: { slug: string } }
|
||||
) => {
|
||||
const client = await clientPromise;
|
||||
const db = client.db();
|
||||
const { slug } = params;
|
||||
const { title, content } = await req.json();
|
||||
|
||||
if (!slug) {
|
||||
return NextResponse.json({ message: "Slug is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const result = await db
|
||||
.collection("pages")
|
||||
.insertOne({ title, slug, content });
|
||||
.findOneAndUpdate(
|
||||
{ slug },
|
||||
{ $set: { title, content } },
|
||||
{ returnDocument: "after" }
|
||||
);
|
||||
|
||||
if (result.acknowledged) {
|
||||
const newPage = await db
|
||||
.collection("pages")
|
||||
.findOne({ _id: result.insertedId });
|
||||
return NextResponse.json(newPage, { status: 201 });
|
||||
if (result && result.value) {
|
||||
const page = result.value;
|
||||
return NextResponse.json(page, { status: 200 });
|
||||
} else {
|
||||
return NextResponse.json({ message: "Page not found" }, { status: 404 });
|
||||
}
|
||||
};
|
||||
|
||||
export const POST = async (req: NextRequest) => {
|
||||
const client = await clientPromise;
|
||||
const db = client.db();
|
||||
const { title, slug, content } = await req.json();
|
||||
|
||||
if (!title || !slug || !content) {
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to create page" },
|
||||
{ status: 500 }
|
||||
{ message: "Missing required fields" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to create page" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const newPage = { title, slug, content };
|
||||
const result = await db.collection("pages").insertOne(newPage);
|
||||
return NextResponse.json(newPage, { status: 201 });
|
||||
};
|
||||
|
|
0
lib/models/multiLogs.ts
Normal file
0
lib/models/multiLogs.ts
Normal file
Loading…
Reference in a new issue