BangSo/apps/mobile/app/new-group.tsx

140 lines
4.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { GROUP_MEMBER_MAX, GROUP_MEMBER_MIN } from "@rakazo/contracts";
import { botAvatarImageSrc } from "@rakazo/core";
import { Stack, useRouter } from "expo-router";
import { useEffect, useState } from "react";
import { Pressable, ScrollView, Text, TextInput } from "react-native";
import { BotAvatar } from "../components/bot-avatar";
import { type MobileBot, rpc } from "../lib/api";
export default function NewGroup() {
const router = useRouter();
const [bots, setBots] = useState<MobileBot[]>([]);
const [name, setName] = useState("");
const [selected, setSelected] = useState<string[]>([]);
const [error, setError] = useState<string | null>(null);
const [pending, setPending] = useState(false);
useEffect(() => {
void rpc<MobileBot[]>("bots/list")
.then((nextBots) => setBots(nextBots.filter((bot) => !bot.archivedAt)))
.catch(() => undefined);
}, []);
function toggle(botId: string) {
setSelected((current) => {
if (current.includes(botId)) return current.filter((id) => id !== botId);
if (current.length >= GROUP_MEMBER_MAX) return current;
return [...current, botId];
});
}
async function create() {
if (
!name.trim() ||
selected.length < GROUP_MEMBER_MIN ||
selected.length > GROUP_MEMBER_MAX ||
pending
)
return;
setPending(true);
setError(null);
try {
const group = await rpc<{ id: string; name: string }>("groups/create", {
name: name.trim(),
botIds: selected,
});
router.replace({
pathname: "/group-thread",
params: { groupId: group.id, name: group.name },
});
} catch (err) {
setError(err instanceof Error ? err.message : "Could not create group");
} finally {
setPending(false);
}
}
return (
<>
<Stack.Screen options={{ title: "New group" }} />
<ScrollView
style={{ flex: 1, backgroundColor: "#050506" }}
contentContainerStyle={{ padding: 24 }}
>
<Text style={{ color: "#85858A", fontSize: 14 }}>Name</Text>
<TextInput
value={name}
onChangeText={setName}
placeholder="Name this group"
placeholderTextColor="#6C6C70"
style={{
marginTop: 8,
backgroundColor: "#1A1A1D",
borderRadius: 11,
padding: 14,
color: "#ECECEE",
fontSize: 16,
}}
/>
<Text style={{ color: "#85858A", fontSize: 14, marginTop: 20 }}>
Members ({GROUP_MEMBER_MIN}{GROUP_MEMBER_MAX})
</Text>
{bots.map((bot) => {
const checked = selected.includes(bot.id);
return (
<Pressable
key={bot.id}
onPress={() => toggle(bot.id)}
style={{
flexDirection: "row",
alignItems: "center",
gap: 12,
paddingVertical: 12,
}}
>
<BotAvatar
color={bot.color}
identity={bot.id}
size={34}
status={bot.status}
shape={bot.avatarShape}
imageSrc={botAvatarImageSrc(bot)}
/>
<Text style={{ flex: 1, color: "#ECECEE", fontSize: 16 }}>{bot.name}</Text>
<Text style={{ color: "#6C6C70" }}>{checked ? "✓" : ""}</Text>
</Pressable>
);
})}
{error ? <Text style={{ color: "#FF6B6B", marginTop: 12 }}>{error}</Text> : null}
<Pressable
onPress={() => void create()}
disabled={
!name.trim() ||
selected.length < GROUP_MEMBER_MIN ||
selected.length > GROUP_MEMBER_MAX ||
pending
}
style={{
marginTop: 24,
backgroundColor: "#8B5CF6",
opacity:
!name.trim() ||
selected.length < GROUP_MEMBER_MIN ||
selected.length > GROUP_MEMBER_MAX ||
pending
? 0.5
: 1,
borderRadius: 11,
padding: 14,
alignItems: "center",
}}
>
<Text style={{ color: "#FFF", fontSize: 16, fontWeight: "600" }}>
{pending ? "Creating…" : "Create group"}
</Text>
</Pressable>
</ScrollView>
</>
);
}