From 2e1fff4c2dd3cac8fdb41beee2bf6db37b4bcf8a Mon Sep 17 00:00:00 2001 From: Aaron Wood Date: Sat, 11 Apr 2026 11:55:43 -0400 Subject: [PATCH] feat: replace inline create form with CharacterWizard multi-step modal Co-Authored-By: Claude Sonnet 4.6 --- client/src/pages/CampaignView.module.css | 132 ----------------------- client/src/pages/CampaignView.tsx | 91 ++-------------- 2 files changed, 9 insertions(+), 214 deletions(-) diff --git a/client/src/pages/CampaignView.module.css b/client/src/pages/CampaignView.module.css index f1bd6bd..fa2a770 100644 --- a/client/src/pages/CampaignView.module.css +++ b/client/src/pages/CampaignView.module.css @@ -165,135 +165,3 @@ font-style: italic; grid-column: 1 / -1; } - -.createModal { - position: fixed; - inset: 0; - background: var(--bg-overlay); - display: flex; - align-items: center; - justify-content: center; - z-index: 100; - padding: 1rem; -} - -.createForm { - background-color: var(--bg-modal); - background-image: var(--texture-surface), var(--texture-speckle); - background-size: - 256px 256px, - 128px 128px; - background-repeat: repeat, repeat; - border: 2px solid rgba(var(--gold-rgb), 0.3); - border-radius: 4px; - padding: 1.5rem; - width: 100%; - max-width: 400px; - box-shadow: - 0 8px 40px rgba(var(--shadow-rgb), 0.7), - 0 2px 8px rgba(var(--shadow-rgb), 0.5), - inset 0 1px 0 rgba(var(--gold-rgb), 0.1), - inset 0 0 60px rgba(var(--shadow-rgb), 0.2); -} - -.createTitle { - font-family: "Cinzel", Georgia, serif; - font-size: 1.2rem; - font-weight: 700; - margin-bottom: 1rem; - color: var(--gold); - letter-spacing: 0.05em; - text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); -} - -.formField { - display: flex; - flex-direction: column; - gap: 0.25rem; - margin-bottom: 0.75rem; -} - -.formLabel { - font-family: "Cinzel", Georgia, serif; - font-size: 0.75rem; - color: var(--text-secondary); - text-transform: uppercase; - font-weight: 600; - letter-spacing: 0.05em; -} - -.formInput { - padding: 0.5rem 0.75rem; - background: var(--bg-inset); - border: 1px solid rgba(var(--gold-rgb), 0.15); - border-radius: 4px; - color: var(--text-primary); - font-size: 0.9rem; - font-family: "Alegreya", Georgia, serif; -} - -.formInput:focus { - outline: none; - border-color: var(--gold); -} - -.formSelect { - padding: 0.5rem 0.75rem; - background: var(--bg-inset); - border: 1px solid rgba(var(--gold-rgb), 0.15); - border-radius: 4px; - color: var(--text-primary); - font-size: 0.9rem; - font-family: "Alegreya", Georgia, serif; -} - -.formSelect option { - background: var(--bg-modal); - color: var(--text-primary); -} - -.formActions { - display: flex; - gap: 0.5rem; - justify-content: flex-end; - margin-top: 1rem; -} - -.formBtn { - padding: 0.5rem 1rem; - border-radius: 4px; - font-weight: 600; - cursor: pointer; - font-size: 0.9rem; -} - -.formBtnPrimary { - background: var(--btn-gold-bg); - color: var(--btn-active-text); - border: none; - font-family: "Cinzel", Georgia, serif; - box-shadow: - 0 2px 4px rgba(var(--shadow-rgb), 0.3), - inset 0 1px 0 rgba(255, 255, 255, 0.1); - text-shadow: 0 1px 1px rgba(var(--shadow-rgb), 0.2); -} - -.formBtnPrimary:hover { - background: linear-gradient( - 180deg, - var(--gold-bright), - var(--gold-hover) 40%, - var(--gold) - ); -} - -.formBtnSecondary { - background: transparent; - color: var(--text-secondary); - border: 1px solid rgba(var(--gold-rgb), 0.2); -} - -.formBtnSecondary:hover { - border-color: rgba(var(--gold-rgb), 0.4); - color: var(--text-primary); -} diff --git a/client/src/pages/CampaignView.tsx b/client/src/pages/CampaignView.tsx index 8af0643..ecf1669 100644 --- a/client/src/pages/CampaignView.tsx +++ b/client/src/pages/CampaignView.tsx @@ -29,12 +29,10 @@ import ParticleOverlay from "../components/ParticleOverlay"; import ThreeFireOverlay from "../components/ThreeFireOverlay"; import type { AtmosphereState } from "../lib/atmosphereTypes"; import { defaultAtmosphere } from "../lib/atmosphereTypes"; -import SelectDropdown from "../components/SelectDropdown"; +import CharacterWizard from "../components/CharacterWizard"; +import type { CreateCharacterData } from "../api"; import styles from "./CampaignView.module.css"; -const CLASSES = ["Fighter", "Priest", "Thief", "Wizard"]; -const ANCESTRIES = ["Human", "Elf", "Dwarf", "Halfling", "Goblin", "Half-Orc"]; - export default function CampaignView() { const { id } = useParams<{ id: string }>(); const campaignId = Number(id); @@ -43,12 +41,6 @@ export default function CampaignView() { const [characters, setCharacters] = useState([]); const [selectedId, setSelectedId] = useState(null); const [showCreate, setShowCreate] = useState(false); - const [newChar, setNewChar] = useState({ - name: "", - class: "Fighter", - ancestry: "Human", - hp_max: 1, - }); const [campaignName, setCampaignName] = useState(""); const [rolls, setRolls] = useState([]); const [freshIds, setFreshIds] = useState>(new Set()); @@ -293,12 +285,9 @@ export default function CampaignView() { }; }, []); - async function handleCreate(e: React.FormEvent) { - e.preventDefault(); - if (!newChar.name.trim()) return; + async function handleCreate(data: CreateCharacterData) { try { - await createCharacter(campaignId, newChar); - setNewChar({ name: "", class: "Fighter", ancestry: "Human", hp_max: 1 }); + await createCharacter(campaignId, data); setShowCreate(false); } catch (err) { console.error("Failed to create character:", err); @@ -450,73 +439,11 @@ export default function CampaignView() { )} {showCreate && ( -
setShowCreate(false)} - > -
e.stopPropagation()} - onSubmit={handleCreate} - > -
New Character
-
- - - setNewChar({ ...newChar, name: e.target.value }) - } - autoFocus - /> -
-
- - setNewChar({ ...newChar, class: v })} - /> -
-
- - setNewChar({ ...newChar, ancestry: v })} - /> -
-
- - - setNewChar({ ...newChar, hp_max: Number(e.target.value) }) - } - /> -
-
- - -
-
-
+ setShowCreate(false)} + /> )}