diff --git a/.replit b/.replit index a45f89c..ba89ca5 100644 --- a/.replit +++ b/.replit @@ -22,6 +22,10 @@ externalPort = 3001 localPort = 43349 externalPort = 3000 +[[ports]] +localPort = 44197 +externalPort = 3002 + [env] PORT = "5000" diff --git a/attached_assets/Lautaro Martínez_1759170040559.jpeg b/attached_assets/Lautaro Martínez_1759170040559.jpeg new file mode 100644 index 0000000..ce09dd4 Binary files /dev/null and b/attached_assets/Lautaro Martínez_1759170040559.jpeg differ diff --git a/attached_assets/Nicolò Barella_1759170031318.jpeg b/attached_assets/Nicolò Barella_1759170031318.jpeg new file mode 100644 index 0000000..41cb11f Binary files /dev/null and b/attached_assets/Nicolò Barella_1759170031318.jpeg differ diff --git a/attached_assets/Rodri Hernández (Rodrigo Hernández)_1759170035155.jpeg b/attached_assets/Rodri Hernández (Rodrigo Hernández)_1759170035155.jpeg new file mode 100644 index 0000000..03bab8f Binary files /dev/null and b/attached_assets/Rodri Hernández (Rodrigo Hernández)_1759170035155.jpeg differ diff --git a/attached_assets/altcoin_1759170052209.jpeg b/attached_assets/altcoin_1759170052209.jpeg new file mode 100644 index 0000000..8fad727 Binary files /dev/null and b/attached_assets/altcoin_1759170052209.jpeg differ diff --git a/client/src/components/MediaOutletManagement.tsx b/client/src/components/MediaOutletManagement.tsx new file mode 100644 index 0000000..4ce0cd2 --- /dev/null +++ b/client/src/components/MediaOutletManagement.tsx @@ -0,0 +1,293 @@ +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +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"; + +interface MediaOutletManagementProps { + outlet: MediaOutlet; + onBack: () => void; +} + +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 + }); + + return ( +
+ {/* Header */} +
+
+
+
+ + +
+ {outlet.imageUrl ? ( + {outlet.name} + ) : ( +
+ + {outlet.name.charAt(0)} + +
+ )} +
+

{outlet.name}

+

{outlet.description || "Media Outlet"}

+
+
+
+ +
+ + {outlet.category} + + +
+
+
+
+ + {/* Main Content */} +
+ + + 개요 + 기사 관리 + 예측시장 + 경매 + 댓글 + + + +
+ + +
+
+

총 기사 수

+

0

+
+
+ +
+
+
+
+ + + +
+
+

활성 예측시장

+

0

+
+
+ +
+
+
+
+ + + +
+
+

진행중인 경매

+

0

+
+
+ +
+
+
+
+ + + +
+
+

총 댓글 수

+

0

