From 402830ac55c327d369e212fbeae39ae64e9e0690 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Thu, 13 Nov 2025 06:20:47 +0000 Subject: [PATCH] Admin Feed Management page - Create and manage community posts cgen-dd64993cfb9c4e4faac47333676d9459 --- client/pages/AdminFeed.tsx | 421 +++++++++++++++++++++++++++++++++++++ 1 file changed, 421 insertions(+) create mode 100644 client/pages/AdminFeed.tsx diff --git a/client/pages/AdminFeed.tsx b/client/pages/AdminFeed.tsx new file mode 100644 index 00000000..c9e3e986 --- /dev/null +++ b/client/pages/AdminFeed.tsx @@ -0,0 +1,421 @@ +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import Layout from "@/components/Layout"; +import SEO from "@/components/SEO"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { Badge } from "@/components/ui/badge"; +import { useAuth } from "@/contexts/AuthContext"; +import { aethexToast } from "@/lib/aethex-toast"; +import LoadingScreen from "@/components/LoadingScreen"; +import ArmPostCard, { ArmType } from "@/components/feed/ArmPostCard"; +import { PenTool, Trash2, Eye, Lock } from "lucide-react"; + +const API_BASE = import.meta.env.VITE_API_BASE || ""; + +const ARM_OPTIONS: { id: ArmType; label: string }[] = [ + { id: "labs", label: "Labs" }, + { id: "gameforge", label: "GameForge" }, + { id: "corp", label: "Corp" }, + { id: "foundation", label: "Foundation" }, + { id: "devlink", label: "Dev-Link" }, + { id: "nexus", label: "Nexus" }, + { id: "staff", label: "Staff" }, +]; + +interface AdminPost { + id: string; + title: string; + content: string; + arm_affiliation: ArmType; + author_id: string; + created_at: string; + is_published: boolean; + tags?: string[]; + category?: string; + user_profiles?: { + id: string; + username?: string; + full_name?: string; + avatar_url?: string; + }; +} + +export default function AdminFeed() { + const { user, roles, loading: authLoading } = useAuth(); + const navigate = useNavigate(); + + const hasAccess = roles.includes("admin") || roles.includes("staff"); + + // Form state + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); + const [armAffiliation, setArmAffiliation] = useState("labs"); + const [tags, setTags] = useState(""); + const [category, setCategory] = useState(""); + const [isPublished, setIsPublished] = useState(true); + const [isSubmitting, setIsSubmitting] = useState(false); + + // Posts list state + const [posts, setPosts] = useState([]); + const [isLoadingPosts, setIsLoadingPosts] = useState(true); + + // Load all posts + const loadPosts = async () => { + setIsLoadingPosts(true); + try { + const response = await fetch(`${API_BASE}/api/admin/feed`); + if (response.ok) { + const data = await response.json(); + setPosts(data.posts || []); + } + } catch (error) { + console.error("Failed to load posts:", error); + aethexToast.error({ + title: "Failed to load posts", + description: "Please try again", + }); + } finally { + setIsLoadingPosts(false); + } + }; + + useEffect(() => { + if (!authLoading && (!user || !hasAccess)) { + navigate("/login", { replace: true }); + } else { + loadPosts(); + } + }, [user, hasAccess, authLoading, navigate]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!title.trim() || !content.trim()) { + aethexToast.error({ + title: "Validation error", + description: "Title and content are required", + }); + return; + } + + setIsSubmitting(true); + try { + const tagsArray = tags + .split(",") + .map((t) => t.trim()) + .filter((t) => t.length > 0); + + const response = await fetch(`${API_BASE}/api/admin/feed`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + title, + content, + arm_affiliation: armAffiliation, + author_id: user?.id, + tags: tagsArray, + category: category || null, + is_published: isPublished, + }), + }); + + if (response.ok) { + aethexToast.success({ + title: "Post created", + description: "Your post has been published successfully", + }); + + // Reset form + setTitle(""); + setContent(""); + setArmAffiliation("labs"); + setTags(""); + setCategory(""); + setIsPublished(true); + + // Reload posts + loadPosts(); + } else { + const error = await response.json(); + aethexToast.error({ + title: "Failed to create post", + description: error.error || "Please try again", + }); + } + } catch (error) { + console.error("Error creating post:", error); + aethexToast.error({ + title: "Error", + description: error instanceof Error ? error.message : "Unknown error", + }); + } finally { + setIsSubmitting(false); + } + }; + + if (authLoading) { + return ; + } + + if (!user || !hasAccess) { + return ( + +
+
+ + + Access Denied + + This page requires admin or staff access. + + + + + + +
+
+
+ ); + } + + return ( + + + +
+
+ {/* Header */} +
+

+ Feed Management +

+

+ Create and manage community feed posts +

+
+ +
+ {/* Create Post Form */} +
+ + + + + Create New Post + + + Create a post for the community feed with arm affiliation + + + +
+ {/* Title */} +
+ + setTitle(e.target.value)} + placeholder="Post title" + className="bg-background/50 border-border/50" + required + /> +
+ + {/* Content */} +
+ +