Display bid history for media outlet auctions and fetch bid data

Add a new API endpoint and integrate bid history display into the media outlet auction page.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 069d4324-6c40-4355-955e-c714a50de1ea
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3df548ff-50ae-432f-9be4-25d34eccc983/069d4324-6c40-4355-955e-c714a50de1ea/6XTzcDL
This commit is contained in:
kimjaehyeon0101
2025-09-29 19:23:17 +00:00
parent d6682e32d9
commit 10d2fd7026
3 changed files with 107 additions and 0 deletions

View File

@ -30,6 +30,11 @@ export default function MediaOutletAuction() {
enabled: !!params?.slug enabled: !!params?.slug
}); });
const { data: bids = [], isLoading: bidsLoading } = useQuery<Bid[]>({
queryKey: ["/api/auctions", auction?.id, "bids"],
enabled: !!auction?.id
});
const placeBidMutation = useMutation({ const placeBidMutation = useMutation({
mutationFn: async (bidData: { amount: number; qualityScore?: number }) => { mutationFn: async (bidData: { amount: number; qualityScore?: number }) => {
return apiRequest("POST", `/api/media-outlets/${params?.slug}/auction/bids`, bidData); return apiRequest("POST", `/api/media-outlets/${params?.slug}/auction/bids`, bidData);
@ -42,6 +47,7 @@ export default function MediaOutletAuction() {
setBidAmount(""); setBidAmount("");
setQualityScore(""); setQualityScore("");
queryClient.invalidateQueries({ queryKey: ["/api/media-outlets", params?.slug, "auction"] }); queryClient.invalidateQueries({ queryKey: ["/api/media-outlets", params?.slug, "auction"] });
queryClient.invalidateQueries({ queryKey: ["/api/auctions", auction?.id, "bids"] });
}, },
onError: (error: any) => { onError: (error: any) => {
toast({ toast({
@ -363,6 +369,88 @@ export default function MediaOutletAuction() {
</Card> </Card>
</div> </div>
{/* Bid History */}
<Card className="mt-8">
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<TrendingUp className="h-5 w-5" />
<span> </span>
<Badge variant="outline">{bids.length}</Badge>
</CardTitle>
</CardHeader>
<CardContent>
{bidsLoading ? (
<div className="space-y-4">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="animate-pulse flex justify-between items-center p-3 border rounded">
<div className="h-4 bg-gray-300 rounded w-1/3"></div>
<div className="h-4 bg-gray-300 rounded w-1/4"></div>
</div>
))}
</div>
) : bids.length > 0 ? (
<div className="space-y-3 max-h-96 overflow-y-auto">
{(() => {
// Sort bids by time (newest first)
const sortedBids = [...bids].sort((a, b) =>
new Date(b.createdAt!).getTime() - new Date(a.createdAt!).getTime()
);
// Find highest bid by amount
const highestBid = bids.reduce((highest, current) =>
parseFloat(current.amount) > parseFloat(highest.amount) ? current : highest
, bids[0]);
return sortedBids.map((bid, index) => (
<div
key={bid.id}
className={`flex justify-between items-center p-4 border rounded-lg ${
index === 0 ? 'bg-green-50 border-green-200' : 'bg-gray-50 border-gray-200'
}`}
data-testid={`bid-item-${bid.id}`}
>
<div className="flex items-center space-x-4">
<div>
<div className="flex items-center space-x-2">
<span className="font-bold text-lg" data-testid={`bid-amount-${bid.id}`}>
{formatCurrency(parseFloat(bid.amount))}
</span>
{bid.id === highestBid.id && (
<Badge className="bg-green-600"></Badge>
)}
</div>
<div className="text-sm text-muted-foreground">
: <span data-testid={`bid-bidder-${bid.id}`}>{bid.bidderId.slice(0, 8)}***</span>
{bid.qualityScore !== undefined && bid.qualityScore !== null && (
<span className="ml-2">: {bid.qualityScore}</span>
)}
</div>
</div>
</div>
<div className="text-right text-sm text-muted-foreground">
<span data-testid={`bid-date-${bid.id}`}>
{new Date(bid.createdAt!).toLocaleDateString('ko-KR', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
</span>
</div>
</div>
));
})()}
</div>
) : (
<div className="text-center py-8 text-muted-foreground">
<TrendingUp className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p> </p>
<p className="text-sm"> !</p>
</div>
)}
</CardContent>
</Card>
{/* Auction Description */} {/* Auction Description */}
<Card className="mt-8"> <Card className="mt-8">
<CardHeader> <CardHeader>

View File

@ -168,6 +168,16 @@ export async function registerRoutes(app: Express): Promise<Server> {
} }
}); });
app.get('/api/auctions/:id/bids', async (req, res) => {
try {
const bids = await storage.getBidsByAuctionId(req.params.id);
res.json(bids);
} catch (error) {
console.error("Error fetching bids:", error);
res.status(500).json({ message: "Failed to fetch bids" });
}
});
app.post('/api/auctions/:id/bid', isAuthenticated, async (req: any, res) => { app.post('/api/auctions/:id/bid', isAuthenticated, async (req: any, res) => {
try { try {
const userId = req.user.claims.sub; const userId = req.user.claims.sub;

View File

@ -59,6 +59,7 @@ export interface IStorage {
getAuctionByMediaOutlet(mediaOutletId: string): Promise<Auction | undefined>; getAuctionByMediaOutlet(mediaOutletId: string): Promise<Auction | undefined>;
createAuction(auction: InsertAuction): Promise<Auction>; createAuction(auction: InsertAuction): Promise<Auction>;
placeBid(bid: InsertBid): Promise<Bid>; placeBid(bid: InsertBid): Promise<Bid>;
getBidsByAuctionId(auctionId: string): Promise<Bid[]>;
// Media outlet request operations // Media outlet request operations
getMediaOutletRequests(status?: string): Promise<MediaOutletRequest[]>; getMediaOutletRequests(status?: string): Promise<MediaOutletRequest[]>;
@ -252,6 +253,14 @@ export class DatabaseStorage implements IStorage {
return newBid; return newBid;
} }
async getBidsByAuctionId(auctionId: string): Promise<Bid[]> {
return await db
.select()
.from(bids)
.where(eq(bids.auctionId, auctionId))
.orderBy(desc(bids.createdAt));
}
// Media outlet request operations // Media outlet request operations
async getMediaOutletRequests(status?: string): Promise<MediaOutletRequest[]> { async getMediaOutletRequests(status?: string): Promise<MediaOutletRequest[]> {
const query = db.select().from(mediaOutletRequests); const query = db.select().from(mediaOutletRequests);