diff --git a/.replit b/.replit index 48ea545..1dcf651 100644 --- a/.replit +++ b/.replit @@ -18,6 +18,10 @@ 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 5038d9b..2fb6824 100644 --- a/client/src/components/MediaOutletManagement.tsx +++ b/client/src/components/MediaOutletManagement.tsx @@ -1,11 +1,15 @@ import { useState } from "react"; -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useMutation } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +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 { ArrowLeft, Edit, Plus, BarChart3, Gavel, MessageSquare } from "lucide-react"; -import type { MediaOutlet } from "@shared/schema"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; +import { ArrowLeft, Edit, Plus, BarChart3, Gavel, MessageSquare, Trash2, Eye } from "lucide-react"; +import { useToast } from "@/hooks/use-toast"; +import { queryClient, apiRequest } from "@/lib/queryClient"; +import type { MediaOutlet, Article } from "@shared/schema"; interface MediaOutletManagementProps { outlet: MediaOutlet; @@ -14,12 +18,127 @@ interface MediaOutletManagementProps { export default function MediaOutletManagement({ outlet, onBack }: MediaOutletManagementProps) { const [activeTab, setActiveTab] = useState("overview"); - - // TODO: Add queries for articles, predictions, auctions, comments related to this outlet - const { data: articles = [] } = useQuery({ - queryKey: ["/api/articles", outlet.id], - enabled: false, // Disabled for now as articles API is not implemented + const [isArticleDialogOpen, setIsArticleDialogOpen] = useState(false); + const [editingArticle, setEditingArticle] = useState
(null); + const [articleForm, setArticleForm] = useState({ + title: "", + slug: "", + content: "", + excerpt: "", + imageUrl: "", + publishedMinutesAgo: 0, + isFeatured: false }); + const { toast } = useToast(); + + const { data: articles = [], isLoading: articlesLoading } = useQuery({ + queryKey: [`/api/media-outlets/${outlet.slug}/articles`], + }); + + const createArticleMutation = useMutation({ + mutationFn: async (data: typeof articleForm) => { + return await apiRequest("/api/articles", "POST", { ...data, mediaOutletId: outlet.id }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [`/api/media-outlets/${outlet.slug}/articles`] }); + setIsArticleDialogOpen(false); + resetArticleForm(); + toast({ + title: "Article Created", + description: "The article has been created successfully.", + }); + }, + onError: (error: Error) => { + toast({ + title: "Error", + description: error.message || "Failed to create article", + variant: "destructive", + }); + } + }); + + const updateArticleMutation = useMutation({ + mutationFn: async (data: { id: string; updates: Partial }) => { + return await apiRequest(`/api/articles/${data.id}`, "PATCH", data.updates); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [`/api/media-outlets/${outlet.slug}/articles`] }); + setIsArticleDialogOpen(false); + setEditingArticle(null); + resetArticleForm(); + toast({ + title: "Article Updated", + description: "The article has been updated successfully.", + }); + }, + onError: (error: Error) => { + toast({ + title: "Error", + description: error.message || "Failed to update article", + variant: "destructive", + }); + } + }); + + const deleteArticleMutation = useMutation({ + mutationFn: async (id: string) => { + return await apiRequest(`/api/articles/${id}`, "DELETE", {}); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [`/api/media-outlets/${outlet.slug}/articles`] }); + toast({ + title: "Article Deleted", + description: "The article has been deleted successfully.", + }); + }, + onError: (error: Error) => { + toast({ + title: "Error", + description: error.message || "Failed to delete article", + variant: "destructive", + }); + } + }); + + const resetArticleForm = () => { + setArticleForm({ + title: "", + slug: "", + content: "", + excerpt: "", + imageUrl: "", + publishedMinutesAgo: 0, + isFeatured: false + }); + }; + + const openNewArticleDialog = () => { + resetArticleForm(); + setEditingArticle(null); + setIsArticleDialogOpen(true); + }; + + const openEditArticleDialog = (article: Article) => { + setArticleForm({ + title: article.title, + slug: article.slug, + content: article.content || "", + excerpt: article.excerpt || "", + imageUrl: article.imageUrl || "", + publishedMinutesAgo: article.publishedMinutesAgo, + isFeatured: article.isFeatured || false + }); + setEditingArticle(article); + setIsArticleDialogOpen(true); + }; + + const handleSubmitArticle = () => { + if (editingArticle) { + updateArticleMutation.mutate({ id: editingArticle.id, updates: articleForm }); + } else { + createArticleMutation.mutate(articleForm); + } + }; return (
@@ -84,7 +203,7 @@ export default function MediaOutletManagement({ outlet, onBack }: MediaOutletMan

Total Articles

-

0

+

{articles.length}

@@ -207,18 +326,90 @@ export default function MediaOutletManagement({ outlet, onBack }: MediaOutletMan
- Article Management -
-
-
No articles yet
-
Create your first article
-
+ {articlesLoading ? ( +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+
+
+
+
+
+
+
+
+
+ ))} +
+ ) : articles.length === 0 ? ( +
+
No articles yet
+
Create your first article
+
+ ) : ( +
+ {articles.map((article) => ( +
+
+
+

{article.title}

+ {article.isFeatured && ( + + Featured + + )} +
+

{article.excerpt || "No excerpt"}

+
+ Published {article.publishedMinutesAgo} minutes ago • Slug: {article.slug} +
+
+
+ + + +
+
+ ))} +
+ )}
@@ -278,6 +469,112 @@ export default function MediaOutletManagement({ outlet, onBack }: MediaOutletMan
+ + {/* Article Dialog */} + + + + {editingArticle ? "Edit Article" : "Create New Article"} + + {editingArticle ? "Update the article details below." : "Fill in the details to create a new article."} + + +
+
+ + setArticleForm({ ...articleForm, title: e.target.value })} + placeholder="Enter article title" + data-testid="input-article-title" + /> +
+
+ + setArticleForm({ ...articleForm, slug: e.target.value })} + placeholder="article-slug-url" + data-testid="input-article-slug" + /> +

URL-friendly identifier (e.g., my-article-title)

+
+
+ +