"use client"; import Image from "next/image"; import { useEffect, useMemo, useRef, useState } from "react"; import type { GamePricePoint, GameProfile } from "@/lib/game-profile"; import { steamdbRating } from "@/lib/game-profile"; import type { Dictionary } from "@/lib/i18n/get-dictionary"; type ModalDict = Dictionary["discord"]["gameModal"]; const STEAM_SCORE_KEYS: Record = { "Overwhelmingly Positive": "overwhelminglyPositive", "Very Positive": "veryPositive", Positive: "positive", "Mostly Positive": "mostlyPositive", Mixed: "mixed", "Mostly Negative": "mostlyNegative", Negative: "negative", "Very Negative": "veryNegative", "Overwhelmingly Negative": "overwhelminglyNegative", }; const LOCALE_TAGS: Record = { tr: "tr-TR", en: "en-US", de: "de-DE", }; const COUNTRIES: Record = { tr: "TR", en: "US", de: "DE", }; function localizeScore(label: string, dict: ModalDict): string { const key = STEAM_SCORE_KEYS[label]; return key ? dict.reviewScores[key] : label; } function formatRatingPercent(value: number, tag: string): string { try { return new Intl.NumberFormat(tag, { style: "percent", maximumFractionDigits: 2, }).format(value); } catch { return `%${(value * 100).toFixed(2)}`; } } function formatPrice(point: GamePricePoint, tag: string): string { try { return new Intl.NumberFormat(tag, { style: "currency", currency: point.currency, }).format(point.price); } catch { return `${point.price} ${point.currency}`; } } function formatReleaseDate(value: string, tag: string): string { if (/^\d{4}-\d{2}-\d{2}$/.test(value)) { const parsed = new Date(`${value}T00:00:00`); if (!Number.isNaN(parsed.getTime())) { return parsed.toLocaleDateString(tag, { day: "numeric", month: "long", year: "numeric", }); } } return value; } function formatCount(value: number | null | undefined, tag: string, unit: string): string { if (value == null) { return "—"; } return `${value.toLocaleString(tag)} ${unit}`; } function discountPercent(point: GamePricePoint): number | null { if (point.regular > point.price && point.regular > 0) { return Math.round((1 - point.price / point.regular) * 100); } return null; } export function GameProfileModal({ applicationId, heroSeed, lang, iconUrl, fallbackName, fallbackGameUrl, dict, onClose, }: { applicationId: string; heroSeed: number; lang: string; iconUrl: string | null; fallbackName: string; fallbackGameUrl: string; dict: ModalDict; onClose: () => void; }) { const [profile, setProfile] = useState(null); const [failed, setFailed] = useState(false); const [attempt, setAttempt] = useState(0); const [bannerPick, setBannerPick] = useState(null); const tag = LOCALE_TAGS[lang] ?? "en-US"; const country = COUNTRIES[lang] ?? "US"; useEffect(() => { let cancelled = false; async function load(): Promise { try { const response = await fetch( `/api/game/${applicationId}?lang=${lang}&country=${country}`, ); if (!response.ok) { throw new Error(`game api: ${response.status}`); } const json = (await response.json()) as GameProfile; if (!cancelled) { setProfile(json); setFailed(false); } } catch { if (!cancelled) { setFailed(true); } } } void load(); return () => { cancelled = true; }; }, [applicationId, lang, country, attempt]); const partialFail = !!profile && !profile.sources.igdb && !profile.sources.steam && !profile.sources.itad; useEffect(() => { function onKey(event: KeyboardEvent): void { if (event.key === "Escape") { onClose(); } } document.addEventListener("keydown", onKey); const previous = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.removeEventListener("keydown", onKey); document.body.style.overflow = previous; }; }, [onClose]); const gameUrl = profile?.gameUrl ?? fallbackGameUrl; const name = profile?.name ?? fallbackName; const best = profile?.itad?.prices ?? null; const low = profile?.itad?.historyLow ?? null; const isFree = !!best && best.price === 0; const hero = useMemo(() => { if (bannerPick) { return bannerPick; } const shots = profile?.screenshots ?? []; if (shots.length > 0) { return shots[heroSeed % shots.length] ?? null; } return profile?.artworkUrl ?? null; }, [profile, heroSeed, bannerPick]); const dbRating = profile?.steamReviews?.positive != null && profile?.steamReviews?.total != null ? steamdbRating(profile.steamReviews.positive, profile.steamReviews.total) : null; const itadReviews = profile?.itad?.reviews ?? []; const hasDetails = !!( profile && (profile.developers.length > 0 || profile.publishers.length > 0 || profile.releaseDate || profile.platforms.length > 0 || profile.links.length > 0) ); const metascore = itadReviews.find((r) => r.source === "Metascore") ?? null; const opencritic = itadReviews.find((r) => r.source === "OpenCritic") ?? null; const hasReviews = !!( profile?.steamReviews || (dbRating != null && profile?.steamId) || metascore || opencritic ); const stripRef = useRef(null); useEffect(() => { const strip = stripRef.current; if (!strip) { return; } function onWheel(event: WheelEvent): void { const el = stripRef.current; if (!el) { return; } if (Math.abs(event.deltaX) >= Math.abs(event.deltaY)) { return; } if (el.scrollWidth <= el.clientWidth + 1) { return; } event.preventDefault(); el.scrollLeft += event.deltaY; } strip.addEventListener("wheel", onWheel, { passive: false }); return () => strip.removeEventListener("wheel", onWheel); }, [profile]); function scrollStrip(by: number): void { stripRef.current?.scrollBy({ left: by, behavior: "smooth" }); } return (
event.stopPropagation()} > {hero ? (
) : null}
{iconUrl ? ( ) : (
{name.charAt(0).toUpperCase()}
)}

