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 } 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 type { Article, PredictionMarket, MediaOutlet } from "@shared/schema"; export default function Article() { const [, params] = useRoute("/articles/:slug"); const [, setLocation] = useLocation(); const { toast } = useToast(); const { user } = useAuth(); const [betAmounts, setBetAmounts] = useState>({}); 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 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 '미정'; return new Intl.DateTimeFormat('ko-KR', { 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 (

기사를 찾을 수 없습니다

); } return (
{/* Header */}
{outlet?.imageUrl ? ( {outlet.name} setLocation(`/media/${outlet.slug}`)} data-testid="img-outlet-profile" /> ) : (
outlet && setLocation(`/media/${outlet.slug}`)} data-testid="img-outlet-profile-fallback" > {outlet?.name.charAt(0)}
)} {outlet && (
setLocation("/")}> SAPIENS
{outlet.name}
)}
{/* 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.

)}
{/* Footer */}
); }