import React, { useState, useEffect, useRef } from 'react'; import { X, UserPlus, Users, Copy, Check, Shield, Trash2, Loader2, AlertCircle, Palette, CheckCircle, ExternalLink, } from 'lucide-react'; import { CalendarSpace, SpaceMember } from '../types/calendar'; import { atprotoService, CALENDAR_COLORS } from '../services/atproto'; import { useToast } from './Toast'; import { useModalFocus } from '../hooks/useModalFocus'; interface ShareSpaceModalProps { isOpen: boolean; onClose: () => void; space: CalendarSpace | null; onMembersUpdated: () => void; onSpaceUpdated?: (updatedSpace: CalendarSpace) => void; onOpenTools?: () => void; } export const ShareSpaceModal: React.FC = ({ isOpen, onClose, space, onMembersUpdated, onSpaceUpdated, onOpenTools, }) => { const toast = useToast(); const [members, setMembers] = useState([]); const [loadingMembers, setLoadingMembers] = useState(false); const [membersError, setMembersError] = useState(null); const [inviteIdentifier, setInviteIdentifier] = useState(''); const [inviting, setInviting] = useState(false); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); const [removingDid, setRemovingDid] = useState(null); const [spaceName, setSpaceName] = useState(''); const [spaceColor, setSpaceColor] = useState(''); const [spaceDescription, setSpaceDescription] = useState(''); const [savingSettings, setSavingSettings] = useState(false); const membersRequestId = useRef(0); const requestClose = () => { if (!inviting && !removingDid && !savingSettings) onClose(); }; const dialogRef = useModalFocus(isOpen, requestClose); const currentDid = atprotoService.getCurrentDid(); useEffect(() => { if (isOpen && space) { setMembers([]); loadMembers(); setSpaceName(space.name); setSpaceColor(space.color); setSpaceDescription(space.description || ''); } return () => { membersRequestId.current += 1; }; }, [isOpen, space]); const loadMembers = async (requestedSpace = space) => { if (!requestedSpace) return; const requestId = ++membersRequestId.current; setLoadingMembers(true); setMembersError(null); try { const list = await atprotoService.listSpaceMembers(requestedSpace.uri); if (requestId === membersRequestId.current && space?.uri === requestedSpace.uri) { setMembers(list); } } catch (err: any) { if (requestId === membersRequestId.current && space?.uri === requestedSpace.uri) { setMembersError(err.message || 'Failed to load space members.'); } } finally { if (requestId === membersRequestId.current) setLoadingMembers(false); } }; if (!isOpen || !space) return null; const handleCopyUri = async () => { try { await navigator.clipboard.writeText(space.uri); setCopied(true); toast.success('Space URI copied to clipboard'); setTimeout(() => setCopied(false), 2000); } catch { toast.error('Could not copy the calendar address. Select and copy it manually.'); } }; const handleInvite = async (e: React.FormEvent) => { e.preventDefault(); if (!inviteIdentifier.trim()) return; setInviting(true); setError(null); try { await atprotoService.addSpaceMember(space.uri, inviteIdentifier.trim()); toast.success(`Invited ${inviteIdentifier.trim()} to space`); setInviteIdentifier(''); await loadMembers(); onMembersUpdated(); } catch (err: any) { setError(err.message || 'Failed to invite user to space. Check handle/DID.'); } finally { setInviting(false); } }; const handleRemove = async (did: string) => { setRemovingDid(did); setError(null); try { await atprotoService.removeSpaceMember(space.uri, did); toast.success('Member removed'); await loadMembers(); onMembersUpdated(); } catch (err: any) { setError(err.message || 'Failed to remove member.'); } finally { setRemovingDid(null); } }; const handleSaveSettings = async () => { if (!spaceName.trim()) return; setSavingSettings(true); try { await atprotoService.updateSpaceMeta(space.uri, { name: spaceName.trim(), color: spaceColor, description: spaceDescription.trim() || undefined, }); toast.success('Space settings updated'); if (onSpaceUpdated) { onSpaceUpdated({ ...space, name: spaceName.trim(), color: spaceColor, description: spaceDescription.trim() || undefined, }); } } catch { toast.error('Failed to update space settings'); } finally { setSavingSettings(false); } }; return (
e.stopPropagation()} >

Calendar settings

Access control, members, and calendar settings

{space.uri} PDSls
{space.isOwner && (
setSpaceName(e.target.value)} className="flex-1 px-3 py-1.5 text-xs rounded-xl border border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white outline-none focus:ring-2 focus:ring-primary-500" />
{CALENDAR_COLORS.map((c) => (
{(spaceName !== space.name || spaceColor !== space.color || spaceDescription !== (space.description || '')) && ( )}