From 95117207ed2b80e1d4852427467d653eeea1ec5c Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Fri, 14 Nov 2025 22:24:24 +0000 Subject: [PATCH] Admin Feed Manager - Create system posts cgen-3cd25e567e604d0d820275dc93b1fe2c --- client/pages/AdminFeed.tsx | 677 ++++++++++++++++--------------------- 1 file changed, 298 insertions(+), 379 deletions(-) diff --git a/client/pages/AdminFeed.tsx b/client/pages/AdminFeed.tsx index c9e3e986..6c93c801 100644 --- a/client/pages/AdminFeed.tsx +++ b/client/pages/AdminFeed.tsx @@ -1,17 +1,9 @@ -import { useState, useEffect } from "react"; -import { useNavigate } from "react-router-dom"; +import { useState } from "react"; import Layout from "@/components/Layout"; -import SEO from "@/components/SEO"; +import { useAuth } from "@/contexts/AuthContext"; +import { useToast } from "@/hooks/use-aethex-toast"; 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, @@ -20,400 +12,327 @@ import { 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"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Loader2, Sparkles } 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" }, +const ARMS = [ + { id: "labs", label: "Labs", color: "bg-yellow-500" }, + { id: "gameforge", label: "GameForge", color: "bg-green-500" }, + { id: "corp", label: "Corp", color: "bg-blue-500" }, + { id: "foundation", label: "Foundation", color: "bg-red-500" }, + { id: "devlink", label: "Dev-Link", color: "bg-cyan-500" }, + { id: "nexus", label: "Nexus", color: "bg-purple-500" }, + { id: "staff", label: "Staff", color: "bg-indigo-500" }, ]; -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 { user } = useAuth(); + const { toast } = useToast(); - const hasAccess = roles.includes("admin") || roles.includes("staff"); - - // Form state + const [isLoading, setIsLoading] = useState(false); 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); + const [selectedArm, setSelectedArm] = useState("labs"); + const [tags, setTags] = useState([]); + const [tagInput, setTagInput] = useState(""); + const [isDraft, setIsDraft] = 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) { + // Check admin access + if (!user?.user_metadata?.is_admin) { return ( -
-
- - - Access Denied - - This page requires admin or staff access. - - - - - - -
+
+ + + Access Denied + + +

+ Only administrators can access this page. +

+
+
); } + const addTag = () => { + const trimmed = tagInput.trim(); + if (trimmed && !tags.includes(trimmed)) { + setTags([...tags, trimmed]); + setTagInput(""); + } + }; + + const removeTag = (tag: string) => { + setTags(tags.filter((t) => t !== tag)); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!title.trim() || !content.trim()) { + toast({ + description: "Title and content are required", + variant: "destructive", + }); + return; + } + + if (!user?.id) { + toast({ + description: "You must be logged in to create posts", + variant: "destructive", + }); + return; + } + + setIsLoading(true); + + try { + const response = await fetch("/api/community/posts", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + title: title.trim(), + content: content.trim(), + arm_affiliation: selectedArm, + author_id: user.id, + tags: tags, + category: "announcement", + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || "Failed to create post"); + } + + const { post } = await response.json(); + + toast({ + description: "Post created successfully! 🎉", + }); + + // Reset form + setTitle(""); + setContent(""); + setTags([]); + setTagInput(""); + setSelectedArm("labs"); + } catch (error: any) { + console.error("Failed to create post:", error); + toast({ + description: error.message || "Failed to create post", + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + const selectedArmData = ARMS.find((arm) => arm.id === selectedArm); + return ( - - -
-
+
+
{/* Header */} -
-

- Feed Management -

-

- Create and manage community feed posts +

+
+ +

+ Feed Manager +

+
+

+ Create system announcements and showcase Arm-to-Arm partnerships. + This is how we prove the Axiom Model in action.

-
- {/* 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 */} -
- -