"use client"; import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { ArcadeImportModal } from "@/components/arcade/ArcadeImportModal"; import { CROSSWORD_SIZE_TARGETS } from "@/lib/arcade/crosswordEngine"; import type { ArcadeAttemptSummary, ArcadePackSummary, CrosswordLayout, CrosswordSize } from "@/types/arcade"; const SIZES: { value: CrosswordSize; label: string; note: string }[] = [ { value: "mini", label: "Mini", note: "Up to 15 words" }, { value: "standard", label: "Standard", note: "Up to 30 words" }, { value: "large", label: "Large", note: "Up to 50 words" }, { value: "extra-large", label: "Extra Large", note: "Up to 80 words" }, ]; export function CrosswordHub({ classId, classSlug, initialPacks }: { classId: string; classSlug: string; initialPacks: ArcadePackSummary[] }) { const router = useRouter(); const [selectedId, setSelectedId] = useState(initialPacks[0]?.id ?? ""); const [showImport, setShowImport] = useState(false); const [size, setSize] = useState("standard"); const [instantCheck, setInstantCheck] = useState(initialPacks[0]?.defaultInstantCheck ?? false); const [allowHints, setAllowHints] = useState(initialPacks[0]?.defaultAllowHints ?? true); const [attempts, setAttempts] = useState([]); const [attemptsLoading, setAttemptsLoading] = useState(initialPacks.length > 0); const [previewLayout, setPreviewLayout] = useState(null); const [previewLoading, setPreviewLoading] = useState(initialPacks.length > 0); const effectiveSelectedId = initialPacks.some((pack) => pack.id === selectedId) ? selectedId : initialPacks[0]?.id ?? ""; const selected = useMemo(() => initialPacks.find((pack) => pack.id === effectiveSelectedId) ?? null, [effectiveSelectedId, initialPacks]); useEffect(() => { if (!selected) return; fetch(`/api/arcade/packs/${selected.id}/attempts`) .then((response) => response.ok ? response.json() : []) .then(setAttempts) .finally(() => setAttemptsLoading(false)); }, [selected]); useEffect(() => { if (!selected) return; let current = true; fetch(`/api/arcade/packs/${selected.id}/layout?size=${size}`) .then((response) => response.ok ? response.json() : null) .then((layout) => { if (current) setPreviewLayout(layout); }) .finally(() => { if (current) setPreviewLoading(false); }); return () => { current = false; }; }, [selected, size]); function selectPack(pack: ArcadePackSummary) { if (pack.id === effectiveSelectedId) return; setSelectedId(pack.id); setInstantCheck(pack.defaultInstantCheck); setAllowHints(pack.defaultAllowHints); setAttempts([]); setAttemptsLoading(true); setPreviewLayout(null); setPreviewLoading(true); } function selectSize(nextSize: CrosswordSize) { if (nextSize === size) return; setSize(nextSize); setPreviewLayout(null); setPreviewLoading(true); } async function renamePack(pack: ArcadePackSummary) { const name = window.prompt("Rename Crossword pack", pack.name)?.trim(); if (!name || name === pack.name) return; await fetch(`/api/arcade/packs/${pack.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }) }); router.refresh(); } async function deletePack(pack: ArcadePackSummary) { if (!window.confirm(`Delete “${pack.name}” and its attempt history?`)) return; await fetch(`/api/arcade/packs/${pack.id}`, { method: "DELETE" }); if (selectedId === pack.id) setSelectedId(""); router.refresh(); } const playHref = selected ? `/${classSlug}/arcade/crossword/${selected.id}/play?size=${size}&instant=${instantCheck ? "1" : "0"}&hints=${allowHints ? "1" : "0"}` : "#"; return (
← Back to Arcade

The evening edition

Crossword

Choose a terminology bank, set the edition size, and work the clues at your own pace.

{initialPacks.length === 0 ? (
{Array.from({ length: 25 }, (_, index) => )}

Print your first edition

Import one JSON object containing exactly 80 clue-and-answer entries.

) : (

Puzzle banks

{initialPacks.map((pack) => { const active = pack.id === effectiveSelectedId; return
; })}
)} {showImport && setShowImport(false)} onImported={() => { setShowImport(false); router.refresh(); }} />}
); } function CrosswordBoardPreview({ layout, loading }: { layout: CrosswordLayout | null; loading: boolean }) { const cellSize = layout ? Math.max(3, Math.min(8, Math.floor(190 / Math.max(layout.rows, layout.columns)))) : 6; return
Layout preview{layout && {layout.entries.length} words · {layout.columns} × {layout.rows}}
{loading ?
Composing puzzle…
: layout ?
{layout.cells.map((cell) => )}
:

Preview unavailable

}
; }