import { Trans, useLingui } from "@lingui/react/macro"; import { type Bot, GROUP_MEMBER_MAX, GROUP_MEMBER_MIN, type Group } from "@rakazo/contracts"; import { BotAvatar, Button, Input } from "@rakazo/ui-web"; import { Check, X } from "lucide-react"; import { useId, useMemo, useState } from "react"; function validSelection(name: string, selected: readonly string[]) { return ( Boolean(name.trim()) && selected.length >= GROUP_MEMBER_MIN && selected.length <= GROUP_MEMBER_MAX ); } function sameMembers(left: readonly string[], right: readonly string[]) { if (left.length !== right.length) return false; const rightIds = new Set(right); return left.every((id) => rightIds.has(id)); } function MemberPicker({ bots, selected, onChange, maxHeight, }: { bots: Bot[]; selected: string[]; onChange: (selected: string[]) => void; maxHeight: "max-h-[240px]" | "max-h-[280px]"; }) { const selectable = useMemo(() => bots.filter((bot) => !bot.archivedAt), [bots]); function toggle(botId: string) { if (selected.includes(botId)) { onChange(selected.filter((id) => id !== botId)); } else if (selected.length < GROUP_MEMBER_MAX) { onChange([...selected, botId]); } } return (
{selectable.map((bot) => { const checked = selected.includes(bot.id); return ( ); })}
); } export function CreateGroupForm({ bots, onCancel, onCreate, }: { bots: Bot[]; onCancel: () => void; onCreate: (input: { name: string; botIds: string[] }) => Promise; }) { const { t } = useLingui(); const nameId = useId(); const [name, setName] = useState(""); const [selected, setSelected] = useState([]); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); async function create() { if (submitting || !validSelection(name, selected)) return; setSubmitting(true); setError(null); try { await onCreate({ name: name.trim(), botIds: selected }); } catch (cause) { setError(cause instanceof Error ? cause.message : t`Could not create group`); } finally { setSubmitting(false); } } return (
New group
{error ? (

{error}

) : null}
Members (pick {GROUP_MEMBER_MIN}–{GROUP_MEMBER_MAX})
); } export function GroupSettings({ group, bots, onSave, onRemove, }: { group: Group; bots: Bot[]; onSave: (input: { name?: string; botIds?: string[] }) => Promise; onRemove: () => Promise; }) { const { t } = useLingui(); const nameId = useId(); const [name, setName] = useState(group.name); const [selected, setSelected] = useState(group.members.map((member) => member.botId)); const [pending, setPending] = useState<"save" | "remove" | null>(null); const [error, setError] = useState(null); async function mutate(kind: "save" | "remove", action: () => Promise) { if (pending) return; setPending(kind); setError(null); try { await action(); } catch (cause) { setError( cause instanceof Error ? cause.message : kind === "save" ? t`Could not save group` : t`Could not remove group`, ); } finally { setPending(null); } } function save() { return onSave({ name: name.trim() !== group.name ? name.trim() : undefined, botIds: sameMembers( selected, group.members.map((member) => member.botId), ) ? undefined : selected, }); } return (
Group settings
{error ? (

{error}

) : null}
Members ({GROUP_MEMBER_MIN}–{GROUP_MEMBER_MAX})
); } export function memberName( members: Group["members"] | undefined, botId: string | undefined, ): string | undefined { if (!botId || !members) return undefined; return members.find((member) => member.botId === botId)?.name; }