import { useEffect, useState } from "react"; import { freshness } from "../../../shared/feed.ts"; import type { CodesFeed, RedeemCode } from "../../../shared/codes.ts"; import { REDEEM } from "../../../shared/redeem.ts"; import type { GameId } from "../../../shared/schema.ts"; import type { ReaderState } from "../../state/reader/schema.ts"; import { codeExpired, codeUsed, newCodes } from "../../state/reader/codes.ts"; import { actionMeta } from "../../state/reader/actions.ts"; import { formatInZone } from "../../state/displayTime.ts"; import type { ActionHandler } from "./GameSettings.tsx"; export function CodesPage({ game, feed, state, onAction, }: { game: string; feed: CodesFeed | null; state: ReaderState; onAction: ActionHandler; }) { const [message, setMessage] = useState(""); const accounts = Object.values(state.accounts).filter( (a) => a.game === game && !a.retiredAt, ); const codes = feed?.codes.filter((c) => c.game === game) ?? []; // Redeemed on every server, or past its stated expiry: greyed, below the // codes still to use. const now = Date.now(); const done = (c: RedeemCode) => codeExpired(c, now) || codeUsed( c, accounts.map((a) => a.id), state.codeRedeemed, ); const open = codes.filter((c) => !done(c)), closed = codes.filter(done); const redeem = REDEEM[game as GameId]; const unseen = feed ? newCodes(feed, state.codeSeen[game] ?? [], game) : []; useEffect(() => { if (unseen.length) void onAction({ type: "seen", kind: "codeSeen", id: game, ids: unseen.map((c) => c.id), ...actionMeta(), }); }, [game, feed, onAction]); const row = (c: RedeemCode) => (
{c.code} {c.reward &&

{c.reward}

} {codeExpired(c, now) ? "Expired" : c.expiresAt ? `Expires ${formatInZone(c.expiresAt, true, typeof state.prefs.timezone === "string" ? state.prefs.timezone : null)}` : c.expiresOn ? `Expires on ${c.expiresOn} (time not stated)` : "Expiry not announced"}{" "} ·{" "} Source
{accounts.map((a) => ( ))}
); return (

Redeem codes

New-code notices are separate from redemption. Mark codes redeemed for each account you play.

{redeem && (
{redeem.url && ( Redeem on the official site ↗ )}

{redeem.url ? "Or in game:" : "Redeem in game:"}{" "} {redeem.inGame}

)}

{message}

{feed && freshness( feed.sources.filter((s) => s.game === game), Date.now(), ).stale.length > 0 && (

Code data may be out of date. Check the linked source before relying on a code's availability.

)} {!feed ? (

The codes feed is unavailable. Event tracking is still available.

) : !codes.length ? (

No published codes are available for this game.

) : ( <> {open.length ? ( open.map(row) ) : (

Every code here is redeemed or expired.

)} {closed.length > 0 && (

Redeemed or expired

{closed.map(row)}
)} )}
); }