import { useEffect, useState, type FormEvent } from "react"; import { classById } from "../app/characterCatalog"; import { selectedCharacter, useShellStore } from "../app/shellStore"; import { useOnlineGroupStore } from "../app/onlineGroupStore"; import { classCanFillPartyRole, partyRolesForClass, PARTY_ROLE_LABELS, type PartyRole, } from "../game/partyRoles"; export function OnlineGroupPanel() { const [username, setUsername] = useState(""); const [panelOpen, setPanelOpen] = useState(false); const session = useShellStore((state) => state.session); const character = useShellStore(selectedCharacter); const snapshot = useOnlineGroupStore((state) => state.snapshot); const busy = useOnlineGroupStore((state) => state.busy); const error = useOnlineGroupStore((state) => state.error); const fillWithAi = useOnlineGroupStore((state) => state.fillWithAi); const invite = useOnlineGroupStore((state) => state.invite); const respond = useOnlineGroupStore((state) => state.respond); const leave = useOnlineGroupStore((state) => state.leave); const remove = useOnlineGroupStore((state) => state.remove); const updateMember = useOnlineGroupStore((state) => state.updateMember); const clearActivity = useOnlineGroupStore((state) => state.clearActivity); const setFillWithAi = useOnlineGroupStore((state) => state.setFillWithAi); useEffect(() => { if (snapshot.invitations.length > 0) setPanelOpen(true); }, [snapshot.invitations.length]); if (!session || session.kind !== "account" || !session.accessToken) return null; const group = snapshot.group; const currentMember = group?.members.find((member) => member.accountId === session.ownerId) ?? null; const leader = group?.leaderAccountId === session.ownerId; const roles = character ? partyRolesForClass(character.classId) : []; const readyCount = group?.members.filter((member) => member.character && member.role).length ?? 0; const submitInvite = async (event: FormEvent) => { event.preventDefault(); if (!username.trim()) return; if (await invite(session, username.trim())) setUsername(""); }; const chooseRole = (role: PartyRole) => { if (character && classCanFillPartyRole(character.classId, role)) void updateMember(session, character, role); }; return (
setPanelOpen(event.currentTarget.open)}> Online group {group ? `${group.members.length} / 5 players · ${readyCount} ready` : "Optional co-op"}
{snapshot.invitations.length > 0 && (
Invitations {snapshot.invitations.map((invitation) => (
{invitation.inviterUsername}wants you in their group
))}
)} {group && (
Party roster{leader ? "You are the leader" : "Waiting for the leader"}
{group.members.map((member) => { const memberClass = member.character ? classById(member.character.classId as Parameters[0]) : null; return (
); })}
)} {group && character && currentMember && (
Your role
{roles.map((role) => ( ))}
)} {group?.activity && (
Launching {group.activity.type === "dungeon" ? "dungeon" : "Manastorm"} {group.activity.fillWithAi ? `Party filled to ${group.activity.partySize} with AI` : `${group.activity.partySize} players only`} {leader && }
)} {leader && group && !group.activity && ( )} {(!group || leader) && (
setUsername(event.target.value)} />
)} {group && ( )} {error &&

{error}

}
); }