add manual addtiona of RH as these sometimes are missed
This commit is contained in:
3
data/overrides.json
Normal file
3
data/overrides.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"reverseHolofoil": {}
|
||||||
|
}
|
||||||
70
src/app/api/magikarp/override/route.ts
Normal file
70
src/app/api/magikarp/override/route.ts
Normal file
@@ -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<void> {
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import path from 'path';
|
|||||||
|
|
||||||
const DATA_DIR = path.join(process.cwd(), 'data');
|
const DATA_DIR = path.join(process.cwd(), 'data');
|
||||||
const CARDS_FILE = path.join(DATA_DIR, 'cards.json');
|
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
|
const TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
||||||
|
|
||||||
async function ensureDataDir() {
|
async function ensureDataDir() {
|
||||||
@@ -21,12 +22,28 @@ export async function GET(req: NextRequest) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await ensureDataDir();
|
await ensureDataDir();
|
||||||
|
// Load overrides best-effort
|
||||||
|
let overrides: { reverseHolofoil?: Record<string, boolean> } = {};
|
||||||
|
try {
|
||||||
|
const rawOv = await fs.readFile(OVERRIDES_FILE, 'utf-8');
|
||||||
|
overrides = rawOv ? JSON.parse(rawOv) : {};
|
||||||
|
} catch {}
|
||||||
|
const reverseMap = overrides?.reverseHolofoil || {};
|
||||||
if (!refresh) {
|
if (!refresh) {
|
||||||
// Try to serve from cache if exists
|
// Try to serve from cache if exists
|
||||||
try {
|
try {
|
||||||
const raw = await fs.readFile(CARDS_FILE, 'utf-8');
|
const raw = await fs.readFile(CARDS_FILE, 'utf-8');
|
||||||
if (raw) {
|
if (raw) {
|
||||||
const json = JSON.parse(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 ts = json?.updatedAt ? Date.parse(json.updatedAt) : NaN;
|
||||||
const fresh = Number.isFinite(ts) && (Date.now() - ts) < TTL_MS;
|
const fresh = Number.isFinite(ts) && (Date.now() - ts) < TTL_MS;
|
||||||
if (fresh) {
|
if (fresh) {
|
||||||
@@ -38,7 +55,11 @@ export async function GET(req: NextRequest) {
|
|||||||
|
|
||||||
// Fetch fresh from API
|
// Fetch fresh from API
|
||||||
const data = await fetchMagikarpCards({ q, page, pageSize });
|
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
|
// Write cache best-effort
|
||||||
try { await fs.writeFile(CARDS_FILE, JSON.stringify(payload, null, 2), 'utf-8'); } catch {}
|
try { await fs.writeFile(CARDS_FILE, JSON.stringify(payload, null, 2), 'utf-8'); } catch {}
|
||||||
return Response.json(payload, { status: 200 });
|
return Response.json(payload, { status: 200 });
|
||||||
|
|||||||
@@ -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 }) {
|
export default function CardItem({ card, checked, onToggle }:{ card: TcgCard; checked: VariantState; onToggle: (id:string, key: VariantKey)=>void }) {
|
||||||
const rate = useUsdGbpRate();
|
const rate = useUsdGbpRate();
|
||||||
const hasHolo = !!(card.variants?.holofoil || (card.tcgplayer as any)?.prices?.holofoil);
|
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<boolean>(false);
|
||||||
|
const [reverseRemoved, setReverseRemoved] = React.useState<boolean>(false);
|
||||||
|
const hasReverse = (hasReverseDetected && !reverseRemoved) || reverseOverride;
|
||||||
const prices = (card as any).tcgplayer?.prices || {};
|
const prices = (card as any).tcgplayer?.prices || {};
|
||||||
const rarityFull = card.rarity || '';
|
const rarityFull = card.rarity || '';
|
||||||
const rarityAbbr = (() => {
|
const rarityAbbr = (() => {
|
||||||
@@ -34,6 +37,28 @@ export default function CardItem({ card, checked, onToggle }:{ card: TcgCard; ch
|
|||||||
// Fallback: first letter of first word uppercased
|
// Fallback: first letter of first word uppercased
|
||||||
return rarityFull ? rarityFull.charAt(0).toUpperCase() : '';
|
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 getPrice = (key: VariantKey): number | undefined => {
|
||||||
const map: Record<VariantKey, string> = {
|
const map: Record<VariantKey, string> = {
|
||||||
base: 'normal',
|
base: 'normal',
|
||||||
@@ -66,15 +91,61 @@ export default function CardItem({ card, checked, onToggle }:{ card: TcgCard; ch
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 flex flex-col gap-2">
|
<div className="p-3 flex flex-col gap-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between gap-2">
|
||||||
{(() => {
|
{(() => {
|
||||||
const titleText = `${card.name} #${card.number}`;
|
const titleText = `${card.name} #${card.number}`;
|
||||||
const len = titleText.length;
|
const len = titleText.length;
|
||||||
const size = len > 34 ? 'text-xs' : len > 24 ? 'text-sm' : 'text-base';
|
const size = len > 34 ? 'text-xs' : len > 24 ? 'text-sm' : 'text-base';
|
||||||
return (
|
return (
|
||||||
<h3 className={`font-medium ${size} leading-tight`} title={titleText}>
|
<div className="flex items-center gap-1 min-w-0">
|
||||||
{card.name} <span className="text-slate-500">#{card.number}</span>
|
<h3 className={`font-medium ${size} leading-tight truncate`} title={titleText}>
|
||||||
</h3>
|
{card.name} <span className="text-slate-500">#{card.number}</span>
|
||||||
|
</h3>
|
||||||
|
{/* Show + only if no detected reverse and no override; show − only if override currently active */}
|
||||||
|
{(!hasReverseDetected && !reverseOverride) && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Add Reverse variant"
|
||||||
|
aria-label="Add Reverse variant"
|
||||||
|
className="ml-1 inline-flex h-5 w-5 items-center justify-center rounded border border-slate-300 text-slate-600 hover:bg-slate-50 text-xs flex-shrink-0"
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
try {
|
||||||
|
await fetch('/api/magikarp/override', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id: card.id, reverseHolofoil: true }),
|
||||||
|
});
|
||||||
|
setReverseOverride(true);
|
||||||
|
} catch {}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{reverseOverride && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Remove Reverse variant"
|
||||||
|
aria-label="Remove Reverse variant"
|
||||||
|
className="ml-1 inline-flex h-5 w-5 items-center justify-center rounded border border-slate-300 text-slate-600 hover:bg-slate-50 text-xs flex-shrink-0"
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
try {
|
||||||
|
await fetch('/api/magikarp/override', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id: card.id, reverseHolofoil: false }),
|
||||||
|
});
|
||||||
|
setReverseOverride(false);
|
||||||
|
setReverseRemoved(true);
|
||||||
|
} catch {}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
{rarityFull ? (
|
{rarityFull ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user