Initial crossword addition and arcade redesign
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m41s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m41s
This commit is contained in:
parent
611a585757
commit
5bec95fb30
32 changed files with 1635 additions and 56 deletions
|
|
@ -2,14 +2,16 @@
|
|||
|
||||
import { useState } from "react";
|
||||
import { GenerateTab } from "@/components/import/GenerateTab";
|
||||
import type { ArcadeImportBatchPreview } from "@/types/arcade";
|
||||
import type { ArcadeGameKey, ArcadeImportBatchPreview } from "@/types/arcade";
|
||||
|
||||
export function ArcadeImportModal({
|
||||
classId,
|
||||
gameType,
|
||||
onClose,
|
||||
onImported,
|
||||
}: {
|
||||
classId: string;
|
||||
gameType: ArcadeGameKey;
|
||||
onClose: () => void;
|
||||
onImported: () => void;
|
||||
}) {
|
||||
|
|
@ -31,7 +33,7 @@ export function ArcadeImportModal({
|
|||
const response = await fetch("/api/arcade/import/preview", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ gameType: "connections", rawJson }),
|
||||
body: JSON.stringify({ gameType, rawJson }),
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
|
|
@ -56,9 +58,9 @@ export function ArcadeImportModal({
|
|||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
classId,
|
||||
gameType: "connections",
|
||||
gameType,
|
||||
rawJson,
|
||||
names: names.map((name) => name.trim()),
|
||||
...(gameType === "connections" ? { names: names.map((name) => name.trim()) } : { name: names[0]?.trim() }),
|
||||
warningsAcknowledged: acknowledged,
|
||||
}),
|
||||
});
|
||||
|
|
@ -77,9 +79,9 @@ export function ArcadeImportModal({
|
|||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center sm:p-4">
|
||||
<button className="absolute inset-0 bg-black/45 backdrop-blur-sm" onClick={onClose} aria-label="Close import dialog" />
|
||||
<section role="dialog" aria-modal="true" aria-labelledby="arcade-import-title" className="connections-world connections-import relative flex max-h-[92vh] w-full max-w-2xl flex-col rounded-t-3xl border border-border-light bg-bg-surface shadow-[var(--shadow-modal)] sm:rounded-3xl">
|
||||
<section role="dialog" aria-modal="true" aria-labelledby="arcade-import-title" className={`${gameType === "connections" ? "connections-world connections-import" : "crossword-world crossword-import"} relative flex max-h-[92vh] w-full max-w-2xl flex-col rounded-t-3xl border border-border-light bg-bg-surface shadow-[var(--shadow-modal)] sm:rounded-3xl`}>
|
||||
<div className="flex items-center justify-between px-6 pt-5">
|
||||
<h2 id="arcade-import-title" className="editorial-title text-2xl text-text-heading">Import Connections pack</h2>
|
||||
<h2 id="arcade-import-title" className="editorial-title text-2xl text-text-heading">Import {gameType === "connections" ? "Connections" : "Crossword"} pack</h2>
|
||||
<button onClick={onClose} className="grid h-10 w-10 place-items-center rounded-xl text-xl text-text-muted hover:bg-bg-surface-alt" aria-label="Close">×</button>
|
||||
</div>
|
||||
<div className="flex gap-1 border-b border-border-light px-6 pt-4">
|
||||
|
|
@ -88,10 +90,10 @@ export function ArcadeImportModal({
|
|||
))}
|
||||
</div>
|
||||
<div className="overflow-y-auto p-6">
|
||||
{tab === "generate" ? <GenerateTab importType="connections" /> : (
|
||||
{tab === "generate" ? <GenerateTab importType={gameType} /> : (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-text-secondary">Paste one pack object or an array of up to ten packs. Every board is validated before anything is saved.</p>
|
||||
<textarea value={rawJson} onChange={(event) => { setRawJson(event.target.value); setPreview(null); setError(""); }} rows={11} placeholder="Paste Connections JSON object or array here…" className="w-full resize-y rounded-xl border border-border bg-bg-surface-alt/60 px-4 py-3 font-mono text-sm text-text-heading focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20" />
|
||||
<p className="text-sm text-text-secondary">{gameType === "connections" ? "Paste one pack object or an array of up to ten packs." : "Paste one Crossword pack containing exactly 80 entries."} Every board is validated before anything is saved.</p>
|
||||
<textarea value={rawJson} onChange={(event) => { setRawJson(event.target.value); setPreview(null); setError(""); }} rows={11} placeholder={`Paste ${gameType === "connections" ? "Connections" : "Crossword"} JSON here…`} className="w-full resize-y rounded-xl border border-border bg-bg-surface-alt/60 px-4 py-3 font-mono text-sm text-text-heading focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20" />
|
||||
{!preview && <button onClick={previewImport} disabled={!rawJson.trim() || busy} className="w-full min-h-11 rounded-xl bg-primary px-5 text-sm font-bold text-white disabled:opacity-45">{busy ? "Checking…" : "Preview pack(s)"}</button>}
|
||||
{error && <div role="alert" className="rounded-xl border border-error/20 bg-error-bg p-4 text-sm text-error"><p className="font-bold">{error}</p>{details.length > 0 && <ul className="mt-2 list-disc space-y-1 pl-5">{details.map((detail) => <li key={detail}>{detail}</li>)}</ul>}</div>}
|
||||
{preview && (
|
||||
|
|
@ -102,8 +104,13 @@ export function ArcadeImportModal({
|
|||
<article key={`${pack.name}-${index}`} className="rounded-xl border border-border-light bg-bg-surface p-3">
|
||||
<label className="mb-1 block text-xs font-bold uppercase tracking-wide text-text-muted">Pack {index + 1} name</label>
|
||||
<input value={names[index] ?? ""} onChange={(event) => setNames((current) => current.map((name, nameIndex) => nameIndex === index ? event.target.value : name))} className="w-full rounded-lg border border-border bg-bg-surface-alt/50 px-3 py-2 font-bold text-text-heading" />
|
||||
<div className="mt-2 flex flex-wrap gap-1.5">{pack.categories.map((category) => <span key={category} className="rounded-full bg-bg-surface-alt px-2 py-1 text-[11px] font-bold text-text-secondary">{category}</span>)}</div>
|
||||
<p className="mt-2 text-xs text-text-muted">16 tiles · 4 groups</p>
|
||||
{gameType === "connections" ? <>
|
||||
<div className="mt-2 flex flex-wrap gap-1.5">{pack.categories.map((category) => <span key={category} className="rounded-full bg-bg-surface-alt px-2 py-1 text-[11px] font-bold text-text-secondary">{category}</span>)}</div>
|
||||
<p className="mt-2 text-xs text-text-muted">16 tiles · 4 groups</p>
|
||||
</> : <>
|
||||
<p className="mt-2 text-xs text-text-muted">80 entries · four board sizes</p>
|
||||
<div className="mt-2 grid grid-cols-2 gap-2 text-xs text-text-secondary">{pack.layoutPreviews?.map((layout) => <span key={layout.size} className="rounded-lg bg-bg-surface-alt px-2 py-1.5 capitalize">{layout.size}: {layout.placedCount}/{layout.targetCount}</span>)}</div>
|
||||
</>}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue