You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
115 lines
4.2 KiB
115 lines
4.2 KiB
import { ExtendedKind } from '@/constants' |
|
import { getReplaceableCoordinateFromEvent } from '@/lib/event' |
|
import { isCalendarEventKind } from '@/lib/calendar-event' |
|
import client from '@/services/client.service' |
|
import { queryService } from '@/services/client.service' |
|
import { useNostr } from '@/providers/NostrProvider' |
|
import { Event } from 'nostr-tools' |
|
import { useEffect, useState } from 'react' |
|
import { normalizeAnyRelayUrl } from '@/lib/url' |
|
import { FAST_READ_RELAY_URLS } from '@/constants' |
|
import { userReadRelaysWithHttp } from '@/lib/favorites-feed-relays' |
|
import { tagNameEquals } from '@/lib/tag' |
|
|
|
function getRsvpStatus(rsvp: Event): 'accepted' | 'tentative' | 'declined' | undefined { |
|
const status = rsvp.tags.find(tagNameEquals('status'))?.[1] |
|
if (status === 'accepted' || status === 'tentative' || status === 'declined') return status |
|
return undefined |
|
} |
|
|
|
function mergeRsvp(prev: Event[], evt: Event): Event[] { |
|
const next = prev.filter((e) => e.id !== evt.id) |
|
const samePubkey = next.find((e) => e.pubkey === evt.pubkey) |
|
if (samePubkey && samePubkey.created_at >= evt.created_at) return next |
|
const withoutSamePubkey = samePubkey ? next.filter((e) => e.pubkey !== evt.pubkey) : next |
|
return [...withoutSamePubkey, evt].sort((a, b) => b.created_at - a.created_at) |
|
} |
|
|
|
export function useFetchCalendarRsvps(calendarEvent: Event | undefined) { |
|
const { relayList } = useNostr() |
|
const [rsvps, setRsvps] = useState<Event[]>([]) |
|
const [isFetching, setIsFetching] = useState(false) |
|
|
|
useEffect(() => { |
|
if (!calendarEvent || !isCalendarEventKind(calendarEvent.kind)) { |
|
setRsvps([]) |
|
return |
|
} |
|
|
|
let cancelled = false |
|
setIsFetching(true) |
|
|
|
const coordinate = getReplaceableCoordinateFromEvent(calendarEvent) |
|
const userRead = userReadRelaysWithHttp(relayList) |
|
const baseUrls = new Set<string>([ |
|
...FAST_READ_RELAY_URLS.map((url) => normalizeAnyRelayUrl(url) || url), |
|
...userRead.map((url) => normalizeAnyRelayUrl(url) || url) |
|
].filter(Boolean) as string[]) |
|
|
|
// Include organizer's relays so RSVPs are found when viewing an attendee's profile (RSVPs are often on organizer's outbox/inbox) |
|
const organizerPubkey = calendarEvent.pubkey |
|
client |
|
.fetchRelayList(organizerPubkey) |
|
.then((organizerRelays) => { |
|
if (cancelled) return |
|
;[ |
|
...(organizerRelays?.httpRead ?? []), |
|
...(organizerRelays?.read ?? []), |
|
...(organizerRelays?.httpWrite ?? []), |
|
...(organizerRelays?.write ?? []) |
|
].forEach((url) => { |
|
const u = normalizeAnyRelayUrl(url) |
|
if (u) baseUrls.add(u) |
|
}) |
|
return Array.from(baseUrls) |
|
}) |
|
.catch(() => Array.from(baseUrls)) |
|
.then((relayUrls: string[] | undefined) => { |
|
if (cancelled) return |
|
const urls = relayUrls?.length ? relayUrls : Array.from(baseUrls) |
|
return queryService.fetchEvents( |
|
urls, |
|
{ |
|
kinds: [ExtendedKind.CALENDAR_EVENT_RSVP], |
|
'#a': [coordinate], |
|
limit: 200 |
|
}, |
|
{ firstRelayResultGraceMs: false } |
|
) |
|
}) |
|
.then((events) => { |
|
if (cancelled) return |
|
setRsvps(events ?? []) |
|
}) |
|
.finally(() => { |
|
if (!cancelled) setIsFetching(false) |
|
}) |
|
|
|
return () => { |
|
cancelled = true |
|
} |
|
}, [calendarEvent?.id, calendarEvent?.kind, calendarEvent?.pubkey, relayList]) |
|
|
|
// When we publish an RSVP, NostrProvider calls client.emitNewEvent(event). Merge it into rsvps so the UI updates immediately. |
|
useEffect(() => { |
|
if (!calendarEvent || !isCalendarEventKind(calendarEvent.kind)) return |
|
|
|
const coordinate = getReplaceableCoordinateFromEvent(calendarEvent) |
|
const handler = (e: CustomEvent<Event>) => { |
|
const evt = e.detail |
|
if (evt.kind !== ExtendedKind.CALENDAR_EVENT_RSVP) return |
|
const aTag = evt.tags.find(tagNameEquals('a')) |
|
if (aTag?.[1] !== coordinate) return |
|
setRsvps((prev) => mergeRsvp(prev, evt)) |
|
} |
|
|
|
client.addEventListener('newEvent', handler as EventListener) |
|
return () => client.removeEventListener('newEvent', handler as EventListener) |
|
}, [calendarEvent?.id, calendarEvent?.kind]) |
|
|
|
return { |
|
rsvps, |
|
isFetching, |
|
getRsvpStatus |
|
} |
|
}
|
|
|