Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions app/api/approve-blog/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@/utils/supabase/server";
import { withAuth } from "@/utils/withAuth";
// Todo only admin can approve blog
async function updateBlogStatus(request: NextRequest) {
const supabase = createClient();

try {
const { id, status } = await request.json();

const { data, error } = await supabase
.from("blog")
.update({ status: status })
.eq("id", id);

if (error) {
console.error("Error updating blog status:", error);
return NextResponse.json(
{ error: "Failed to update blog status" },
{ status: 500 },
);
}

return NextResponse.json({
message: "Blog status updated successfully",
data,
});
} catch (error) {
console.error("Error parsing request body:", error);
return NextResponse.json(
{ error: "Invalid request body" },
{ status: 400 },
);
}
}

async function getBlog(request: NextRequest) {
const supabase = createClient();

try {
const { data, error } = await supabase
.from("blog")
.select("*")
.eq("status", "pending");

if (error) {
return NextResponse.json(
{ error: "failed to fetch blogs" },
{ status: 500 },
);
}

return NextResponse.json(data, { status: 200 });
} catch (error) {
console.error("Error parsing request body:", error);
return NextResponse.json(
{ error: "Invalid request body" },
{ status: 400 },
);
}
}

export const POST = updateBlogStatus;
export const GET = getBlog;
47 changes: 47 additions & 0 deletions app/api/submit-blog/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { NextRequest, NextResponse } from "next/server";
import { withAuth } from "@/utils/withAuth";
import { createClient } from "@/utils/supabase/server";

// TODO add rate limiting to this endpoint to prevent spam submissions
async function submitBlog(request: NextRequest) {
const supabase = createClient();

try {
const { title, url, excerpt, date, author, tag, content, readingTime } =
await request.json();
console.log("request", request);

const { data: blog, error } = await supabase.from("blog").insert([
{
title,
url,
excerpt,
date,
author,
readingtime: readingTime,
tag,
content,
status: "pending",
},
]);
console.log("blog", blog);

if (error) {
console.error("error submitting blog", error);
return NextResponse.json(
{ error: "Error submitting blog. Please contact PearAI team." },
{ status: 500 },
);
}

return NextResponse.json({ blog });
} catch (error) {
console.error("error submitting blog", error);
return NextResponse.json(
{ error: "Error submitting blog. Please contact PearAI team." },
{ status: 500 },
);
}
}

export const POST = withAuth(submitBlog);
173 changes: 173 additions & 0 deletions app/blog/secret-admin-page/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
"use client";

import React, { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { MoreHorizontal, Loader2 } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

interface Blog {
id: number;
title: string;
url: string;
excerpt: string;
date: string;
author: string;
tag: string | null;
content: string;
readingtime: number;
status: string;
}

export default function BlogApproval() {
const [blogs, setBlogs] = useState<Blog[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
fetchBlogs();
}, []);

const fetchBlogs = async () => {
try {
const response = await fetch("/api/approve-blog");
if (!response.ok) {
throw new Error("Failed to fetch blogs");
}
const data = await response.json();
setBlogs(data);
} catch (err) {
setError("Failed to load blogs. Please try again.");
} finally {
setIsLoading(false);
}
};

const handleApprove = async (blogId: number) => {
try {
const response = await fetch("/api/approve-blog", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ id: blogId, status: "approved" }),
});

if (!response.ok) {
throw new Error("Failed to approve blog");
}

// Update the local state to reflect the change
} catch (err) {
setError("Failed to approve blog.");
}
};

const handleReject = async (blogId: number) => {
try {
const response = await fetch("/api/approve-blog", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ id: blogId, status: "rejected" }),
});

if (!response.ok) {
throw new Error("Failed to reject blog");
}

// Update the local state to reflect the change
} catch (err) {
setError("Failed to reject blog.");
}
};

if (isLoading) {
return (
<div className="flex h-screen items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin" />
</div>
);
}

if (error) {
return (
<div className="flex h-screen items-center justify-center">{error}</div>
);
}

return (
<Card className="mt-20 w-full">
<CardHeader>
<CardTitle>Approve Blogs</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Title</TableHead>
<TableHead>URL</TableHead>
<TableHead>Excerpt</TableHead>
<TableHead>Date</TableHead>
<TableHead>Author</TableHead>
<TableHead>Tag</TableHead>
<TableHead>Content</TableHead>
<TableHead>Reading Time</TableHead>
<TableHead>Status</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{blogs.map((blog) => (
<TableRow key={blog.id}>
<TableCell>{blog.id}</TableCell>
<TableCell>{blog.title}</TableCell>
<TableCell>{blog.url}</TableCell>
<TableCell>{blog.excerpt}</TableCell>
<TableCell>{blog.date}</TableCell>
<TableCell>{blog.author}</TableCell>
<TableCell>{blog.tag || "N/A"}</TableCell>
<TableCell>{blog.content.substring(0, 50)}...</TableCell>
<TableCell>{blog.readingtime} min</TableCell>
<TableCell>{blog.status}</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleApprove(blog.id)}>
Approve
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleReject(blog.id)}>
Reject
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
);
}
Loading