feat: Implement room restoration functionality and update room state management

This commit is contained in:
Alexa
2026-06-26 11:14:21 +03:00
parent 870baea34a
commit 7f105c0035
4 changed files with 74 additions and 18 deletions
+1
View File
@@ -84,6 +84,7 @@ export interface Room {
isAGRoom: boolean;
isDormRoom: boolean;
creatorPlayerId: number;
state: number;
blobCount: number;
}
+28 -6
View File
@@ -1006,7 +1006,11 @@ function DangerTab({ room, onAfter }: { room: RoomDetail; onAfter: () => void })
const [confirm2, setConfirm2] = useState('');
const [busy, setBusy] = useState(false);
const isCustom = !room.isDormRoom && room.owner.id !== 1;
// Anything not owned by the system seed account (id 1) can be purged —
// including dorms. Purging a dorm wipes its contents but the player's dorm
// regenerates on next join; the two typed-name confirmations are the guard.
const isCustom = room.owner.id !== 1;
const isArchived = room.state === 1;
const purgeReady = confirm1 === room.name && confirm2 === room.name && !busy;
const archive = async () => {
@@ -1019,6 +1023,16 @@ function DangerTab({ room, onAfter }: { room: RoomDetail; onAfter: () => void })
}
};
const restore = async () => {
try {
await api(`/rooms/${room.id}/restore`, { method: 'POST' });
toast.push(`Restored ${room.name}`, 'success');
onAfter();
} catch (e) {
toast.push((e as Error).message, 'error');
}
};
const purge = async () => {
if (!purgeReady) return;
setBusy(true);
@@ -1041,14 +1055,22 @@ function DangerTab({ room, onAfter }: { room: RoomDetail; onAfter: () => void })
<div className="card p-4">
<div className="flex items-center justify-between gap-3">
<div>
<h3 className="text-sm font-semibold text-ink-50">Archive</h3>
<h3 className="text-sm font-semibold text-ink-50">{isArchived ? 'Restore' : 'Archive'}</h3>
<p className="text-xs text-ink-400 mt-1">
Soft delete — sets State=Archived. The room stops appearing in browse/search but stays restorable.
{isArchived
? 'This room is archived (State=1). Restore sets State=0 (Active) so it shows up in browse/search again.'
: 'Soft delete — sets State=Archived. The room stops appearing in browse/search but stays restorable.'}
</p>
</div>
<button onClick={() => setArchiveOpen(true)} className="btn-secondary text-xs text-danger">
<Trash /> Archive
</button>
{isArchived ? (
<button onClick={restore} className="btn-secondary text-xs" disabled={busy}>
Restore
</button>
) : (
<button onClick={() => setArchiveOpen(true)} className="btn-secondary text-xs text-danger">
<Trash /> Archive
</button>
)}
</div>
</div>
{isCustom && (
+24 -10
View File
@@ -79,6 +79,16 @@ export function Rooms() {
}
};
const restore = async (r: Room) => {
try {
await api(`/rooms/${r.id}/restore`, { method: 'POST' });
toast.push(`Restored ${r.name}`, 'success');
refresh();
} catch (e) {
toast.push((e as Error).message, 'error');
}
};
return (
<div>
<PageHeader
@@ -172,18 +182,22 @@ export function Rooms() {
<td className="px-4 py-2.5 text-right text-ink-200 tabular-nums">{r.blobCount}</td>
<td className="px-4 py-2.5 text-right whitespace-nowrap">
<Link to={`/rooms/${r.id}`} className="btn-ghost text-xs">Manage</Link>
<button onClick={() => setPendingDelete(r)} className="btn-ghost text-xs text-danger ml-1" title="Soft archive (reversible)">
<Trash />
</button>
{!r.isDormRoom && (
<button
onClick={() => setPendingPurge(r)}
className="btn-ghost text-xs text-danger ml-1"
title="Purge — permanent hard delete (requires typing the room name twice)"
>
Purge
{r.state === 1 ? (
<button onClick={() => restore(r)} className="btn-ghost text-xs ml-1" title="Unarchive — set State=Active">
Restore
</button>
) : (
<button onClick={() => setPendingDelete(r)} className="btn-ghost text-xs text-danger ml-1" title="Soft archive (reversible)">
<Trash />
</button>
)}
<button
onClick={() => setPendingPurge(r)}
className="btn-ghost text-xs text-danger ml-1"
title="Purge — permanent hard delete (requires typing the room name twice)"
>
Purge
</button>
</td>
</tr>
))}