diff --git a/.replit b/.replit index 1dcf651..48ea545 100644 --- a/.replit +++ b/.replit @@ -18,10 +18,6 @@ externalPort = 80 localPort = 34047 externalPort = 3002 -[[ports]] -localPort = 34595 -externalPort = 6800 - [[ports]] localPort = 36309 externalPort = 5173 diff --git a/client/src/components/MediaOutletManagement.tsx b/client/src/components/MediaOutletManagement.tsx index 2fb6824..8f0db12 100644 --- a/client/src/components/MediaOutletManagement.tsx +++ b/client/src/components/MediaOutletManagement.tsx @@ -6,10 +6,10 @@ import { Textarea } from "@/components/ui/textarea"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; -import { ArrowLeft, Edit, Plus, BarChart3, Gavel, MessageSquare, Trash2, Eye } from "lucide-react"; +import { ArrowLeft, Edit, Plus, BarChart3, Gavel, MessageSquare, Trash2, Eye, Pin, Bell } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { queryClient, apiRequest } from "@/lib/queryClient"; -import type { MediaOutlet, Article } from "@shared/schema"; +import type { MediaOutlet, Article, CommunityPost } from "@shared/schema"; interface MediaOutletManagementProps { outlet: MediaOutlet; @@ -35,9 +35,13 @@ export default function MediaOutletManagement({ outlet, onBack }: MediaOutletMan queryKey: [`/api/media-outlets/${outlet.slug}/articles`], }); + const { data: communityPosts = [], isLoading: communityLoading } = useQuery({ + queryKey: [`/api/media-outlets/${outlet.slug}/community`], + }); + const createArticleMutation = useMutation({ mutationFn: async (data: typeof articleForm) => { - return await apiRequest("/api/articles", "POST", { ...data, mediaOutletId: outlet.id }); + return await apiRequest("POST", "/api/articles", { ...data, mediaOutletId: outlet.id }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [`/api/media-outlets/${outlet.slug}/articles`] }); @@ -59,7 +63,7 @@ export default function MediaOutletManagement({ outlet, onBack }: MediaOutletMan const updateArticleMutation = useMutation({ mutationFn: async (data: { id: string; updates: Partial }) => { - return await apiRequest(`/api/articles/${data.id}`, "PATCH", data.updates); + return await apiRequest("PATCH", `/api/articles/${data.id}`, data.updates); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [`/api/media-outlets/${outlet.slug}/articles`] }); @@ -82,7 +86,7 @@ export default function MediaOutletManagement({ outlet, onBack }: MediaOutletMan const deleteArticleMutation = useMutation({ mutationFn: async (id: string) => { - return await apiRequest(`/api/articles/${id}`, "DELETE", {}); + return await apiRequest("DELETE", `/api/articles/${id}`, {}); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [`/api/media-outlets/${outlet.slug}/articles`] }); @@ -100,6 +104,46 @@ export default function MediaOutletManagement({ outlet, onBack }: MediaOutletMan } }); + const updateCommunityPostMutation = useMutation({ + mutationFn: async (data: { id: string; updates: Partial }) => { + return await apiRequest("PATCH", `/api/community/posts/${data.id}`, data.updates); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [`/api/media-outlets/${outlet.slug}/community`] }); + toast({ + title: "Post Updated", + description: "The community post has been updated successfully.", + }); + }, + onError: (error: Error) => { + toast({ + title: "Error", + description: error.message || "Failed to update post", + variant: "destructive", + }); + } + }); + + const deleteCommunityPostMutation = useMutation({ + mutationFn: async (id: string) => { + return await apiRequest("DELETE", `/api/community/posts/${id}`, {}); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [`/api/media-outlets/${outlet.slug}/community`] }); + toast({ + title: "Post Deleted", + description: "The community post has been deleted successfully.", + }); + }, + onError: (error: Error) => { + toast({ + title: "Error", + description: error.message || "Failed to delete post", + variant: "destructive", + }); + } + }); + const resetArticleForm = () => { setArticleForm({ title: "", @@ -193,7 +237,7 @@ export default function MediaOutletManagement({ outlet, onBack }: MediaOutletMan Articles Prediction Markets Auctions - Comments + Community @@ -454,16 +498,109 @@ export default function MediaOutletManagement({ outlet, onBack }: MediaOutletMan - + - Comment Management + Community Management ({communityPosts.length}) -
-
No comments to manage
-
Comments will appear here when posted
-
+ {communityLoading ? ( +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+
+
+
+
+
+
+
+
+
+ ))} +
+ ) : communityPosts.length === 0 ? ( +
+
No community posts yet
+
Posts will appear here when users create them
+
+ ) : ( +
+ {communityPosts.map((post) => ( +
+
+
+

{post.title}

+ {post.isPinned && ( + + + Pinned + + )} + {post.isNotice && ( + + + Notice + + )} +
+

{post.content}

+
+ Views: {post.viewCount} • Likes: {post.likeCount} • Replies: {post.replyCount} +
+
+
+ + + + +
+
+ ))} +
+ )}
diff --git a/client/src/pages/Community.tsx b/client/src/pages/Community.tsx index 8830ffe..2eb766e 100644 --- a/client/src/pages/Community.tsx +++ b/client/src/pages/Community.tsx @@ -38,7 +38,7 @@ export default function Community() { const createPostMutation = useMutation({ mutationFn: async (data: { title: string; content: string; imageUrl?: string }) => { - return await apiRequest(`/api/media-outlets/${slug}/community`, "POST", data); + return await apiRequest("POST", `/api/media-outlets/${slug}/community`, data); }, onSuccess: () => { queryClient.invalidateQueries({ diff --git a/client/src/pages/CommunityPost.tsx b/client/src/pages/CommunityPost.tsx index 7cce06e..f7eda8d 100644 --- a/client/src/pages/CommunityPost.tsx +++ b/client/src/pages/CommunityPost.tsx @@ -36,7 +36,7 @@ export default function CommunityPostPage() { const likePostMutation = useMutation({ mutationFn: async () => { - return await apiRequest(`/api/community/posts/${postId}/like`, "POST", {}); + return await apiRequest("POST", `/api/community/posts/${postId}/like`, {}); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [`/api/community/posts/${postId}`] }); @@ -45,7 +45,7 @@ export default function CommunityPostPage() { const createReplyMutation = useMutation({ mutationFn: async (content: string) => { - return await apiRequest(`/api/community/posts/${postId}/replies`, "POST", { content }); + return await apiRequest("POST", `/api/community/posts/${postId}/replies`, { content }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [`/api/community/posts/${postId}/replies`] }); @@ -56,7 +56,7 @@ export default function CommunityPostPage() { const deletePostMutation = useMutation({ mutationFn: async () => { - return await apiRequest(`/api/community/posts/${postId}`, "DELETE", {}); + return await apiRequest("DELETE", `/api/community/posts/${postId}`, {}); }, onSuccess: () => { setLocation(`/media/${slug}/community`); diff --git a/replit.md b/replit.md index 100f5fb..343f3cd 100644 --- a/replit.md +++ b/replit.md @@ -30,6 +30,8 @@ The system uses a PostgreSQL database with the following core entities: - **Auctions**: eBay-style bidding system for media outlet management rights - **Bids**: Individual auction bids with quality scoring - **Comments**: User-generated content on articles +- **Community Posts**: DC Inside-style forum posts for each media outlet with views, likes, and replies +- **Community Replies**: Threaded replies to community posts ## Authentication & Authorization - **Multi-tier Access**: Three user roles with escalating permissions @@ -41,8 +43,10 @@ The system uses a PostgreSQL database with the following core entities: - **Dual View Modes**: Grid and list views for article browsing - **Auction System**: Real-time bidding with quality score integration - **Prediction Markets**: Integration with external prediction market APIs -- **Admin Dashboards**: Role-specific management interfaces -- **Content Management**: Rich article creation and media outlet administration +- **Admin Dashboards**: Role-specific management interfaces with article CRUD operations +- **Content Management**: Rich article creation, editing, and deletion with media outlet administration +- **DC Inside-style Community**: Forum-style discussion boards for each media outlet with posts, replies, likes, and sorting options +- **Community Features**: Post creation, threaded replies, view counts, like system, and multiple sort options (latest, views, likes, replies) # External Dependencies