{name}

{profile && profile.genres.length > 0 ? (
{profile.genres.map((genre) => ( {genre} ))}
) : null}
{!profile && !failed ? (

{dict.loading}

) : null} {failed && !profile ? (

{dict.loadError}

) : null} {partialFail ? (

{dict.partialFail}

) : null} {profile?.description ? (

{profile.description}

) : null}
{profile?.steamUrl ? ( {dict.openOnSteam} ↗ ) : null} {profile?.steamId ? ( {dict.openOnSteamDb} ↗ ) : null} {profile?.igdbUrl ? ( {dict.openOnIgdb} ↗ ) : null} {dict.openOnDiscord} ↗ {profile?.itad?.pageUrl ? ( {dict.allPrices} ↗ ) : null}
{hasDetails ? (
{profile.developers.length > 0 ? (
{dict.developers}
{profile.developers.join(", ")}
) : null} {profile.publishers.length > 0 ? (
{dict.publishers}
{profile.publishers.join(", ")}
) : null} {profile.releaseDate ? (
{dict.releaseDate}
{formatReleaseDate(profile.releaseDate, tag)}
) : null} {profile.platforms.length > 0 ? (
{dict.platforms}
{profile.platforms.join(" · ")}
) : null} {profile.links.length > 0 ? (
{dict.links}
{profile.links.slice(0, 8).map((link) => ( {link.label} ↗ ))}
) : null}
) : null} {profile && hasReviews ? ( ) : null} {profile?.itad && (best || low) ? (

{dict.prices}

{isFree ? (

{dict.free}

) : (
{best ? (

{dict.bestPrice} · {best.shop}

{formatPrice(best, tag)}{" "} {discountPercent(best) ? ( -%{discountPercent(best)} ) : null}

) : null} {low ? (

{dict.historyLow} · {low.shop}

{formatPrice(low, tag)}

) : null}
)}
) : null} {profile && profile.screenshots.length > 0 ? (

{dict.screenshots}

{profile.screenshots.map((shot) => ( ))}
) : null}
); }