import { useState } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { useRoute, useLocation } from "wouter"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { TrendingUp, TrendingDown, DollarSign, Clock, IdCard, Search, User, LogOut, Settings } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { apiRequest, queryClient } from "@/lib/queryClient"; import Footer from "@/components/Footer"; import { useAuth } from "@/hooks/useAuth"; import LoginModal from "@/components/LoginModal"; import SearchModal from "@/components/SearchModal"; import type { Article, PredictionMarket, MediaOutlet } from "@shared/schema"; export default function Article() { const [, params] = useRoute("/articles/:slug"); const [, setLocation] = useLocation(); const { toast } = useToast(); const { user, isAuthenticated } = useAuth(); const [betAmounts, setBetAmounts] = useState>({}); const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); const { data: articleData, isLoading: articleLoading } = useQuery
({ queryKey: ["/api/articles", params?.slug], enabled: !!params?.slug }); const article = articleData; const outlet = articleData?.outlet; const { data: markets = [], isLoading: marketsLoading } = useQuery({ queryKey: ["/api/articles", params?.slug, "markets"], enabled: !!params?.slug }); const handleLogout = async () => { try { const response = await fetch("/api/logout", { method: "POST", credentials: "include", }); if (response.ok) { toast({ title: "Logout Successful", description: "You have been successfully logged out.", }); queryClient.invalidateQueries({ queryKey: ["/api/auth/user"] }); } } catch (error) { toast({ title: "Logout Error", description: "An error occurred during logout.", variant: "destructive", }); } }; const handleAdminPage = () => { setLocation("/admin"); }; const placeBetMutation = useMutation({ mutationFn: async ({ marketId, side, amount }: { marketId: string; side: "yes" | "no"; amount: number }) => { return apiRequest("POST", `/api/prediction-markets/${marketId}/bets`, { side, amount }); }, onSuccess: () => { toast({ title: "Bet Placed Successfully", description: "Your prediction market bet has been placed." }); queryClient.invalidateQueries({ queryKey: ["/api/articles", params?.slug, "markets"] }); setBetAmounts({}); }, onError: (error: any) => { toast({ title: "Bet Failed", description: error.message || "An error occurred while placing your bet.", variant: "destructive" }); } }); const formatCurrency = (amount: string | number | null) => { if (!amount) return '₩0'; return new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW' }).format(Number(amount)); }; const formatPercentage = (value: string | number | null) => { if (!value) return '0.0%'; return `${(Number(value) * 100).toFixed(1)}%`; }; const formatDate = (dateString: string | Date | null) => { if (!dateString) return 'TBD'; return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(dateString)); }; const handleBetAmountChange = (marketId: string, value: string) => { setBetAmounts(prev => ({ ...prev, [marketId]: value })); }; const handlePlaceBet = (marketId: string, side: "yes" | "no") => { // Default bet amount (can be customized later) const amount = 10000; placeBetMutation.mutate({ marketId, side, amount }); }; const parseArticleContent = (content: string) => { const sectionRegex = /##SECTION##(.+?)##/g; const parts: { type: 'section' | 'text'; content: string }[] = []; let lastIndex = 0; let match; while ((match = sectionRegex.exec(content)) !== null) { if (match.index > lastIndex) { parts.push({ type: 'text', content: content.slice(lastIndex, match.index) }); } parts.push({ type: 'section', content: match[1] }); lastIndex = match.index + match[0].length; } if (lastIndex < content.length) { parts.push({ type: 'text', content: content.slice(lastIndex) }); } return parts; }; if (articleLoading) { return (
); } if (!article) { return (

Article Not Found

); } return (
{/* Header */}
{outlet?.imageUrl ? ( {outlet.name} setLocation(`/media/${outlet.slug}`)} data-testid="image-outlet-header-profile" /> ) : (
{outlet?.name.charAt(0)}
)} {outlet && (
{outlet.name}
)}
setIsSearchModalOpen(true)} data-testid="search-container" >
search in SAPIENS
{isAuthenticated && user ? ( <>
{user.firstName} {user.lastName}
) : ( )}
{/* Article Content */}
{article.imageUrl && (
{article.title}
)}

{article.title}

{article.excerpt && (

{article.excerpt}

)}
{parseArticleContent(article.content).map((part, index) => { if (part.type === 'section') { return (

{part.content.trim()}

); } return (

{part.content.trim()}

); })}
{/* Prediction Markets Section */}

Related Prediction Markets

{marketsLoading ? (
{Array.from({ length: 3 }).map((_, i) => (
))}
) : markets.length > 0 ? (
{markets.map((market) => ( {/* Question and Live Badge */}

{market.question}

{market.isActive && ( LIVE )}
{/* Percentages */}
{formatPercentage(market.yesPrice)}
YES
{formatPercentage(market.noPrice)}
NO
{/* Yes/No Buttons */}
{/* Market Info */}
{formatCurrency(market.totalVolume)} Vol.
Politics
in 3 months
))}
) : (

No Related Prediction Markets

There are no prediction markets related to this article yet.

)}
{/* Modals */} setIsLoginModalOpen(false)} /> setIsSearchModalOpen(false)} /> {/* Footer */}
); }