import { useState, useEffect, useRef } from 'react'; import { api } from '../../../api/client'; import { ConfirmDialog } from '../../ui/ConfirmDialog'; import type { StickerPack, Sticker } from '@backspace/shared'; interface StickersPanelProps { spaceId: string; } export function StickersPanel({ spaceId }: StickersPanelProps) { const [packs, setPacks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); // Create pack state const [newPackName, setNewPackName] = useState(''); const [newPackDesc, setNewPackDesc] = useState(''); const [creating, setCreating] = useState(false); // Upload sticker state const [uploadPackId, setUploadPackId] = useState(null); const [stickerName, setStickerName] = useState(''); const [stickerTags, setStickerTags] = useState(''); const [stickerFile, setStickerFile] = useState(null); const [uploading, setUploading] = useState(false); const fileInputRef = useRef(null); // Delete confirmation const [deleteTarget, setDeleteTarget] = useState<{ type: 'pack' | 'sticker'; id: string; name: string } | null>(null); const [deleting, setDeleting] = useState(false); const fetchPacks = async () => { try { const { packs: data } = await api.stickers.getPacks(spaceId); setPacks(data); setLoading(false); } catch { setError('Failed to load sticker packs'); setLoading(false); } }; useEffect(() => { fetchPacks(); }, [spaceId]); const handleCreatePack = async () => { if (!newPackName.trim()) return; setCreating(true); setError(''); try { const pack = await api.stickers.createPack(spaceId, { name: newPackName.trim(), description: newPackDesc.trim() || undefined, }); setPacks((prev) => [...prev, { ...pack, stickers: [] }]); setNewPackName(''); setNewPackDesc(''); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create pack'); } finally { setCreating(false); } }; const handleUploadSticker = async () => { if (!uploadPackId || !stickerFile || !stickerName.trim()) return; setUploading(true); setError(''); try { const sticker = await api.stickers.uploadSticker( spaceId, uploadPackId, stickerFile, stickerName.trim(), stickerTags.trim(), ); setPacks((prev) => prev.map((p) => p.id === uploadPackId ? { ...p, stickers: [...p.stickers, sticker] } : p, ), ); setStickerName(''); setStickerTags(''); setStickerFile(null); setUploadPackId(null); if (fileInputRef.current) fileInputRef.current.value = ''; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to upload sticker'); } finally { setUploading(false); } }; const handleDelete = async () => { if (!deleteTarget) return; setDeleting(true); setError(''); try { if (deleteTarget.type === 'pack') { await api.stickers.deletePack(spaceId, deleteTarget.id); setPacks((prev) => prev.filter((p) => p.id !== deleteTarget.id)); } else { await api.stickers.deleteSticker(deleteTarget.id); setPacks((prev) => prev.map((p) => ({ ...p, stickers: p.stickers.filter((s) => s.id !== deleteTarget.id), })), ); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to delete'); } finally { setDeleting(false); setDeleteTarget(null); } }; const getStickerUrl = (sticker: Sticker) => { if (sticker.filename.startsWith('http') || sticker.filename.startsWith('/')) return sticker.filename; return `/api/uploads/${sticker.filename}`; }; if (loading) { return
Loading sticker packs...
; } return (
Manage sticker packs for this space. Members can use these stickers in messages.
{error && (
{error}
)} {/* Create Pack */}
Create Sticker Pack
setNewPackName(e.target.value.slice(0, 32))} placeholder="Pack name" className="input-standard w-full" /> setNewPackDesc(e.target.value.slice(0, 100))} placeholder="Description (optional)" className="input-standard w-full" />
{/* Existing Packs */} {packs.length === 0 ? (
No sticker packs yet.
) : (
{packs.map((pack) => (
{pack.name}
{pack.description && (
{pack.description}
)}
{/* Upload form for this pack */} {uploadPackId === pack.id && (
setStickerName(e.target.value.slice(0, 32))} placeholder="Sticker name" className="input-standard flex-1" /> setStickerTags(e.target.value.slice(0, 100))} placeholder="Tags (optional)" className="input-standard flex-1" />
setStickerFile(e.target.files?.[0] ?? null)} className="text-sm text-txt-secondary file:mr-2 file:py-1 file:px-2 file:rounded file:border-0 file:text-xs file:bg-interactive-hover file:text-txt-primary hover:file:bg-interactive-active" />
PNG, WebP, or GIF. Max 512x512px, 500KB.
)} {/* Sticker grid */} {pack.stickers.length > 0 && (
{pack.stickers.map((sticker) => (
{sticker.name}
{sticker.name}
))}
)} {pack.stickers.length === 0 && (
No stickers in this pack yet.
)}
))}
)} {/* Delete confirmation */} setDeleteTarget(null)} title={`Delete ${deleteTarget?.type === 'pack' ? 'Sticker Pack' : 'Sticker'}`} description={`Are you sure you want to delete "${deleteTarget?.name}"?${ deleteTarget?.type === 'pack' ? ' All stickers in this pack will be deleted.' : '' } Existing messages will show "Sticker unavailable".`} confirmLabel={deleting ? 'Deleting...' : 'Delete'} onConfirm={handleDelete} variant="danger" loading={deleting} />
); }