Translate all user-facing text to English and update UI elements

This commit addresses the user's request to translate all visible text within the application to English. It also updates the login modal to display the SAPIENS logo correctly. Additionally, various UI components and messages related to articles, community posts, auctions, and login/error handling have been localized.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9a264234-c5d7-4dcc-adf3-a954b149b30d
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3df548ff-50ae-432f-9be4-25d34eccc983/9a264234-c5d7-4dcc-adf3-a954b149b30d/CMG42YQ
This commit is contained in:
kimjaehyeon0101
2025-10-15 08:06:03 +00:00
parent 7af7c0ef12
commit d2eb898640
6 changed files with 120 additions and 113 deletions

View File

@ -34,8 +34,8 @@ export default function LoginModal({ isOpen, onClose }: LoginModalProps) {
if (response.ok) { if (response.ok) {
const user = await response.json(); const user = await response.json();
toast({ toast({
title: "로그인 성공", title: "Login Successful",
description: `환영합니다, ${user.firstName}!`, description: `Welcome, ${user.firstName}!`,
}); });
// Invalidate auth queries to refresh user state // Invalidate auth queries to refresh user state
@ -48,15 +48,15 @@ export default function LoginModal({ isOpen, onClose }: LoginModalProps) {
} else { } else {
const error = await response.json(); const error = await response.json();
toast({ toast({
title: "로그인 실패", title: "Login Failed",
description: error.message || "잘못된 아이디 또는 비밀번호입니다.", description: error.message || "Invalid username or password.",
variant: "destructive", variant: "destructive",
}); });
} }
} catch (error) { } catch (error) {
toast({ toast({
title: "로그인 오류", title: "Login Error",
description: "로그인 중 오류가 발생했습니다.", description: "An error occurred during login.",
variant: "destructive", variant: "destructive",
}); });
} finally { } finally {
@ -79,7 +79,7 @@ export default function LoginModal({ isOpen, onClose }: LoginModalProps) {
</DialogHeader> </DialogHeader>
<form onSubmit={handleLogin} className="space-y-4"> <form onSubmit={handleLogin} className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="username"></Label> <Label htmlFor="username">Username</Label>
<Input <Input
id="username" id="username"
type="text" type="text"
@ -87,13 +87,13 @@ export default function LoginModal({ isOpen, onClose }: LoginModalProps) {
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
onInvalid={(e) => (e.target as HTMLInputElement).setCustomValidity('Please enter your username')} onInvalid={(e) => (e.target as HTMLInputElement).setCustomValidity('Please enter your username')}
onInput={(e) => (e.target as HTMLInputElement).setCustomValidity('')} onInput={(e) => (e.target as HTMLInputElement).setCustomValidity('')}
placeholder="아이디를 입력하세요" placeholder="Enter your username"
required required
data-testid="input-username" data-testid="input-username"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="password"></Label> <Label htmlFor="password">Password</Label>
<Input <Input
id="password" id="password"
type="password" type="password"
@ -101,7 +101,7 @@ export default function LoginModal({ isOpen, onClose }: LoginModalProps) {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
onInvalid={(e) => (e.target as HTMLInputElement).setCustomValidity('Please enter your password')} onInvalid={(e) => (e.target as HTMLInputElement).setCustomValidity('Please enter your password')}
onInput={(e) => (e.target as HTMLInputElement).setCustomValidity('')} onInput={(e) => (e.target as HTMLInputElement).setCustomValidity('')}
placeholder="비밀번호를 입력하세요" placeholder="Enter your password"
required required
data-testid="input-password" data-testid="input-password"
/> />
@ -113,7 +113,7 @@ export default function LoginModal({ isOpen, onClose }: LoginModalProps) {
disabled={isLoading} disabled={isLoading}
data-testid="button-login-submit" data-testid="button-login-submit"
> >
{isLoading ? "로그인 중..." : "로그인"} {isLoading ? "Logging in..." : "Login"}
</Button> </Button>
<Button <Button
type="button" type="button"
@ -122,7 +122,7 @@ export default function LoginModal({ isOpen, onClose }: LoginModalProps) {
disabled={isLoading} disabled={isLoading}
data-testid="button-login-cancel" data-testid="button-login-cancel"
> >
Cancel
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -99,8 +99,8 @@ export default function Article() {
}; };
const formatDate = (dateString: string | Date | null) => { const formatDate = (dateString: string | Date | null) => {
if (!dateString) return '미정'; if (!dateString) return 'TBD';
return new Intl.DateTimeFormat('ko-KR', { return new Intl.DateTimeFormat('en-US', {
year: 'numeric', year: 'numeric',
month: 'long', month: 'long',
day: 'numeric', day: 'numeric',
@ -179,8 +179,8 @@ export default function Article() {
return ( return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center"> <div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<h1 className="text-2xl font-bold mb-4"> </h1> <h1 className="text-2xl font-bold mb-4">Article Not Found</h1>
<Button onClick={() => setLocation("/")}> </Button> <Button onClick={() => setLocation("/")}>Return Home</Button>
</div> </div>
</div> </div>
); );

View File

@ -78,16 +78,16 @@ export default function Community() {
const diffInMs = now.getTime() - postDate.getTime(); const diffInMs = now.getTime() - postDate.getTime();
const diffInMinutes = Math.floor(diffInMs / 60000); const diffInMinutes = Math.floor(diffInMs / 60000);
if (diffInMinutes < 1) return "방금"; if (diffInMinutes < 1) return "just now";
if (diffInMinutes < 60) return `${diffInMinutes}분 전`; if (diffInMinutes < 60) return `${diffInMinutes}m ago`;
const diffInHours = Math.floor(diffInMinutes / 60); const diffInHours = Math.floor(diffInMinutes / 60);
if (diffInHours < 24) return `${diffInHours}시간 전`; if (diffInHours < 24) return `${diffInHours}h ago`;
const diffInDays = Math.floor(diffInHours / 24); const diffInDays = Math.floor(diffInHours / 24);
if (diffInDays < 30) return `${diffInDays}일 전`; if (diffInDays < 30) return `${diffInDays}d ago`;
return postDate.toLocaleDateString('ko-KR'); return postDate.toLocaleDateString('en-US');
}; };
const filteredPosts = posts.filter(post => const filteredPosts = posts.filter(post =>
@ -104,14 +104,14 @@ export default function Community() {
<div className="flex items-center justify-center min-h-screen"> <div className="flex items-center justify-center min-h-screen">
<div className="text-center"> <div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600"> ...</p> <p className="text-gray-600">Loading...</p>
</div> </div>
</div> </div>
); );
} }
if (!outlet) { if (!outlet) {
return <div> </div>; return <div>Media outlet not found</div>;
} }
return ( return (
@ -232,9 +232,9 @@ export default function Community() {
<div className="mb-6 flex items-center justify-between"> <div className="mb-6 flex items-center justify-between">
<div> <div>
<h1 className="text-2xl font-bold text-gray-900" data-testid="text-community-title"> <h1 className="text-2xl font-bold text-gray-900" data-testid="text-community-title">
{outlet.name} {outlet.name} Community
</h1> </h1>
<p className="text-sm text-gray-600 mt-1"> </p> <p className="text-sm text-gray-600 mt-1">Share your thoughts freely</p>
</div> </div>
<Dialog open={isNewPostOpen} onOpenChange={setIsNewPostOpen}> <Dialog open={isNewPostOpen} onOpenChange={setIsNewPostOpen}>
@ -248,12 +248,12 @@ export default function Community() {
}} }}
data-testid="button-new-post" data-testid="button-new-post"
> >
Write Post
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-w-2xl" data-testid="dialog-new-post"> <DialogContent className="max-w-2xl" data-testid="dialog-new-post">
<DialogHeader> <DialogHeader>
<DialogTitle> </DialogTitle> <DialogTitle>Create New Post</DialogTitle>
</DialogHeader> </DialogHeader>
<form onSubmit={(e) => { <form onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
@ -268,7 +268,7 @@ export default function Community() {
<div> <div>
<Input <Input
name="title" name="title"
placeholder="제목을 입력하세요" placeholder="Enter title"
required required
data-testid="input-post-title" data-testid="input-post-title"
/> />
@ -276,7 +276,7 @@ export default function Community() {
<div> <div>
<Textarea <Textarea
name="content" name="content"
placeholder="내용을 입력하세요" placeholder="Enter content"
rows={10} rows={10}
required required
data-testid="textarea-post-content" data-testid="textarea-post-content"
@ -285,16 +285,16 @@ export default function Community() {
<div> <div>
<Input <Input
name="imageUrl" name="imageUrl"
placeholder="이미지 URL (선택사항)" placeholder="Image URL (optional)"
data-testid="input-post-image-url" data-testid="input-post-image-url"
/> />
</div> </div>
<div className="flex justify-end space-x-2"> <div className="flex justify-end space-x-2">
<Button type="button" variant="outline" onClick={() => setIsNewPostOpen(false)} data-testid="button-cancel-post"> <Button type="button" variant="outline" onClick={() => setIsNewPostOpen(false)} data-testid="button-cancel-post">
Cancel
</Button> </Button>
<Button type="submit" disabled={createPostMutation.isPending} data-testid="button-submit-post"> <Button type="submit" disabled={createPostMutation.isPending} data-testid="button-submit-post">
{createPostMutation.isPending ? '작성 중...' : '작성하기'} {createPostMutation.isPending ? 'Creating...' : 'Create Post'}
</Button> </Button>
</div> </div>
</div> </div>
@ -311,10 +311,10 @@ export default function Community() {
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="latest"></SelectItem> <SelectItem value="latest">Latest</SelectItem>
<SelectItem value="views"></SelectItem> <SelectItem value="views">Most Views</SelectItem>
<SelectItem value="likes"></SelectItem> <SelectItem value="likes">Most Likes</SelectItem>
<SelectItem value="replies"></SelectItem> <SelectItem value="replies">Most Replies</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@ -323,7 +323,7 @@ export default function Community() {
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" /> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input <Input
type="text" type="text"
placeholder="검색" placeholder="Search"
className="w-64 pl-10" className="w-64 pl-10"
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
@ -336,7 +336,7 @@ export default function Community() {
{postsLoading ? ( {postsLoading ? (
<div className="text-center py-12"> <div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600"> ...</p> <p className="text-gray-600">Loading...</p>
</div> </div>
) : ( ) : (
<div className="space-y-1"> <div className="space-y-1">
@ -360,13 +360,13 @@ export default function Community() {
<div className="flex items-center space-x-2 mb-1"> <div className="flex items-center space-x-2 mb-1">
{post.isNotice && ( {post.isNotice && (
<Badge variant="destructive" className="text-xs" data-testid={`badge-notice-${post.id}`}> <Badge variant="destructive" className="text-xs" data-testid={`badge-notice-${post.id}`}>
Notice
</Badge> </Badge>
)} )}
{post.isPinned && !post.isNotice && ( {post.isPinned && !post.isNotice && (
<Badge variant="secondary" className="text-xs" data-testid={`badge-pin-${post.id}`}> <Badge variant="secondary" className="text-xs" data-testid={`badge-pin-${post.id}`}>
<Pin className="h-3 w-3 mr-1" /> <Pin className="h-3 w-3 mr-1" />
Pinned
</Badge> </Badge>
)} )}
</div> </div>
@ -437,7 +437,7 @@ export default function Community() {
{filteredPosts.length === 0 && ( {filteredPosts.length === 0 && (
<div className="text-center py-12"> <div className="text-center py-12">
<p className="text-gray-500"> .</p> <p className="text-gray-500">No posts yet.</p>
</div> </div>
)} )}
</div> </div>

View File

@ -70,16 +70,16 @@ export default function CommunityPostPage() {
const diffInMs = now.getTime() - postDate.getTime(); const diffInMs = now.getTime() - postDate.getTime();
const diffInMinutes = Math.floor(diffInMs / 60000); const diffInMinutes = Math.floor(diffInMs / 60000);
if (diffInMinutes < 1) return "방금"; if (diffInMinutes < 1) return "just now";
if (diffInMinutes < 60) return `${diffInMinutes}분 전`; if (diffInMinutes < 60) return `${diffInMinutes}m ago`;
const diffInHours = Math.floor(diffInMinutes / 60); const diffInHours = Math.floor(diffInMinutes / 60);
if (diffInHours < 24) return `${diffInHours}시간 전`; if (diffInHours < 24) return `${diffInHours}h ago`;
const diffInDays = Math.floor(diffInHours / 24); const diffInDays = Math.floor(diffInHours / 24);
if (diffInDays < 30) return `${diffInDays}일 전`; if (diffInDays < 30) return `${diffInDays}d ago`;
return postDate.toLocaleDateString('ko-KR'); return postDate.toLocaleDateString('en-US');
}; };
const canDeletePost = () => { const canDeletePost = () => {
@ -92,14 +92,14 @@ export default function CommunityPostPage() {
<div className="flex items-center justify-center min-h-screen"> <div className="flex items-center justify-center min-h-screen">
<div className="text-center"> <div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600"> ...</p> <p className="text-gray-600">Loading...</p>
</div> </div>
</div> </div>
); );
} }
if (!post) { if (!post) {
return <div> </div>; return <div>Post not found</div>;
} }
return ( return (
@ -114,7 +114,7 @@ export default function CommunityPostPage() {
data-testid="button-back" data-testid="button-back"
> >
<ArrowLeft className="h-4 w-4 mr-2" /> <ArrowLeft className="h-4 w-4 mr-2" />
Back to Community
</Button> </Button>
{outlet && ( {outlet && (
@ -144,12 +144,12 @@ export default function CommunityPostPage() {
<div className="flex items-center space-x-2 mb-3"> <div className="flex items-center space-x-2 mb-3">
{post.isNotice && ( {post.isNotice && (
<Badge variant="destructive" className="text-xs" data-testid="badge-notice"> <Badge variant="destructive" className="text-xs" data-testid="badge-notice">
Notice
</Badge> </Badge>
)} )}
{post.isPinned && !post.isNotice && ( {post.isPinned && !post.isNotice && (
<Badge variant="secondary" className="text-xs" data-testid="badge-pin"> <Badge variant="secondary" className="text-xs" data-testid="badge-pin">
Pinned
</Badge> </Badge>
)} )}
</div> </div>
@ -217,7 +217,7 @@ export default function CommunityPostPage() {
data-testid="button-like" data-testid="button-like"
> >
<ThumbsUp className="h-4 w-4 mr-2" /> <ThumbsUp className="h-4 w-4 mr-2" />
({post.likeCount}) Like ({post.likeCount})
</Button> </Button>
</div> </div>
</CardContent> </CardContent>
@ -226,7 +226,7 @@ export default function CommunityPostPage() {
{/* Replies Section */} {/* Replies Section */}
<div className="mb-6"> <div className="mb-6">
<h2 className="text-lg font-semibold mb-4" data-testid="text-replies-title"> <h2 className="text-lg font-semibold mb-4" data-testid="text-replies-title">
{post.replyCount} {post.replyCount} Replies
</h2> </h2>
{/* Reply Form */} {/* Reply Form */}
@ -234,7 +234,7 @@ export default function CommunityPostPage() {
<Card className="mb-6"> <Card className="mb-6">
<CardContent className="p-4"> <CardContent className="p-4">
<Textarea <Textarea
placeholder="댓글을 입력하세요" placeholder="Write a reply"
value={replyContent} value={replyContent}
onChange={(e) => setReplyContent(e.target.value)} onChange={(e) => setReplyContent(e.target.value)}
rows={3} rows={3}
@ -247,7 +247,7 @@ export default function CommunityPostPage() {
disabled={!replyContent.trim() || createReplyMutation.isPending} disabled={!replyContent.trim() || createReplyMutation.isPending}
data-testid="button-submit-reply" data-testid="button-submit-reply"
> >
{createReplyMutation.isPending ? '작성 중...' : '댓글 작성'} {createReplyMutation.isPending ? 'Posting...' : 'Post Reply'}
</Button> </Button>
</div> </div>
</CardContent> </CardContent>
@ -255,7 +255,7 @@ export default function CommunityPostPage() {
) : ( ) : (
<Card className="mb-6"> <Card className="mb-6">
<CardContent className="p-4 text-center text-gray-500"> <CardContent className="p-4 text-center text-gray-500">
. Please login to write a reply.
</CardContent> </CardContent>
</Card> </Card>
)} )}
@ -264,7 +264,7 @@ export default function CommunityPostPage() {
{repliesLoading ? ( {repliesLoading ? (
<div className="text-center py-8"> <div className="text-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-3"></div> <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-3"></div>
<p className="text-gray-600 text-sm"> ...</p> <p className="text-gray-600 text-sm">Loading replies...</p>
</div> </div>
) : replies.length > 0 ? ( ) : replies.length > 0 ? (
<div className="space-y-3"> <div className="space-y-3">
@ -283,7 +283,7 @@ export default function CommunityPostPage() {
</div> </div>
) : ( ) : (
<div className="text-center py-8"> <div className="text-center py-8">
<p className="text-gray-500"> !</p> <p className="text-gray-500">Be the first to reply!</p>
</div> </div>
)} )}
</div> </div>

View File

@ -238,7 +238,14 @@ export default function Landing() {
<Dialog open={showLoginModal} onOpenChange={setShowLoginModal}> <Dialog open={showLoginModal} onOpenChange={setShowLoginModal}>
<DialogContent className="sm:max-w-md" data-testid="modal-login"> <DialogContent className="sm:max-w-md" data-testid="modal-login">
<DialogHeader> <DialogHeader>
<DialogTitle>Login to SAPIENS</DialogTitle> <DialogTitle className="flex items-center gap-2">
<span>Login to</span>
<img
src="/attached_assets/logo_black_1759181850935.png"
alt="SAPIENS"
className="h-4 w-auto"
/>
</DialogTitle>
</DialogHeader> </DialogHeader>
<form onSubmit={handleLoginSubmit} className="space-y-4"> <form onSubmit={handleLoginSubmit} className="space-y-4">
<div> <div>

View File

@ -45,8 +45,8 @@ export default function MediaOutletAuction() {
}, },
onSuccess: () => { onSuccess: () => {
toast({ toast({
title: "입찰 성공", title: "Bid Successful",
description: "입찰이 성공적으로 등록되었습니다.", description: "Your bid has been successfully placed.",
}); });
setBidAmount(""); setBidAmount("");
setQualityScore(""); setQualityScore("");
@ -55,8 +55,8 @@ export default function MediaOutletAuction() {
}, },
onError: (error: any) => { onError: (error: any) => {
toast({ toast({
title: "입찰 실패", title: "Bid Failed",
description: error.message || "입찰 중 오류가 발생했습니다.", description: error.message || "An error occurred while placing your bid.",
variant: "destructive", variant: "destructive",
}); });
}, },
@ -65,8 +65,8 @@ export default function MediaOutletAuction() {
const handlePlaceBid = () => { const handlePlaceBid = () => {
if (!bidAmount) { if (!bidAmount) {
toast({ toast({
title: "입찰 금액 필요", title: "Bid Amount Required",
description: "입찰 금액을 입력해주세요.", description: "Please enter a bid amount.",
variant: "destructive", variant: "destructive",
}); });
return; return;
@ -75,8 +75,8 @@ export default function MediaOutletAuction() {
const amount = parseFloat(bidAmount); const amount = parseFloat(bidAmount);
if (isNaN(amount) || amount <= 0) { if (isNaN(amount) || amount <= 0) {
toast({ toast({
title: "유효하지 않은 금액", title: "Invalid Amount",
description: "올바른 입찰 금액을 입력해주세요.", description: "Please enter a valid bid amount.",
variant: "destructive", variant: "destructive",
}); });
return; return;
@ -84,8 +84,8 @@ export default function MediaOutletAuction() {
if (auction && auction.currentBid && amount <= parseFloat(auction.currentBid)) { if (auction && auction.currentBid && amount <= parseFloat(auction.currentBid)) {
toast({ toast({
title: "입찰 금액 부족", title: "Bid Too Low",
description: `현재 최고 입찰가(${auction.currentBid}원)보다 높은 금액을 입력해주세요.`, description: `Please enter an amount higher than the current bid (₩${auction.currentBid}).`,
variant: "destructive", variant: "destructive",
}); });
return; return;
@ -114,15 +114,15 @@ export default function MediaOutletAuction() {
const now = new Date(); const now = new Date();
const diff = end.getTime() - now.getTime(); const diff = end.getTime() - now.getTime();
if (diff <= 0) return "경매 종료"; if (diff <= 0) return "Auction Ended";
const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
if (days > 0) return `${days} ${hours}시간 남음`; if (days > 0) return `${days}d ${hours}h left`;
if (hours > 0) return `${hours}시간 ${minutes}분 남음`; if (hours > 0) return `${hours}h ${minutes}m left`;
return `${minutes}분 남음`; return `${minutes}m left`;
}; };
if (outletLoading || auctionLoading) { if (outletLoading || auctionLoading) {
@ -143,9 +143,9 @@ export default function MediaOutletAuction() {
return ( return (
<div className="min-h-screen bg-background flex items-center justify-center"> <div className="min-h-screen bg-background flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<h1 className="text-2xl font-bold mb-2"> </h1> <h1 className="text-2xl font-bold mb-2">Media Outlet Not Found</h1>
<p className="text-muted-foreground mb-4"> .</p> <p className="text-muted-foreground mb-4">The requested media outlet does not exist.</p>
<Button onClick={() => setLocation("/")}> </Button> <Button onClick={() => setLocation("/")}>Go Home</Button>
</div> </div>
</div> </div>
); );
@ -166,10 +166,10 @@ export default function MediaOutletAuction() {
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<Button variant="ghost" onClick={() => setLocation(`/media/${params?.slug}`)}> <Button variant="ghost" onClick={() => setLocation(`/media/${params?.slug}`)}>
Back to Outlet
</Button> </Button>
<Button variant="outline" onClick={() => setLocation("/")}> <Button variant="outline" onClick={() => setLocation("/")}>
Home
</Button> </Button>
</div> </div>
</div> </div>
@ -178,12 +178,12 @@ export default function MediaOutletAuction() {
<main className="max-w-4xl mx-auto px-6 py-8 pb-24"> <main className="max-w-4xl mx-auto px-6 py-8 pb-24">
<div className="text-center"> <div className="text-center">
<h1 className="text-2xl font-bold mb-2"> </h1> <h1 className="text-2xl font-bold mb-2">No Active Auction</h1>
<p className="text-muted-foreground mb-4"> <p className="text-muted-foreground mb-4">
{outlet.name} . There is currently no active auction for {outlet.name} management rights.
</p> </p>
<Button onClick={() => setLocation(`/media/${params?.slug}`)}> <Button onClick={() => setLocation(`/media/${params?.slug}`)}>
Back to Outlet Page
</Button> </Button>
</div> </div>
</main> </main>
@ -236,7 +236,7 @@ export default function MediaOutletAuction() {
)} )}
<div className="flex-1"> <div className="flex-1">
<h1 className="text-3xl font-bold mb-2">{outlet.name} </h1> <h1 className="text-3xl font-bold mb-2">{outlet.name} Management Rights Auction</h1>
<p className="text-lg text-muted-foreground mb-4"> <p className="text-lg text-muted-foreground mb-4">
{outlet.description} {outlet.description}
</p> </p>
@ -260,7 +260,7 @@ export default function MediaOutletAuction() {
<CardHeader> <CardHeader>
<CardTitle className="flex items-center space-x-2"> <CardTitle className="flex items-center space-x-2">
<Gavel className="h-5 w-5" /> <Gavel className="h-5 w-5" />
<span> </span> <span>Auction Info</span>
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-6"> <CardContent className="space-y-6">
@ -268,7 +268,7 @@ export default function MediaOutletAuction() {
<div className="flex items-center justify-between p-4 bg-muted rounded-lg"> <div className="flex items-center justify-between p-4 bg-muted rounded-lg">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<TrendingUp className="h-4 w-4 text-green-600" /> <TrendingUp className="h-4 w-4 text-green-600" />
<span className="font-medium"> </span> <span className="font-medium">Current Highest</span>
</div> </div>
<span className="text-2xl font-bold text-green-600"> <span className="text-2xl font-bold text-green-600">
{formatCurrency(parseFloat(auction.currentBid || "0"))} {formatCurrency(parseFloat(auction.currentBid || "0"))}
@ -278,7 +278,7 @@ export default function MediaOutletAuction() {
<div className="flex items-center justify-between p-4 bg-muted rounded-lg"> <div className="flex items-center justify-between p-4 bg-muted rounded-lg">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Clock className="h-4 w-4 text-orange-600" /> <Clock className="h-4 w-4 text-orange-600" />
<span className="font-medium"> </span> <span className="font-medium">Time Left</span>
</div> </div>
<span className="text-lg font-bold text-orange-600"> <span className="text-lg font-bold text-orange-600">
{formatTimeRemaining(auction.endDate)} {formatTimeRemaining(auction.endDate)}
@ -289,7 +289,7 @@ export default function MediaOutletAuction() {
<div className="flex items-center justify-between p-4 bg-muted rounded-lg"> <div className="flex items-center justify-between p-4 bg-muted rounded-lg">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<User className="h-4 w-4 text-blue-600" /> <User className="h-4 w-4 text-blue-600" />
<span className="font-medium"> </span> <span className="font-medium">Top Bidder</span>
</div> </div>
<span className="text-lg font-bold text-blue-600"> <span className="text-lg font-bold text-blue-600">
{auction.highestBidderId.slice(0, 3)}*** {auction.highestBidderId.slice(0, 3)}***
@ -300,7 +300,7 @@ export default function MediaOutletAuction() {
{auction.qualityScore && ( {auction.qualityScore && (
<div className="flex items-center justify-between p-4 bg-muted rounded-lg"> <div className="flex items-center justify-between p-4 bg-muted rounded-lg">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<span className="font-medium"> </span> <span className="font-medium">Quality Score</span>
</div> </div>
<span className="text-lg font-bold"> <span className="text-lg font-bold">
{auction.qualityScore}/100 {auction.qualityScore}/100
@ -314,41 +314,41 @@ export default function MediaOutletAuction() {
{/* Bidding Form */} {/* Bidding Form */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle></CardTitle> <CardTitle>Place Bid</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{!isAuthenticated ? ( {!isAuthenticated ? (
<div className="text-center p-6"> <div className="text-center p-6">
<p className="text-muted-foreground mb-4"> <p className="text-muted-foreground mb-4">
. You must be logged in to place a bid.
</p> </p>
<Button onClick={() => setLocation("/")}> <Button onClick={() => setLocation("/")}>
Go to Login
</Button> </Button>
</div> </div>
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<Label htmlFor="bidAmount"> ()</Label> <Label htmlFor="bidAmount">Bid Amount ()</Label>
<Input <Input
id="bidAmount" id="bidAmount"
type="number" type="number"
value={bidAmount} value={bidAmount}
onChange={(e) => setBidAmount(e.target.value)} onChange={(e) => setBidAmount(e.target.value)}
placeholder={`${parseFloat(auction.currentBid || "0") + 1000} 이상`} placeholder={`Minimum ₩${parseFloat(auction.currentBid || "0") + 1000}`}
min={parseFloat(auction.currentBid || "0") + 1} min={parseFloat(auction.currentBid || "0") + 1}
data-testid="input-bid-amount" data-testid="input-bid-amount"
/> />
</div> </div>
<div> <div>
<Label htmlFor="qualityScore"> (, 0-100)</Label> <Label htmlFor="qualityScore">Quality Score (optional, 0-100)</Label>
<Input <Input
id="qualityScore" id="qualityScore"
type="number" type="number"
value={qualityScore} value={qualityScore}
onChange={(e) => setQualityScore(e.target.value)} onChange={(e) => setQualityScore(e.target.value)}
placeholder="품질 점수를 입력하세요" placeholder="Enter quality score"
min={0} min={0}
max={100} max={100}
data-testid="input-quality-score" data-testid="input-quality-score"
@ -361,11 +361,11 @@ export default function MediaOutletAuction() {
className="w-full" className="w-full"
data-testid="button-place-bid" data-testid="button-place-bid"
> >
{placeBidMutation.isPending ? "입찰 중..." : "입찰하기"} {placeBidMutation.isPending ? "Placing Bid..." : "Place Bid"}
</Button> </Button>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
* . * Bids cannot be canceled once placed.
</p> </p>
</div> </div>
)} )}
@ -378,8 +378,8 @@ export default function MediaOutletAuction() {
<CardHeader> <CardHeader>
<CardTitle className="flex items-center space-x-2"> <CardTitle className="flex items-center space-x-2">
<TrendingUp className="h-5 w-5" /> <TrendingUp className="h-5 w-5" />
<span> </span> <span>Bid History</span>
<Badge variant="outline">{bids.length}</Badge> <Badge variant="outline">{bids.length} bids</Badge>
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@ -420,20 +420,20 @@ export default function MediaOutletAuction() {
{formatCurrency(parseFloat(bid.amount))} {formatCurrency(parseFloat(bid.amount))}
</span> </span>
{bid.id === highestBid.id && ( {bid.id === highestBid.id && (
<Badge className="bg-green-600"></Badge> <Badge className="bg-green-600">Highest</Badge>
)} )}
</div> </div>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
: <span data-testid={`bid-bidder-${bid.id}`}>{bid.bidderId.slice(0, 8)}***</span> Bidder: <span data-testid={`bid-bidder-${bid.id}`}>{bid.bidderId.slice(0, 8)}***</span>
{bid.qualityScore !== undefined && bid.qualityScore !== null && ( {bid.qualityScore !== undefined && bid.qualityScore !== null && (
<span className="ml-2">: {bid.qualityScore}</span> <span className="ml-2">Quality: {bid.qualityScore}</span>
)} )}
</div> </div>
</div> </div>
</div> </div>
<div className="text-right text-sm text-muted-foreground"> <div className="text-right text-sm text-muted-foreground">
<span data-testid={`bid-date-${bid.id}`}> <span data-testid={`bid-date-${bid.id}`}>
{new Date(bid.createdAt!).toLocaleDateString('ko-KR', { {new Date(bid.createdAt!).toLocaleDateString('en-US', {
month: 'short', month: 'short',
day: 'numeric', day: 'numeric',
hour: '2-digit', hour: '2-digit',
@ -448,8 +448,8 @@ export default function MediaOutletAuction() {
) : ( ) : (
<div className="text-center py-8 text-muted-foreground"> <div className="text-center py-8 text-muted-foreground">
<TrendingUp className="h-12 w-12 mx-auto mb-4 opacity-50" /> <TrendingUp className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p> </p> <p>No bids yet</p>
<p className="text-sm"> !</p> <p className="text-sm">Be the first to bid!</p>
</div> </div>
)} )}
</CardContent> </CardContent>
@ -458,23 +458,23 @@ export default function MediaOutletAuction() {
{/* Auction Description */} {/* Auction Description */}
<Card className="mt-8"> <Card className="mt-8">
<CardHeader> <CardHeader>
<CardTitle> </CardTitle> <CardTitle>Auction Guide</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="prose prose-sm max-w-none text-muted-foreground"> <div className="prose prose-sm max-w-none text-muted-foreground">
<p> <p>
<strong>{outlet.name}</strong> . This auction is for acquiring management rights to <strong>{outlet.name}</strong>.
: With management rights, you will be able to:
</p> </p>
<ul className="list-disc list-inside space-y-1 mt-4"> <ul className="list-disc list-inside space-y-1 mt-4">
<li> </li> <li>Manage and edit media outlet articles</li>
<li> </li> <li>Create and manage prediction market events</li>
<li> </li> <li>Edit media outlet profile information</li>
<li> </li> <li>Manage subscribers and followers</li>
</ul> </ul>
<p className="mt-4"> <p className="mt-4">
<strong>{formatTimeRemaining(auction.endDate)}</strong> . The auction will end in <strong>{formatTimeRemaining(auction.endDate)}</strong>.
. The highest bidder will acquire management rights.
</p> </p>
</div> </div>
</CardContent> </CardContent>