desloppify: code health overhaul plus abuse hardening (140 green, strict 87.2)
Honest baseline after removing 45 gaming suppressions. Structural: lib/game plus root hooks extraction, CORS origin echo, DEV_ORIGINS layering. Fixes: error-cause binding, discord 5xx split, null-cache guard, co-located tests, rejecting-fetch coverage, naming and typing cleanup, socket controller, cache bound, cascade helpers, latch resets. Hardening: per-IP rate limit on API routes, deterministic-miss UX, retry cooldown plus cap. Suite 140 green, build plus lint clean.
This commit is contained in:
78 files changed
+4908
-1430
No files matched your search
@@ -0,0 +1,262 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useEffect, useRef } from "react";
|
||||
import type { GamePricePoint, GameProfile } from "@/lib/game/game-profile-types";
|
||||
import {
|
||||
describeProfile,
|
||||
discountPercent,
|
||||
formatCount,
|
||||
formatPrice,
|
||||
formatRatingPercent,
|
||||
localizeScore,
|
||||
type ModalDict,
|
||||
} from "@/lib/game/game-format";
|
||||
import { steamdbAppUrl } from "@/lib/game/discord-app";
|
||||
|
||||
export function ModalReviews({
|
||||
profile,
|
||||
dict,
|
||||
localeTag,
|
||||
}: {
|
||||
profile: GameProfile;
|
||||
dict: ModalDict;
|
||||
localeTag: string;
|
||||
}) {
|
||||
const { dbRating, hasReviews, metascore, opencritic } = describeProfile(profile);
|
||||
if (!hasReviews) {
|
||||
return null;
|
||||
}
|
||||
const steamdbRating: { score: number; url: string } | null =
|
||||
dbRating != null && profile.steamId
|
||||
? { score: dbRating, url: steamdbAppUrl(profile.steamId) }
|
||||
: null;
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<p className="font-mono text-xs uppercase tracking-wider text-zinc-500">
|
||||
{dict.reviews}
|
||||
</p>
|
||||
<div className="mt-1.5 grid gap-2 sm:grid-cols-2">
|
||||
{profile.steamReviews ? (
|
||||
<a
|
||||
href={profile.steamReviews.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-xl border border-white/5 bg-white/[0.03] p-3 transition-colors hover:border-white/15"
|
||||
>
|
||||
<p className="font-mono text-xs text-zinc-500">{dict.totalReviews}</p>
|
||||
<p className="mt-1 text-sm font-semibold">
|
||||
{localizeScore(profile.steamReviews.label, dict)}
|
||||
</p>
|
||||
<p className="font-mono text-xs text-zinc-500">
|
||||
{formatCount(profile.steamReviews.total, localeTag, dict.reviewsUnit)}
|
||||
</p>
|
||||
</a>
|
||||
) : null}
|
||||
{steamdbRating ? (
|
||||
<a
|
||||
href={steamdbRating.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-xl border border-white/5 bg-white/[0.03] p-3 transition-colors hover:border-white/15"
|
||||
>
|
||||
<p className="font-mono text-xs text-zinc-500">{dict.steamdbRating}</p>
|
||||
<p className="mt-1 text-sm font-semibold">
|
||||
{formatRatingPercent(steamdbRating.score, localeTag)}
|
||||
</p>
|
||||
<p className="font-mono text-xs text-zinc-500">steamdb.info ↗</p>
|
||||
</a>
|
||||
) : null}
|
||||
{metascore ? (
|
||||
<a
|
||||
href={metascore.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-xl border border-white/5 bg-white/[0.03] p-3 transition-colors hover:border-white/15"
|
||||
>
|
||||
<p className="font-mono text-xs text-zinc-500">Metacritic</p>
|
||||
<p className="mt-1 text-sm font-semibold">
|
||||
{metascore.score ?? "—"}
|
||||
{metascore.score != null ? (
|
||||
<span className="font-normal text-zinc-500">/100</span>
|
||||
) : null}
|
||||
</p>
|
||||
<p className="font-mono text-xs text-zinc-500">
|
||||
{formatCount(metascore.count, localeTag, dict.reviewsUnit)}
|
||||
</p>
|
||||
</a>
|
||||
) : null}
|
||||
{opencritic ? (
|
||||
<a
|
||||
href={opencritic.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-xl border border-white/5 bg-white/[0.03] p-3 transition-colors hover:border-white/15"
|
||||
>
|
||||
<p className="font-mono text-xs text-zinc-500">OpenCritic</p>
|
||||
<p className="mt-1 text-sm font-semibold">
|
||||
{opencritic.score ?? "—"}
|
||||
{opencritic.score != null ? (
|
||||
<span className="font-normal text-zinc-500">/100</span>
|
||||
) : null}
|
||||
</p>
|
||||
<p className="font-mono text-xs text-zinc-500">
|
||||
{formatCount(opencritic.count, localeTag, dict.reviewsUnit)}
|
||||
</p>
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ModalPrices({
|
||||
dict,
|
||||
localeTag,
|
||||
bestPrice,
|
||||
historyLow,
|
||||
isFree,
|
||||
}: {
|
||||
dict: ModalDict;
|
||||
localeTag: string;
|
||||
bestPrice: GamePricePoint | null;
|
||||
historyLow: GamePricePoint | null;
|
||||
isFree: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<p className="font-mono text-xs uppercase tracking-wider text-zinc-500">
|
||||
{dict.prices}
|
||||
</p>
|
||||
{isFree ? (
|
||||
<p className="mt-1.5 inline-block rounded-full border border-mint/20 bg-mint/5 px-4 py-1.5 text-xs font-semibold text-mint">
|
||||
{dict.free}
|
||||
</p>
|
||||
) : (
|
||||
<div className="mt-1.5 grid gap-2 sm:grid-cols-2">
|
||||
{bestPrice ? (
|
||||
<a
|
||||
href={bestPrice.url || undefined}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-xl border border-mint/20 bg-mint/5 p-3 transition-colors hover:border-mint/40"
|
||||
>
|
||||
<p className="font-mono text-xs text-mint">
|
||||
{dict.bestPrice} · {bestPrice.shop}
|
||||
</p>
|
||||
<p className="mt-1 text-lg font-bold">
|
||||
{formatPrice(bestPrice, localeTag)}{" "}
|
||||
{discountPercent(bestPrice) ? (
|
||||
<span className="ml-1 rounded bg-mint/15 px-1.5 py-0.5 align-middle font-mono text-xs text-mint">
|
||||
-%{discountPercent(bestPrice)}
|
||||
</span>
|
||||
) : null}
|
||||
</p>
|
||||
</a>
|
||||
) : null}
|
||||
{historyLow ? (
|
||||
<div className="rounded-xl border border-white/5 bg-white/[0.03] p-3">
|
||||
<p className="font-mono text-xs text-zinc-500">
|
||||
{dict.historyLow} · {historyLow.shop}
|
||||
</p>
|
||||
<p className="mt-1 text-lg font-bold text-zinc-200">
|
||||
{formatPrice(historyLow, localeTag)}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ModalScreenshots({
|
||||
profile,
|
||||
dict,
|
||||
hero,
|
||||
onPick,
|
||||
}: {
|
||||
profile: GameProfile;
|
||||
dict: ModalDict;
|
||||
hero: string | null;
|
||||
onPick: (shot: string) => void;
|
||||
}) {
|
||||
const stripRef = useRef<HTMLDivElement | null>(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 (
|
||||
<div className="mt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="font-mono text-xs uppercase tracking-wider text-zinc-500">
|
||||
{dict.screenshots}
|
||||
</p>
|
||||
<div className="flex gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => scrollStrip(-360)}
|
||||
aria-label={dict.scrollBack}
|
||||
className="rounded-full border border-white/10 px-2.5 py-0.5 text-xs text-zinc-400 transition-colors hover:border-white/25 hover:text-white"
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => scrollStrip(360)}
|
||||
aria-label={dict.scrollForward}
|
||||
className="rounded-full border border-white/10 px-2.5 py-0.5 text-xs text-zinc-400 transition-colors hover:border-white/25 hover:text-white"
|
||||
>
|
||||
›
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref={stripRef} className="no-scrollbar mt-1.5 flex gap-2 overflow-x-auto p-1">
|
||||
{profile.screenshots.map((shot) => (
|
||||
<button
|
||||
key={shot}
|
||||
type="button"
|
||||
onClick={() => onPick(shot)}
|
||||
aria-label={dict.screenshots}
|
||||
className={`shrink-0 overflow-hidden rounded-xl transition-all ${
|
||||
hero === shot ? "ring-2 ring-blurple" : "opacity-70 hover:opacity-100"
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={shot}
|
||||
alt=""
|
||||
width={320}
|
||||
height={180}
|
||||
loading="lazy"
|
||||
className="h-auto w-56"
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user