From 0b6c687f7fbee612a273c2b188888ab9688bee37 Mon Sep 17 00:00:00 2001 From: Foohoo Date: Sun, 17 Aug 2025 12:58:16 +0100 Subject: [PATCH] add local storage in json for collection --- data/checklist.json | 20 +++++++++++++ src/app/api/checklist/route.ts | 45 +++++++++++++++++++++++++++++ src/app/page.tsx | 10 +++++-- src/lib/checklist.ts | 52 ++++++++++++++++++++++++++++------ 4 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 data/checklist.json create mode 100644 src/app/api/checklist/route.ts diff --git a/data/checklist.json b/data/checklist.json new file mode 100644 index 0000000..39c10c8 --- /dev/null +++ b/data/checklist.json @@ -0,0 +1,20 @@ +{ + "det1-8": { + "base": true, + "holofoil": true + }, + "xy7-19": { + "base": true, + "reverseHolofoil": true + }, + "sm115-15": { + "base": true, + "reverseHolofoil": true + }, + "sm75-19": { + "base": true + }, + "mcd16-4": { + "base": true + } +} \ No newline at end of file diff --git a/src/app/api/checklist/route.ts b/src/app/api/checklist/route.ts new file mode 100644 index 0000000..366b3ba --- /dev/null +++ b/src/app/api/checklist/route.ts @@ -0,0 +1,45 @@ +import { NextRequest } from 'next/server'; +import { promises as fs } from 'fs'; +import path from 'path'; + +const DATA_DIR = path.join(process.cwd(), 'data'); +const FILE_PATH = path.join(DATA_DIR, 'checklist.json'); + +async function ensureFile(): Promise { + try { + await fs.mkdir(DATA_DIR, { recursive: true }); + } catch {} + try { + await fs.access(FILE_PATH); + } catch { + // create empty object file on first use + await fs.writeFile(FILE_PATH, JSON.stringify({}, null, 2), 'utf-8'); + } +} + +export async function GET() { + try { + await ensureFile(); + const raw = await fs.readFile(FILE_PATH, 'utf-8'); + const data = raw ? JSON.parse(raw) : {}; + return Response.json({ data }, { status: 200 }); + } catch (err) { + console.error('GET /api/checklist error', err); + return Response.json({ data: {} }, { status: 200 }); + } +} + +export async function PUT(req: NextRequest) { + try { + const body = await req.json(); + if (typeof body !== 'object' || body === null || Array.isArray(body)) { + return Response.json({ error: 'Invalid payload' }, { status: 400 }); + } + await ensureFile(); + await fs.writeFile(FILE_PATH, JSON.stringify(body, null, 2), 'utf-8'); + return Response.json({ ok: true }, { status: 200 }); + } catch (err) { + console.error('PUT /api/checklist error', err); + return Response.json({ error: 'Write failed' }, { status: 500 }); + } +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 2d5eced..328dbd5 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useEffect, useMemo, useState } from 'react'; import type { TcgCard } from '@/types/pokemon'; -import { loadChecklist, saveChecklist } from '@/lib/checklist'; +import { loadChecklistServer, saveChecklistServer } from '@/lib/checklist'; import type { ChecklistV2, VariantKey } from '@/lib/checklist'; import CardGrid from '@/components/CardGrid'; import Header from '@/components/Header'; @@ -18,7 +18,10 @@ export default function Page() { const [tab, setTab] = useState<'uncollected' | 'collected'>('uncollected'); useEffect(() => { - setChecklist(loadChecklist()); + (async () => { + const data = await loadChecklistServer(); + setChecklist(data); + })(); }, []); useEffect(() => { @@ -48,7 +51,8 @@ export default function Page() { // Clean up empty records to keep storage tidy const hasAny = Object.values(current).some(Boolean); if (hasAny) next[id] = current; else delete next[id]; - saveChecklist(next); + // fire-and-forget save to server; fallback handled in lib + void saveChecklistServer(next); return next; }); } diff --git a/src/lib/checklist.ts b/src/lib/checklist.ts index effdef7..ff7f3bc 100644 --- a/src/lib/checklist.ts +++ b/src/lib/checklist.ts @@ -18,27 +18,23 @@ export function loadChecklistV1(): Set { } } -// New storage (v2) -export function loadChecklist(): ChecklistV2 { +// Local-only helpers (kept for migration / fallback) +export function loadChecklistLocal(): ChecklistV2 { if (typeof window === 'undefined') return {}; try { const v2 = localStorage.getItem(CHECKLIST_KEY_V2); if (v2) return JSON.parse(v2) as ChecklistV2; - // migrate from v1 if present const v1 = loadChecklistV1(); if (v1.size === 0) return {}; const migrated: ChecklistV2 = {}; - v1.forEach((id) => { - migrated[id] = { base: true }; - }); - saveChecklist(migrated); + v1.forEach((id) => { migrated[id] = { base: true }; }); return migrated; } catch { return {}; } } -export function saveChecklist(state: ChecklistV2) { +export function saveChecklistLocal(state: ChecklistV2) { if (typeof window === 'undefined') return; try { localStorage.setItem(CHECKLIST_KEY_V2, JSON.stringify(state)); @@ -46,3 +42,43 @@ export function saveChecklist(state: ChecklistV2) { // ignore } } + +// Server-backed persistence +export async function loadChecklistServer(): Promise { + try { + const res = await fetch('/api/checklist', { cache: 'no-store' }); + if (!res.ok) throw new Error('failed'); + const json = await res.json(); + const serverData = (json?.data || {}) as ChecklistV2; + // If server is empty, try migrate from local and push + const hasServerData = serverData && Object.keys(serverData).length > 0; + if (!hasServerData && typeof window !== 'undefined') { + const local = loadChecklistLocal(); + const hasLocal = Object.keys(local).length > 0; + if (hasLocal) { + await saveChecklistServer(local); + // Clear legacy keys after successful push + try { localStorage.removeItem(CHECKLIST_KEY_V2); } catch {} + try { localStorage.removeItem(CHECKLIST_KEY_V1); } catch {} + return local; + } + } + return serverData || {}; + } catch { + // As a fallback (e.g., during dev with API not ready), use local + return loadChecklistLocal(); + } +} + +export async function saveChecklistServer(state: ChecklistV2): Promise { + try { + await fetch('/api/checklist', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(state), + }); + } catch { + // best-effort: persist locally so user doesn't lose progress offline + saveChecklistLocal(state); + } +}