UNPKG

@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
"use strict"; "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))) })] }) })] }) })); }