import { useState } from 'react'; import { formatDistanceToNow } from '@/lib/dateFnsI18n'; import { Megaphone, Loader2, Send, CalendarClock, MapPin, XCircle, Info } from 'lucide-react'; import { Textarea } from '@/components/ui/textarea'; import { Button } from '@/components/ui/button'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAuthor } from '@/hooks/useAuthor'; import { useToast } from '@/hooks/useToast'; import { useEventUpdates, usePostEventUpdate } from '@/hooks/useEventUpdates'; import { useEventRsvps, eventCoord } from '@/hooks/useEventRsvps'; import { genUserName } from '@/lib/genUserName'; import { isAdminPubkey } from '@/lib/admins'; import { cn } from '@/lib/utils'; import { useTranslation } from '@/contexts/language'; import type { EventUpdate, EventUpdateKind, SkateEvent } from '@/lib/types'; const UPDATE_KIND_META: Record< EventUpdateKind, { icon: typeof Info; label: string; color: string; ring: string } > = { update: { icon: Info, label: 'placesUi.updates.kindUpdate', color: 'text-sky-400', ring: 'bg-sky-500/15' }, reschedule: { icon: CalendarClock, label: 'placesUi.updates.kindNewTime', color: 'text-amber-400', ring: 'bg-amber-500/15' }, relocation: { icon: MapPin, label: 'placesUi.updates.kindNewSpot', color: 'text-[#ff0040]', ring: 'bg-[#ff0040]/15' }, cancelled: { icon: XCircle, label: 'placesUi.updates.kindCancelled', color: 'text-red-400', ring: 'bg-red-500/15' }, }; function UpdateItem({ update }: { update: EventUpdate }) { const { t } = useTranslation(); const author = useAuthor(update.pubkey); const name = author.data?.metadata?.name ?? genUserName(update.pubkey); const pic = author.data?.metadata?.picture; const meta = UPDATE_KIND_META[update.updateKind]; const Icon = meta.icon; return (
{name.slice(0, 2).toUpperCase()}
{name} {update.authorRole === 'admin' && ( {t('placesUi.updates.admin')} )} {update.updateKind !== 'update' && ( {t(meta.label)} )} {formatDistanceToNow(new Date(update.createdAt * 1000), { addSuffix: true })}

{update.message}

); } /** * Host update composer (only for the event author) + a public feed of updates. * Posting p-tags every RSVPer so they get a notification. */ export function EventUpdates({ event }: { event: SkateEvent }) { const { t } = useTranslation(); const { user } = useCurrentUser(); const { toast } = useToast(); const coord = eventCoord(event); const { data: updates, isLoading } = useEventUpdates(coord); const { going, tentative } = useEventRsvps(coord); const { mutateAsync: post, isPending } = usePostEventUpdate(); const [message, setMessage] = useState(''); const [kind, setKind] = useState('update'); const isHost = user?.pubkey === event.pubkey; const isAdmin = isAdminPubkey(user?.pubkey); // Admins can moderate any event; the host runs their own. const canPost = isHost || isAdmin; const handlePost = async () => { const text = message.trim(); if (!text) return; try { await post({ event, message: text, updateKind: kind, notifyPubkeys: [...going, ...tentative], }); setMessage(''); setKind('update'); const reach = new Set([...going, ...tentative]).size; toast({ title: t('placesUi.updates.posted'), description: reach > 0 ? t('placesUi.updates.notifiedCount', { count: reach }) : t('placesUi.updates.notifiedEveryone'), }); } catch (err) { toast({ title: t('placesUi.updates.postError'), description: err instanceof Error ? err.message : t('placesUi.updates.tryAgain'), variant: 'destructive', }); } }; const hasUpdates = (updates?.length ?? 0) > 0; return (
{/* Composer — host or SkateSpots admin */} {canPost && (
{isHost ? t('placesUi.updates.postUpdate') : t('placesUi.updates.postAdminUpdate')}
{!isHost && (

{t('placesUi.updates.adminNote')}

)}
{(Object.keys(UPDATE_KIND_META) as EventUpdateKind[]).map((k) => { const m = UPDATE_KIND_META[k]; const Icon = m.icon; const active = kind === k; return ( ); })}