CI / test (push) Successful in 1m10s
Moderators (up to 20 IMVU accounts, set by ID or name) and the owner account boot and ban over realtime (room.boot); bans are listed and lifted over REST. Private rooms are unlisted and joined by the owner, moderators and holders of an accepted invite (room.invite / room.invited / room.invite_reply / room.invite_answer). Room images (PNG/JPEG by magic bytes, up to 512 KB) are stored in the database and served with cache headers. Per-person favorites, ratings and recent rooms. room.watch pushes room.occupancy; participants carry their IMVU name, and GET .../participants lists them. Contract in docs/hosted-rooms.md, "Phase 2". Co-Authored-By: Claude Opus 5.5 <[email protected]>
100 lines
3.7 KiB
TypeScript
100 lines
3.7 KiB
TypeScript
import type { Metadata } from "next";
|
|
import Link from "next/link";
|
|
import { notFound, redirect } from "next/navigation";
|
|
import { ActionForm, SubmitButton } from "@/components/forms";
|
|
import { PageHeader } from "@/components/status";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { FieldGroup } from "@/components/ui/field";
|
|
import { api, getMe, unwrap } from "@/lib/api";
|
|
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field";
|
|
import { Input } from "@/components/ui/input";
|
|
import { closeRoom, removeImage, saveImage, saveRoom } from "../../actions";
|
|
import { RoomFields } from "../../room-fields";
|
|
|
|
export const metadata: Metadata = { title: "Edit room" };
|
|
|
|
export default async function EditRoom({ params }: { params: Promise<{ n: string }> }) {
|
|
const { n } = await params;
|
|
if (!/^[0-9]{1,9}$/.test(n)) notFound();
|
|
const me = await getMe();
|
|
if (!me) redirect(`/signin?next=/rooms/${n}/edit`);
|
|
const [{ rooms }, { links }] = await Promise.all([
|
|
api["hosted-rooms"].get({ query: { mine: "1" } }).then(unwrap),
|
|
api.account.get().then(unwrap),
|
|
]);
|
|
const room = rooms.find((r) => String(r.n) === n);
|
|
if (!room) notFound();
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<PageHeader title={`Edit ${room.name}`}>
|
|
<Link href="/rooms" className="underline-offset-4 hover:underline">
|
|
Back to rooms
|
|
</Link>
|
|
</PageHeader>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardDescription>
|
|
Owned by {room.ownerName} · {room.occupancy} here now
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ActionForm action={saveRoom} fields={{ n: room.n }}>
|
|
<FieldGroup>
|
|
<RoomFields room={room} canAdult={links.some((l) => l.adult)} moderators />
|
|
<div>
|
|
<SubmitButton pendingLabel="Saving…">Save</SubmitButton>
|
|
</div>
|
|
</FieldGroup>
|
|
</ActionForm>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Image</CardTitle>
|
|
<CardDescription>Shown in room lists.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{room.image && (
|
|
// The room's image, from the hub.
|
|
<img src={room.image} alt="" className="aspect-[332/281] w-full max-w-xs rounded-md bg-muted object-cover" />
|
|
)}
|
|
<ActionForm action={saveImage} fields={{ n: room.n }}>
|
|
<Field>
|
|
<FieldLabel htmlFor="image">{room.image ? "Replace" : "Upload"}</FieldLabel>
|
|
<Input id="image" name="image" type="file" accept="image/png,image/jpeg" required className="max-w-md" />
|
|
<FieldDescription>PNG or JPEG, up to 512 KB.</FieldDescription>
|
|
</Field>
|
|
<div>
|
|
<SubmitButton pendingLabel="Uploading…">Save image</SubmitButton>
|
|
</div>
|
|
</ActionForm>
|
|
{room.image && (
|
|
<ActionForm action={removeImage} fields={{ n: room.n }}>
|
|
<SubmitButton variant="outline" pendingLabel="Removing…">
|
|
Remove image
|
|
</SubmitButton>
|
|
</ActionForm>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Close room</CardTitle>
|
|
<CardDescription>Everyone in it is sent out. It can't be opened again.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ActionForm action={closeRoom} fields={{ n: room.n }}>
|
|
<SubmitButton variant="destructive" pendingLabel="Closing…" confirm={`Close ${room.name}?`}>
|
|
Close room
|
|
</SubmitButton>
|
|
</ActionForm>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|