import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Avatar } from '../ui/Avatar'; import { getApiForOrigin } from '../../stores/spaceStore'; import { useSpaceStore } from '../../stores/spaceStore'; import type { SpaceInviteSystemPayload } from '@backspace/shared'; type LiveState = | { kind: 'loading' } | { kind: 'confirmed'; memberCount: number } | { kind: 'revoked' }; interface Props { payload: SpaceInviteSystemPayload; senderName: string; } export function SpaceInviteCard({ payload, senderName }: Props) { const navigate = useNavigate(); const joinByCode = useSpaceStore(s => s.joinByCode); const [live, setLive] = useState({ kind: 'loading' }); const [joining, setJoining] = useState(false); const [joinError, setJoinError] = useState(null); // Live-preview overlay: snapshot is authoritative until live confirms or // revokes it. A mismatched spaceId is treated as revoked because the invite // code now points to a different space than was captured at send time. useEffect(() => { let cancelled = false; const client = getApiForOrigin(payload.spaceInstanceOrigin); client.spaces.invitePreview(payload.inviteCode).then( (preview) => { if (cancelled) return; if (preview.spaceId !== payload.spaceId) { setLive({ kind: 'revoked' }); } else { setLive({ kind: 'confirmed', memberCount: preview.memberCount }); } }, () => { if (!cancelled) setLive({ kind: 'revoked' }); }, ); return () => { cancelled = true; }; }, [payload.inviteCode, payload.spaceId, payload.spaceInstanceOrigin]); const memberCount = live.kind === 'confirmed' ? live.memberCount : payload.snapshot.memberCount; const isRevoked = live.kind === 'revoked'; const onJoin = async () => { if (joining || isRevoked) return; setJoining(true); setJoinError(null); try { // Three-way federation invariant: target the space's home origin, not // the DM transport origin nor window.location.origin. Empty string maps // to undefined so joinByCode follows its local-instance branch. const space = await joinByCode(payload.inviteCode, payload.spaceInstanceOrigin || undefined); navigate(`/spaces/${space.id}`); } catch (err) { setJoinError((err as Error)?.message ?? 'Failed to join'); setJoining(false); } }; return (
{senderName} sent an invite
{payload.snapshot.spaceName}
{memberCount} {memberCount === 1 ? 'member' : 'members'} {payload.snapshot.instanceName ? ` · ${payload.snapshot.instanceName}` : ''} {live.kind === 'loading' && ( )}
{isRevoked ? ( Invite no longer valid ) : ( )}
{joinError && (
{joinError}
)}
); }