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]>
79 lines
3.3 KiB
TypeScript
79 lines
3.3 KiB
TypeScript
// The fields of a hosted room, for making one and for editing it.
|
|
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field";
|
|
import { Input } from "@/components/ui/input";
|
|
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import type { HostedRoom } from "@/lib/api";
|
|
|
|
/** The moderators as the edit field shows them: names where known, else IDs. */
|
|
const moderatorList = (room: HostedRoom) => room.moderators.map((id, i) => room.moderatorNames[i] || String(id)).join(", ");
|
|
|
|
export function RoomFields({ room, canAdult, moderators }: { room?: HostedRoom; canAdult: boolean; moderators?: boolean }) {
|
|
return (
|
|
<>
|
|
<Field>
|
|
<FieldLabel htmlFor="name">Name</FieldLabel>
|
|
<Input id="name" name="name" defaultValue={room?.name} required maxLength={64} className="max-w-md" />
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel htmlFor="description">Description</FieldLabel>
|
|
<Textarea id="description" name="description" defaultValue={room?.description} maxLength={512} rows={3} />
|
|
</Field>
|
|
<div className="grid gap-4 sm:grid-cols-3">
|
|
<Field>
|
|
<FieldLabel htmlFor="roomPid">Room shell</FieldLabel>
|
|
<Input
|
|
id="roomPid"
|
|
name="roomPid"
|
|
type="number"
|
|
inputMode="numeric"
|
|
min={1}
|
|
required
|
|
defaultValue={room?.roomPid}
|
|
placeholder="Product ID"
|
|
/>
|
|
<FieldDescription>An IMVU room product's ID, from its shop page.</FieldDescription>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel htmlFor="rating">Rating</FieldLabel>
|
|
<NativeSelect id="rating" name="rating" defaultValue={room?.rating ?? "general"} className="w-full">
|
|
<NativeSelectOption value="general">General</NativeSelectOption>
|
|
<NativeSelectOption value="adult" disabled={!canAdult && room?.rating !== "adult"}>
|
|
Adult
|
|
</NativeSelectOption>
|
|
</NativeSelect>
|
|
{!canAdult && <FieldDescription>Adult needs an age-verified account.</FieldDescription>}
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel htmlFor="maxUsers">Max people</FieldLabel>
|
|
<Input
|
|
id="maxUsers"
|
|
name="maxUsers"
|
|
type="number"
|
|
inputMode="numeric"
|
|
min={2}
|
|
max={30}
|
|
required
|
|
defaultValue={room?.maxUsers ?? 10}
|
|
/>
|
|
</Field>
|
|
</div>
|
|
<Field>
|
|
<FieldLabel htmlFor="privacy">Privacy</FieldLabel>
|
|
<NativeSelect id="privacy" name="privacy" defaultValue={room?.privacy ?? "public"} className="w-64">
|
|
<NativeSelectOption value="public">Public</NativeSelectOption>
|
|
<NativeSelectOption value="private">Private</NativeSelectOption>
|
|
</NativeSelect>
|
|
<FieldDescription>Private: unlisted; only you, moderators and people invited get in.</FieldDescription>
|
|
</Field>
|
|
{moderators && room && (
|
|
<Field>
|
|
<FieldLabel htmlFor="moderators">Moderators</FieldLabel>
|
|
<Input id="moderators" name="moderators" defaultValue={moderatorList(room)} placeholder="IMVU names or IDs" />
|
|
<FieldDescription>Up to 20, comma-separated. They can boot and ban.</FieldDescription>
|
|
</Field>
|
|
)}
|
|
</>
|
|
);
|
|
}
|