+
+
+ +
+
+
+
+
+ +
+ + + 언론매체 정보 + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + 빠른 작업 + + + + + + + + +
+
+ + + + +
+ 기사 관리 + +
+
+ +
+
아직 기사가 없습니다
+
첫 번째 기사를 작성해보세요
+
+
+
+
+ + + + +
+ 예측시장 관리 + +
+
+ +
+
활성 예측시장이 없습니다
+
새로운 예측시장을 생성해보세요
+
+
+
+
+ + + + +
+ 경매 관리 + +
+
+ +
+
진행중인 경매가 없습니다
+
새로운 경매를 시작해보세요
+
+
+
+
+ + + + + 댓글 관리 + + +
+
관리할 댓글이 없습니다
+
댓글이 등록되면 여기서 관리할 수 있습니다
+
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/client/src/pages/AdminDashboard.tsx b/client/src/pages/AdminDashboard.tsx index 3014098..e69cd78 100644 --- a/client/src/pages/AdminDashboard.tsx +++ b/client/src/pages/AdminDashboard.tsx @@ -1,14 +1,21 @@ import { useQuery } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { Card, CardContent } from "@/components/ui/card"; import { useAuth } from "@/hooks/useAuth"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useToast } from "@/hooks/use-toast"; import { isUnauthorizedError } from "@/lib/authUtils"; +import { Search, Settings } from "lucide-react"; +import type { MediaOutlet } from "@shared/schema"; +import MediaOutletManagement from "@/components/MediaOutletManagement"; export default function AdminDashboard() { const { user, isLoading } = useAuth(); const { toast } = useToast(); + const [searchTerm, setSearchTerm] = useState(""); + const [selectedOutlet, setSelectedOutlet] = useState(null); + const [managingOutlet, setManagingOutlet] = useState(null); // Redirect if not authenticated or not admin/superadmin useEffect(() => { @@ -24,24 +31,24 @@ export default function AdminDashboard() { } }, [isLoading, user, toast]); - const { data: analytics, isLoading: analyticsLoading, error: analyticsError } = useQuery({ - queryKey: ["/api/analytics"], - retry: false, + const { data: mediaOutlets = [], isLoading: outletsLoading } = useQuery({ + queryKey: ["/api/media-outlets"], + queryFn: async () => { + const res = await fetch("/api/media-outlets", { + credentials: "include", + }); + if (!res.ok) { + throw new Error(`${res.status}: ${res.statusText}`); + } + return res.json(); + }, }); - // Handle analytics errors - useEffect(() => { - if (analyticsError && isUnauthorizedError(analyticsError as Error)) { - toast({ - title: "Unauthorized", - description: "You are logged out. Logging in again...", - variant: "destructive", - }); - setTimeout(() => { - window.location.href = "/api/login"; - }, 500); - } - }, [analyticsError, toast]); + // Filter outlets based on search term + const filteredOutlets = mediaOutlets.filter(outlet => + outlet.name.toLowerCase().includes(searchTerm.toLowerCase()) || + (outlet.description && outlet.description.toLowerCase().includes(searchTerm.toLowerCase())) + ); const handleLogout = () => { window.location.href = "/api/logout"; @@ -55,25 +62,67 @@ export default function AdminDashboard() { ); } + // If managing an outlet, show MediaOutletManagement + if (managingOutlet) { + return ( + setManagingOutlet(null)} + /> + ); + } + return ( -
+
{/* Header */} -
+
-
-
- S -
- SAPIENS - • Admin Dashboard +
+ SAPIENS
- - + +
@@ -81,137 +130,135 @@ export default function AdminDashboard() {
-
-
-
-

Admin Dashboard

-

Manage media outlets and content

+
+
+

관리자 대시보드

+

관리할 언론매체를 검색하고 선택하세요

+
+ + {outletsLoading ? ( +
+ {Array.from({ length: 12 }).map((_, i) => ( + + +
+
+
+
+
+
+
+
+
+ ))}
-
- - {/* Analytics Cards */} -
- {analyticsLoading ? ( - Array.from({ length: 4 }).map((_, i) => ( - - -
-
-
- )) - ) : ( - <> - - -
-
-

Total Articles

-

{(analytics as any)?.totalArticles || 0}

-
- -
-
-
- - - -
-
-

Active Predictions

-

{(analytics as any)?.activePredictions || 0}

-
- -
-
-
- - - -
-
-

Live Auctions

-

{(analytics as any)?.liveAuctions || 0}

-
- -
-
-
- - - -
-
-

Revenue

-

${((analytics as any)?.totalRevenue || 0).toLocaleString()}

-
- -
-
-
- - )} -
- - {/* Admin Actions */} -
- - -

Content Management

-
- - - + +
+
+ {outlet.imageUrl ? ( + {outlet.name} + ) : ( +
+ + {outlet.name.charAt(0)} + +
+ )} +
+
+

+ {outlet.name} +

+

+ {outlet.description || "Media Outlet"} +

+
+ + {outlet.category} + +
+
+
+
+ + ))} +
+ + {filteredOutlets.length === 0 && searchTerm && ( +
+
검색 결과가 없습니다
+
다른 검색어를 시도해보세요
-
-
- - - -

Quick Actions

-
-
-
-

Featured Article Priority

-

Manage article visibility

+ )} + + )} + + {/* Selected Outlet Modal/Preview */} + {selectedOutlet && ( +
+
+
+ {selectedOutlet.imageUrl ? ( + {selectedOutlet.name} + ) : ( +
+ + {selectedOutlet.name.charAt(0)} +
- -
-
-
-

Auction Controls

-

Monitor active auctions

-
- + )} +
+

{selectedOutlet.name}

+

{selectedOutlet.description}

+ + {selectedOutlet.category} +
- - -
+
+ + +
+
+
+ )}
);