80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
import { formatPlaytime, gameStateColors } from "@/features/games/schema"
|
|
import { Badge } from "@/shared/components/ui/badge"
|
|
import type { Game } from "@/shared/db/schema"
|
|
|
|
interface GameDiscoverCardProps {
|
|
game: Game
|
|
}
|
|
|
|
function getSteamHeaderImage(sourceId: string): string {
|
|
return `https://cdn.akamai.steamstatic.com/steam/apps/${sourceId}/header.jpg`
|
|
}
|
|
|
|
function parseJsonArray(text: string | null): string[] {
|
|
if (!text) return []
|
|
try {
|
|
return JSON.parse(text)
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
export function GameDiscoverCard({ game }: GameDiscoverCardProps) {
|
|
const imageUrl =
|
|
game.source === "steam" ? getSteamHeaderImage(game.source_id) : null
|
|
const dotColor =
|
|
game.game_state !== "not_set" ? gameStateColors[game.game_state] : null
|
|
const genres = parseJsonArray(game.genres).slice(0, 3)
|
|
const rating =
|
|
game.aggregated_rating != null ? Math.round(game.aggregated_rating) : null
|
|
|
|
return (
|
|
<div className="flex h-full flex-col overflow-hidden rounded-xl border bg-card shadow-lg">
|
|
{imageUrl && (
|
|
<img
|
|
src={imageUrl}
|
|
alt={game.title}
|
|
className="w-full flex-1 object-cover min-h-0"
|
|
/>
|
|
)}
|
|
<div className="shrink-0 p-4">
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="truncate text-lg font-bold">{game.title}</h3>
|
|
<Badge variant="secondary" className="shrink-0">
|
|
{game.source}
|
|
</Badge>
|
|
{dotColor && (
|
|
<span
|
|
className={`inline-block h-2.5 w-2.5 shrink-0 rounded-full ${dotColor}`}
|
|
/>
|
|
)}
|
|
{rating != null && (
|
|
<Badge variant="outline" className="ml-auto shrink-0">
|
|
{rating}%
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
{genres.length > 0 && (
|
|
<div className="mt-1.5 flex flex-wrap gap-1">
|
|
{genres.map((g) => (
|
|
<Badge key={g} variant="secondary" className="text-[10px]">
|
|
{g}
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
)}
|
|
{game.summary && (
|
|
<p className="mt-1.5 line-clamp-2 text-xs text-muted-foreground">
|
|
{game.summary}
|
|
</p>
|
|
)}
|
|
{!game.summary && game.playtime_hours > 0 && (
|
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
{formatPlaytime(game.playtime_hours)} played
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|