From da70f6f184bd191dff590c868d2fe27f9bdab016 Mon Sep 17 00:00:00 2001 From: Foohoo Date: Sun, 17 Aug 2025 15:24:25 +0100 Subject: [PATCH] add manual addtiona of RH as these sometimes are missed --- data/overrides.json | 3 + src/app/api/magikarp/override/route.ts | 70 ++++++++++++++++++++++ src/app/api/magikarp/route.ts | 23 +++++++- src/components/CardItem.tsx | 81 ++++++++++++++++++++++++-- 4 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 data/overrides.json create mode 100644 src/app/api/magikarp/override/route.ts diff --git a/data/overrides.json b/data/overrides.json new file mode 100644 index 0000000..29bac2f --- /dev/null +++ b/data/overrides.json @@ -0,0 +1,3 @@ +{ + "reverseHolofoil": {} +} \ No newline at end of file diff --git a/src/app/api/magikarp/override/route.ts b/src/app/api/magikarp/override/route.ts new file mode 100644 index 0000000..bd020f4 --- /dev/null +++ b/src/app/api/magikarp/override/route.ts @@ -0,0 +1,70 @@ +import { NextRequest } from 'next/server'; +import { promises as fs } from 'fs'; +import path from 'path'; + +const DATA_DIR = path.join(process.cwd(), 'data'); +const OVERRIDES_FILE = path.join(DATA_DIR, 'overrides.json'); +const CARDS_FILE = path.join(DATA_DIR, 'cards.json'); + +async function ensureFile(): Promise { + try { await fs.mkdir(DATA_DIR, { recursive: true }); } catch {} + try { await fs.access(OVERRIDES_FILE); } + catch { await fs.writeFile(OVERRIDES_FILE, JSON.stringify({ reverseHolofoil: {} }, null, 2), 'utf-8'); } +} + +export async function GET() { + try { + await ensureFile(); + const raw = await fs.readFile(OVERRIDES_FILE, 'utf-8'); + const data = raw ? JSON.parse(raw) : { reverseHolofoil: {} }; + return Response.json({ data }, { status: 200 }); + } catch (err) { + console.error('GET /api/magikarp/override error', err); + return Response.json({ data: { reverseHolofoil: {} } }, { status: 200 }); + } +} + +// Body: { id: string, reverseHolofoil?: boolean } +export async function POST(req: NextRequest) { + try { + const { id, reverseHolofoil } = await req.json(); + if (!id || typeof id !== 'string') { + return Response.json({ error: 'Missing id' }, { status: 400 }); + } + await ensureFile(); + const raw = await fs.readFile(OVERRIDES_FILE, 'utf-8'); + const json = raw ? JSON.parse(raw) : { reverseHolofoil: {} }; + if (!json.reverseHolofoil || typeof json.reverseHolofoil !== 'object') json.reverseHolofoil = {}; + if (reverseHolofoil === false) { + delete json.reverseHolofoil[id]; + } else { + json.reverseHolofoil[id] = true; + } + await fs.writeFile(OVERRIDES_FILE, JSON.stringify(json, null, 2), 'utf-8'); + // Best-effort: patch cached cards.json so UI reflects without refresh + try { + const rawCards = await fs.readFile(CARDS_FILE, 'utf-8'); + if (rawCards) { + const cardsJson = JSON.parse(rawCards); + if (Array.isArray(cardsJson?.data)) { + let mutated = false; + cardsJson.data = cardsJson.data.map((c: any) => { + if (c?.id === id) { + const next = { ...c, variants: { ...(c.variants || {}), reverseHolofoil: reverseHolofoil !== false } }; + mutated = true; + return next; + } + return c; + }); + if (mutated) { + await fs.writeFile(CARDS_FILE, JSON.stringify(cardsJson, null, 2), 'utf-8'); + } + } + } + } catch {} + return Response.json({ ok: true }, { status: 200 }); + } catch (err) { + console.error('POST /api/magikarp/override error', err); + return Response.json({ error: 'Write failed' }, { status: 500 }); + } +} diff --git a/src/app/api/magikarp/route.ts b/src/app/api/magikarp/route.ts index 3791269..7c02aac 100644 --- a/src/app/api/magikarp/route.ts +++ b/src/app/api/magikarp/route.ts @@ -5,6 +5,7 @@ import path from 'path'; const DATA_DIR = path.join(process.cwd(), 'data'); const CARDS_FILE = path.join(DATA_DIR, 'cards.json'); +const OVERRIDES_FILE = path.join(DATA_DIR, 'overrides.json'); const TTL_MS = 24 * 60 * 60 * 1000; // 24 hours async function ensureDataDir() { @@ -21,12 +22,28 @@ export async function GET(req: NextRequest) { try { await ensureDataDir(); + // Load overrides best-effort + let overrides: { reverseHolofoil?: Record } = {}; + try { + const rawOv = await fs.readFile(OVERRIDES_FILE, 'utf-8'); + overrides = rawOv ? JSON.parse(rawOv) : {}; + } catch {} + const reverseMap = overrides?.reverseHolofoil || {}; if (!refresh) { // Try to serve from cache if exists try { const raw = await fs.readFile(CARDS_FILE, 'utf-8'); if (raw) { const json = JSON.parse(raw); + // Apply overrides on the fly to cached payload + if (Array.isArray(json?.data)) { + json.data = json.data.map((c: any) => { + if (reverseMap[c.id]) { + c.variants = { ...(c.variants || {}), reverseHolofoil: true }; + } + return c; + }); + } const ts = json?.updatedAt ? Date.parse(json.updatedAt) : NaN; const fresh = Number.isFinite(ts) && (Date.now() - ts) < TTL_MS; if (fresh) { @@ -38,7 +55,11 @@ export async function GET(req: NextRequest) { // Fetch fresh from API const data = await fetchMagikarpCards({ q, page, pageSize }); - const payload = { ...data, cached: false, updatedAt: new Date().toISOString() }; + // Apply overrides to fresh data + const patched = Array.isArray(data?.data) + ? { ...data, data: data.data.map((c: any) => (reverseMap[c.id] ? { ...c, variants: { ...(c.variants || {}), reverseHolofoil: true } } : c)) } + : data; + const payload = { ...patched, cached: false, updatedAt: new Date().toISOString() }; // Write cache best-effort try { await fs.writeFile(CARDS_FILE, JSON.stringify(payload, null, 2), 'utf-8'); } catch {} return Response.json(payload, { status: 200 }); diff --git a/src/components/CardItem.tsx b/src/components/CardItem.tsx index 9e663a8..589e748 100644 --- a/src/components/CardItem.tsx +++ b/src/components/CardItem.tsx @@ -9,7 +9,10 @@ import { useUsdGbpRate } from '@/lib/useExchangeRate'; export default function CardItem({ card, checked, onToggle }:{ card: TcgCard; checked: VariantState; onToggle: (id:string, key: VariantKey)=>void }) { const rate = useUsdGbpRate(); const hasHolo = !!(card.variants?.holofoil || (card.tcgplayer as any)?.prices?.holofoil); - const hasReverse = !!(card.variants?.reverseHolofoil || (card.tcgplayer as any)?.prices?.reverseHolofoil); + const hasReverseDetected = !!(card.variants?.reverseHolofoil || (card.tcgplayer as any)?.prices?.reverseHolofoil); + const [reverseOverride, setReverseOverride] = React.useState(false); + const [reverseRemoved, setReverseRemoved] = React.useState(false); + const hasReverse = (hasReverseDetected && !reverseRemoved) || reverseOverride; const prices = (card as any).tcgplayer?.prices || {}; const rarityFull = card.rarity || ''; const rarityAbbr = (() => { @@ -34,6 +37,28 @@ export default function CardItem({ card, checked, onToggle }:{ card: TcgCard; ch // Fallback: first letter of first word uppercased return rarityFull ? rarityFull.charAt(0).toUpperCase() : ''; })(); + + // Initialize local override based on server overrides to show the − toggle on reloads + React.useEffect(() => { + let cancelled = false; + (async () => { + try { + const res = await fetch('/api/magikarp/override', { cache: 'no-store' }); + if (!res.ok) return; + const json = await res.json(); + const map = json?.data?.reverseHolofoil || {}; + if (!cancelled && map && typeof map === 'object') { + if (map[card.id]) { + setReverseOverride(true); + setReverseRemoved(false); + } + } + } catch { + // ignore + } + })(); + return () => { cancelled = true; }; + }, [card.id]); const getPrice = (key: VariantKey): number | undefined => { const map: Record = { base: 'normal', @@ -66,15 +91,61 @@ export default function CardItem({ card, checked, onToggle }:{ card: TcgCard; ch />
-
+
{(() => { const titleText = `${card.name} #${card.number}`; const len = titleText.length; const size = len > 34 ? 'text-xs' : len > 24 ? 'text-sm' : 'text-base'; return ( -

- {card.name} #{card.number} -

+
+

+ {card.name} #{card.number} +

+ {/* Show + only if no detected reverse and no override; show − only if override currently active */} + {(!hasReverseDetected && !reverseOverride) && ( + + )} + {reverseOverride && ( + + )} +
); })()} {rarityFull ? (