Phone/merit authoring UI: NPC contacts + phone node + terminal NPC picker
Admin NPC editor gains phone number and email fields. The mystery graph editor gets Phone and Merit in the node palette; a phone node's terminals each get an NPC picker (bind the callee you reach by dialing their number), and merit nodes get an "awards achievement" field. Node summaries + type colors for both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+7
-2
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { MysteryGraphEditor } from './mysteryGraph'
|
||||
|
||||
type Pose = { poseKey: string; assetId: string; url: string }
|
||||
type Npc = { id: string; key: string; name: string; role: string; defaultPose: string | null; poses: Pose[]; inUse: boolean }
|
||||
type Npc = { id: string; key: string; name: string; role: string; defaultPose: string | null; phoneNumber: string | null; email: string | null; poses: Pose[]; inUse: boolean }
|
||||
type Mystery = { id: string; slug: string; title: string; nodes: number }
|
||||
|
||||
async function json<T>(url: string, init?: RequestInit): Promise<T> {
|
||||
@@ -172,15 +172,18 @@ function NpcEditor({ npc, onChanged, setStatus }: { npc: Npc; onChanged: (key?:
|
||||
const [name, setName] = useState(npc.name)
|
||||
const [role, setRole] = useState(npc.role)
|
||||
const [defaultPose, setDefaultPose] = useState(npc.defaultPose || '')
|
||||
const [phoneNumber, setPhoneNumber] = useState(npc.phoneNumber || '')
|
||||
const [email, setEmail] = useState(npc.email || '')
|
||||
const [poseKey, setPoseKey] = useState('')
|
||||
const [busy, setBusy] = useState(false)
|
||||
const fileRef = useRef<HTMLInputElement>(null)
|
||||
const dirty = name !== npc.name || role !== npc.role || (defaultPose || null) !== npc.defaultPose
|
||||
|| (phoneNumber || null) !== npc.phoneNumber || (email || null) !== npc.email
|
||||
|
||||
const save = async () => {
|
||||
setBusy(true)
|
||||
try {
|
||||
await json(`/api/admin/npcs/${npc.id}`, { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name, role, defaultPose: defaultPose || null }) })
|
||||
await json(`/api/admin/npcs/${npc.id}`, { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name, role, defaultPose: defaultPose || null, phoneNumber: phoneNumber || null, email: email || null }) })
|
||||
await onChanged(npc.key); setStatus(`Saved ${name}`)
|
||||
} catch (error) { setStatus(String((error as Error).message || error)) } finally { setBusy(false) }
|
||||
}
|
||||
@@ -213,6 +216,8 @@ function NpcEditor({ npc, onChanged, setStatus }: { npc: Npc; onChanged: (key?:
|
||||
</div>
|
||||
<div className="admin-field"><label>Display name</label><input value={name} onChange={event => setName(event.target.value)} /></div>
|
||||
<div className="admin-field"><label>Role / affiliation</label><input value={role} onChange={event => setRole(event.target.value)} placeholder="Glitch University · Investigative Method" /></div>
|
||||
<div className="admin-field"><label>Phone number</label><input value={phoneNumber} onChange={event => setPhoneNumber(event.target.value)} placeholder="55501" /></div>
|
||||
<div className="admin-field"><label>Email</label><input value={email} onChange={event => setEmail(event.target.value)} placeholder="hunter@glitch.university" /></div>
|
||||
<div className="admin-field"><label>Default pose</label>
|
||||
<select value={defaultPose} onChange={event => setDefaultPose(event.target.value)}>
|
||||
<option value="">— none —</option>
|
||||
|
||||
Reference in New Issue
Block a user