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;
}