import React, { useState, useRef, useEffect } from 'react'; import { ImageCropModal } from '../../ui/ImageCropModal'; import { Avatar } from '../../ui/Avatar'; import { useSpaceStore } from '../../../stores/spaceStore'; import { useAuthStore } from '../../../stores/authStore'; import { useUIStore } from '../../../stores/uiStore'; import { useNavigate } from 'react-router-dom'; import { api } from '../../../api/client'; import { hasPermissionBit, PermissionBits } from '../../../utils/permissions'; interface OverviewPanelProps { spaceId: string; } export function OverviewPanel({ spaceId }: OverviewPanelProps) { const spaces = useSpaceStore((s) => s.spaces); const updateSpace = useSpaceStore((s) => s.updateSpace); const deleteSpace = useSpaceStore((s) => s.deleteSpace); const currentUser = useAuthStore((s) => s.user); const closeModal = useUIStore((s) => s.closeModal); const spacePermissions = useSpaceStore((s) => s.spacePermissions); const navigate = useNavigate(); const space = spaces.find((s) => s.id === spaceId); const isOwner = space?.ownerId === currentUser?.id; const myPerms = spacePermissions.get(spaceId); const canManageSpace = hasPermissionBit(myPerms, PermissionBits.MANAGE_SPACE); const [spaceName, setSpaceName] = useState(space?.name ?? ''); // null = no change, '' = remove icon, 'filename.png' = new icon uploaded const [iconFilename, setIconFilename] = useState(null); const [iconPreview, setIconPreview] = useState(null); const [uploadingIcon, setUploadingIcon] = useState(false); const [cropSrc, setCropSrc] = useState(null); const [saving, setSaving] = useState(false); const [saveError, setSaveError] = useState(''); const [saveSuccess, setSaveSuccess] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const fileInputRef = useRef(null); useEffect(() => { if (space) { setSpaceName(space.name); // Reset icon state when space data changes externally setIconFilename(null); if (iconPreview) { URL.revokeObjectURL(iconPreview); setIconPreview(null); } } }, [space?.name, space?.icon]); if (!space) return null; const hasNameChange = spaceName.trim() !== space.name; const hasIconChange = iconFilename !== null; const hasChanges = hasNameChange || hasIconChange; const currentIconUrl = space.icon ? api.uploads.url(space.icon) : null; const handleIconSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = () => setCropSrc(reader.result as string); reader.readAsDataURL(file); if (fileInputRef.current) fileInputRef.current.value = ''; }; const handleCropComplete = async (blob: Blob) => { if (iconPreview) URL.revokeObjectURL(iconPreview); const previewUrl = URL.createObjectURL(blob); setIconPreview(previewUrl); setCropSrc(null); const file = new File([blob], 'icon.png', { type: 'image/png' }); setUploadingIcon(true); try { const attachment = await api.uploads.upload(file); setIconFilename(attachment.filename); } catch { setSaveError('Failed to upload icon'); setIconPreview(null); URL.revokeObjectURL(previewUrl); } finally { setUploadingIcon(false); } }; const handleRemoveIcon = () => { if (iconPreview) URL.revokeObjectURL(iconPreview); setIconPreview(null); setIconFilename(''); // '' signals removal }; const handleSave = async () => { setSaving(true); setSaveError(''); setSaveSuccess(false); try { const updates: { name?: string; icon?: string } = {}; if (hasNameChange) updates.name = spaceName.trim(); if (hasIconChange) { // Empty string signals icon removal to the backend (sets to null) updates.icon = iconFilename === '' ? '' : iconFilename!; } await updateSpace(spaceId, updates); setIconFilename(null); if (iconPreview) { URL.revokeObjectURL(iconPreview); setIconPreview(null); } setSaveSuccess(true); setTimeout(() => setSaveSuccess(false), 2000); } catch (err) { setSaveError(err instanceof Error ? err.message : 'Failed to save'); } finally { setSaving(false); } }; const handleDiscard = () => { setSpaceName(space.name); setIconFilename(null); if (iconPreview) { URL.revokeObjectURL(iconPreview); setIconPreview(null); } setSaveError(''); }; const handleDelete = async () => { if (!confirmDelete) { setConfirmDelete(true); return; } try { await deleteSpace(spaceId); closeModal(); navigate('/channels/@me'); } catch (err) { setSaveError(err instanceof Error ? err.message : 'Failed to delete space'); } }; // Determine what icon to show: preview of pending upload, or current space icon const displayIconSrc = iconPreview ?? (iconFilename === '' ? null : currentIconUrl); const displayIconName = space.name; return ( <>
{/* Space Icon */}
{canManageSpace && (displayIconSrc || space.icon) && iconFilename !== '' && ( )}
{/* Space Name */}
setSpaceName(e.target.value)} className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary" disabled={!canManageSpace} />
{/* Save / Discard */} {saveError && (
{saveError}
)} {saveSuccess && (
Settings saved
)} {canManageSpace && hasChanges && (
)} {/* Danger Zone */} {isOwner && (

Danger Zone

)}
setCropSrc(null)} imageSrc={cropSrc ?? ''} onCropComplete={handleCropComplete} title="Crop Space Icon" cropShape="round" aspectRatio={1} /> ); }