@asgerami/zemenay-blog
Version:
Plug-and-play blog system for Next.js - Get a fully functional blog running in minutes with zero configuration
247 lines (246 loc) • 18.4 kB
JavaScript
;
"use client";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminPanel = AdminPanel;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const supabase_1 = require("../lib/supabase");
const categories_1 = require("../lib/categories");
const CategoryTag_1 = require("./CategoryTag");
const LoadingStates_1 = require("./LoadingStates");
const RichTextEditor_1 = require("./RichTextEditor");
function AdminPanel({ className = "" }) {
const [user, setUser] = (0, react_1.useState)(null);
const [posts, setPosts] = (0, react_1.useState)([]);
const [loading, setLoading] = (0, react_1.useState)(true);
const [editingPost, setEditingPost] = (0, react_1.useState)(null);
const [showCreateForm, setShowCreateForm] = (0, react_1.useState)(false);
const [formData, setFormData] = (0, react_1.useState)({
title: "",
content: "",
slug: "",
});
// Categories and Tags state
const [categories, setCategories] = (0, react_1.useState)([]);
const [tags, setTags] = (0, react_1.useState)([]);
const [selectedCategories, setSelectedCategories] = (0, react_1.useState)([]);
const [selectedTags, setSelectedTags] = (0, react_1.useState)([]);
const [newCategoryName, setNewCategoryName] = (0, react_1.useState)("");
const [newTagName, setNewTagName] = (0, react_1.useState)("");
// Auth state
const [email, setEmail] = (0, react_1.useState)("");
const [password, setPassword] = (0, react_1.useState)("");
const [authLoading, setAuthLoading] = (0, react_1.useState)(false);
(0, react_1.useEffect)(() => {
checkUser();
fetchPosts();
loadCategoriesAndTags();
}, []);
async function loadCategoriesAndTags() {
try {
const [categoriesData, tagsData] = await Promise.all([
(0, categories_1.fetchCategories)(),
(0, categories_1.fetchTags)(),
]);
setCategories(categoriesData);
setTags(tagsData);
}
catch (error) {
console.error("Error loading categories and tags:", error);
}
}
async function checkUser() {
try {
const supabase = (0, supabase_1.getSupabaseClient)();
const { data: { user }, } = await supabase.auth.getUser();
setUser(user);
}
catch (error) {
console.error("Error checking user:", error);
}
finally {
setLoading(false);
}
}
async function signIn() {
setAuthLoading(true);
try {
const supabase = (0, supabase_1.getSupabaseClient)();
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error)
throw error;
await checkUser();
}
catch (error) {
alert("Login failed: " + error.message);
}
finally {
setAuthLoading(false);
}
}
async function signOut() {
try {
const supabase = (0, supabase_1.getSupabaseClient)();
await supabase.auth.signOut();
setUser(null);
}
catch (error) {
console.error("Error signing out:", error);
}
}
async function fetchPosts() {
try {
const supabase = (0, supabase_1.getSupabaseClient)();
const { data, error } = await supabase
.from("posts")
.select("*")
.order("created_at", { ascending: false });
if (error)
throw error;
setPosts(data || []);
}
catch (error) {
console.error("Error fetching posts:", error);
}
}
async function createPost() {
try {
const supabase = (0, supabase_1.getSupabaseClient)();
const { data: newPost, error } = await supabase
.from("posts")
.insert([formData])
.select()
.single();
if (error)
throw error;
// Add categories and tags to the new post
if (newPost) {
await Promise.all([
(0, categories_1.addCategoriesToPost)(newPost.id, selectedCategories),
(0, categories_1.addTagsToPost)(newPost.id, selectedTags),
]);
}
// Reset form
setFormData({ title: "", content: "", slug: "" });
setSelectedCategories([]);
setSelectedTags([]);
setShowCreateForm(false);
fetchPosts();
}
catch (error) {
alert("Error creating post: " + error.message);
}
}
async function updatePost(id, data) {
try {
const supabase = (0, supabase_1.getSupabaseClient)();
const { error } = await supabase
.from("posts")
.update(Object.assign(Object.assign({}, data), { updated_at: new Date().toISOString() }))
.eq("id", id);
if (error)
throw error;
// Update categories and tags for the post
await Promise.all([
(0, categories_1.addCategoriesToPost)(id, selectedCategories),
(0, categories_1.addTagsToPost)(id, selectedTags),
]);
// Reset selection
setSelectedCategories([]);
setSelectedTags([]);
setEditingPost(null);
fetchPosts();
}
catch (error) {
alert("Error updating post: " + error.message);
}
}
// Handle category selection
const toggleCategory = (categoryId) => {
setSelectedCategories((prev) => prev.includes(categoryId)
? prev.filter((id) => id !== categoryId)
: [...prev, categoryId]);
};
// Handle tag selection
const toggleTag = (tagId) => {
setSelectedTags((prev) => prev.includes(tagId)
? prev.filter((id) => id !== tagId)
: [...prev, tagId]);
};
// Create new category
const handleCreateCategory = async () => {
if (!newCategoryName.trim())
return;
const newCategory = await (0, categories_1.createCategory)(newCategoryName.trim());
if (newCategory) {
setCategories((prev) => [...prev, newCategory]);
setNewCategoryName("");
}
};
// Create new tag
const handleCreateTag = async () => {
if (!newTagName.trim())
return;
const newTag = await (0, categories_1.createTag)(newTagName.trim());
if (newTag) {
setTags((prev) => [...prev, newTag]);
setNewTagName("");
}
};
// Load existing categories and tags when editing a post
const startEditing = async (post) => {
var _a, _b;
setEditingPost(post);
// Load existing categories and tags for this post
try {
const supabase = (0, supabase_1.getSupabaseClient)();
const [postCategories, postTags] = await Promise.all([
supabase
.from("post_categories")
.select("category_id")
.eq("post_id", post.id),
supabase.from("post_tags").select("tag_id").eq("post_id", post.id),
]);
setSelectedCategories(((_a = postCategories.data) === null || _a === void 0 ? void 0 : _a.map((pc) => pc.category_id)) ||
[]);
setSelectedTags(((_b = postTags.data) === null || _b === void 0 ? void 0 : _b.map((pt) => pt.tag_id)) || []);
}
catch (error) {
console.error("Error loading post categories and tags:", error);
}
};
async function deletePost(id) {
if (!confirm("Are you sure you want to delete this post?"))
return;
try {
const supabase = (0, supabase_1.getSupabaseClient)();
const { error } = await supabase.from("posts").delete().eq("id", id);
if (error)
throw error;
fetchPosts();
}
catch (error) {
alert("Error deleting post: " + error.message);
}
}
if (loading) {
return (0, jsx_runtime_1.jsx)(LoadingStates_1.AdminPanelSkeleton, { className: className });
}
if (!user) {
return ((0, jsx_runtime_1.jsx)(LoadingStates_1.FadeIn, { children: (0, jsx_runtime_1.jsx)("div", { className: `zemenay-admin-panel ${className}`, children: (0, jsx_runtime_1.jsx)(LoadingStates_1.SlideIn, { direction: "down", delay: 100, children: (0, jsx_runtime_1.jsxs)("div", { className: "auth-form", children: [(0, jsx_runtime_1.jsx)("h2", { children: "Admin Login" }), (0, jsx_runtime_1.jsx)("input", { type: "email", placeholder: "Email", value: email, onChange: (e) => setEmail(e.target.value), className: "smooth-transition" }), (0, jsx_runtime_1.jsx)("input", { type: "password", placeholder: "Password", value: password, onChange: (e) => setPassword(e.target.value), className: "smooth-transition" }), (0, jsx_runtime_1.jsx)("button", { onClick: signIn, disabled: authLoading, className: "pulse-on-hover", children: authLoading ? "Signing in..." : "Sign In" })] }) }) }) }));
}
return ((0, jsx_runtime_1.jsx)(LoadingStates_1.FadeIn, { children: (0, jsx_runtime_1.jsxs)("div", { className: `zemenay-admin-panel ${className}`, children: [(0, jsx_runtime_1.jsx)(LoadingStates_1.SlideIn, { direction: "down", delay: 100, children: (0, jsx_runtime_1.jsxs)("div", { className: "admin-header", children: [(0, jsx_runtime_1.jsxs)("div", { className: "admin-title-section", children: [(0, jsx_runtime_1.jsx)("h1", { children: "Blog Admin" }), (0, jsx_runtime_1.jsxs)("nav", { className: "admin-nav", children: [(0, jsx_runtime_1.jsx)("a", { href: "/admin", className: "nav-link", children: "\uD83D\uDCDD Posts" }), (0, jsx_runtime_1.jsx)("a", { href: "/admin/analytics", className: "nav-link", children: "\uD83D\uDCCA Analytics" })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "admin-actions", children: [(0, jsx_runtime_1.jsxs)("span", { children: ["Welcome, ", user.email] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setShowCreateForm(true), className: "pulse-on-hover", children: "New Post" }), (0, jsx_runtime_1.jsx)("button", { onClick: signOut, className: "pulse-on-hover", children: "Sign Out" })] })] }) }), showCreateForm && ((0, jsx_runtime_1.jsx)(LoadingStates_1.SlideIn, { direction: "up", delay: 200, children: (0, jsx_runtime_1.jsxs)("div", { className: "post-form", children: [(0, jsx_runtime_1.jsx)("h3", { children: "Create New Post" }), (0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "Title", value: formData.title, onChange: (e) => setFormData(Object.assign(Object.assign({}, formData), { title: e.target.value })), className: "smooth-transition" }), (0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "Slug", value: formData.slug, onChange: (e) => setFormData(Object.assign(Object.assign({}, formData), { slug: e.target.value })), className: "smooth-transition" }), (0, jsx_runtime_1.jsx)(RichTextEditor_1.RichTextEditor, { content: formData.content, onChange: (content) => setFormData(Object.assign(Object.assign({}, formData), { content })), placeholder: "Start writing your blog post..." }), (0, jsx_runtime_1.jsx)(LoadingStates_1.FadeIn, { delay: 300, children: (0, jsx_runtime_1.jsxs)("div", { className: "category-tag-manager", children: [(0, jsx_runtime_1.jsx)("h4", { children: "Categories" }), (0, jsx_runtime_1.jsx)("div", { className: "category-tag-selector", children: categories.map((category) => ((0, jsx_runtime_1.jsx)(CategoryTag_1.CategoryBadge, { category: category, onClick: () => toggleCategory(category.id), className: selectedCategories.includes(category.id)
? "selected"
: "" }, category.id))) }), (0, jsx_runtime_1.jsxs)("div", { className: "add-category-tag", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "New category name", value: newCategoryName, onChange: (e) => setNewCategoryName(e.target.value), onKeyPress: (e) => e.key === "Enter" && handleCreateCategory(), className: "smooth-transition" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleCreateCategory, className: "pulse-on-hover", children: "Add Category" })] }), (0, jsx_runtime_1.jsx)("h4", { children: "Tags" }), (0, jsx_runtime_1.jsx)("div", { className: "category-tag-selector", children: tags.map((tag) => ((0, jsx_runtime_1.jsx)(CategoryTag_1.TagBadge, { tag: tag, onClick: () => toggleTag(tag.id), className: selectedTags.includes(tag.id) ? "selected" : "" }, tag.id))) }), (0, jsx_runtime_1.jsxs)("div", { className: "add-category-tag", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "New tag name", value: newTagName, onChange: (e) => setNewTagName(e.target.value), onKeyPress: (e) => e.key === "Enter" && handleCreateTag(), className: "smooth-transition" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleCreateTag, className: "pulse-on-hover", children: "Add Tag" })] })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "form-actions", children: [(0, jsx_runtime_1.jsx)("button", { onClick: createPost, className: "pulse-on-hover", children: "Create Post" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setShowCreateForm(false), className: "pulse-on-hover", children: "Cancel" })] })] }) })), (0, jsx_runtime_1.jsx)(LoadingStates_1.SlideIn, { direction: "up", delay: 300, children: (0, jsx_runtime_1.jsxs)("div", { className: "posts-list", children: [(0, jsx_runtime_1.jsx)("h3", { children: "Posts" }), (0, jsx_runtime_1.jsx)("div", { className: "stagger-animation", children: posts.map((post, index) => ((0, jsx_runtime_1.jsx)(LoadingStates_1.SlideIn, { direction: "up", delay: index * 50 + 400, children: (0, jsx_runtime_1.jsx)("div", { className: "post-item pulse-on-hover", children: (editingPost === null || editingPost === void 0 ? void 0 : editingPost.id) === post.id ? ((0, jsx_runtime_1.jsxs)("div", { className: "post-form", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", value: editingPost.title, onChange: (e) => setEditingPost(Object.assign(Object.assign({}, editingPost), { title: e.target.value })), className: "smooth-transition" }), (0, jsx_runtime_1.jsx)("input", { type: "text", value: editingPost.slug, onChange: (e) => setEditingPost(Object.assign(Object.assign({}, editingPost), { slug: e.target.value })), className: "smooth-transition" }), (0, jsx_runtime_1.jsx)(RichTextEditor_1.RichTextEditor, { content: editingPost.content, onChange: (content) => setEditingPost(Object.assign(Object.assign({}, editingPost), { content })), placeholder: "Edit your blog post..." }), (0, jsx_runtime_1.jsx)(LoadingStates_1.FadeIn, { delay: 200, children: (0, jsx_runtime_1.jsxs)("div", { className: "category-tag-manager", children: [(0, jsx_runtime_1.jsx)("h4", { children: "Categories" }), (0, jsx_runtime_1.jsx)("div", { className: "category-tag-selector", children: categories.map((category) => ((0, jsx_runtime_1.jsx)(CategoryTag_1.CategoryBadge, { category: category, onClick: () => toggleCategory(category.id), className: selectedCategories.includes(category.id)
? "selected"
: "" }, category.id))) }), (0, jsx_runtime_1.jsxs)("div", { className: "add-category-tag", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "New category name", value: newCategoryName, onChange: (e) => setNewCategoryName(e.target.value), onKeyPress: (e) => e.key === "Enter" && handleCreateCategory(), className: "smooth-transition" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleCreateCategory, className: "pulse-on-hover", children: "Add Category" })] }), (0, jsx_runtime_1.jsx)("h4", { children: "Tags" }), (0, jsx_runtime_1.jsx)("div", { className: "category-tag-selector", children: tags.map((tag) => ((0, jsx_runtime_1.jsx)(CategoryTag_1.TagBadge, { tag: tag, onClick: () => toggleTag(tag.id), className: selectedTags.includes(tag.id)
? "selected"
: "" }, tag.id))) }), (0, jsx_runtime_1.jsxs)("div", { className: "add-category-tag", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "New tag name", value: newTagName, onChange: (e) => setNewTagName(e.target.value), onKeyPress: (e) => e.key === "Enter" && handleCreateTag(), className: "smooth-transition" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleCreateTag, className: "pulse-on-hover", children: "Add Tag" })] })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "form-actions", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => updatePost(post.id, {
title: editingPost.title,
content: editingPost.content,
slug: editingPost.slug,
}), className: "pulse-on-hover", children: "Save" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setEditingPost(null), className: "pulse-on-hover", children: "Cancel" })] })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "post-preview", children: [(0, jsx_runtime_1.jsx)("h4", { children: post.title }), (0, jsx_runtime_1.jsxs)("p", { children: ["Slug: ", post.slug] }), (0, jsx_runtime_1.jsxs)("p", { children: ["Created:", " ", new Date(post.created_at).toLocaleDateString()] }), (0, jsx_runtime_1.jsxs)("div", { className: "post-actions", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => startEditing(post), className: "pulse-on-hover", children: "Edit" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => deletePost(post.id), className: "pulse-on-hover", children: "Delete" })] })] })) }) }, post.id))) })] }) })] }) }));
}