diff --git a/src/hooks/useFeaturedOrganizations.ts b/src/hooks/useFeaturedOrganizations.ts index 543e036e..ff6f8168 100644 --- a/src/hooks/useFeaturedOrganizations.ts +++ b/src/hooks/useFeaturedOrganizations.ts @@ -7,31 +7,18 @@ import { parseCommunityEvent, type ParsedCommunity, } from '@/lib/communityUtils'; +import { dedupeAddressableLatest } from '@/lib/addressableEvents'; /** - * Hand-curated list of featured organization event IDs. + * Hand-curated list of featured organization authors. * - * These are kind 34550 (NIP-72 community definition) event IDs that the - * Agora team has selected to showcase on the Organize page until a - * data-driven featuring mechanism exists. - * - * NIP-72 communities are addressable events keyed by - * \`(pubkey, d-tag)\`, so the same community can have many revisions - * over time — pinning a specific event ID locks the featured card to - * the revision the curator approved. To promote a newer revision, - * update the ID below. - * - * Replace this constant with a configurable source (AppConfig field, - * NIP-51 list, etc.) when the featuring story matures. + * We query all organizations authored by these accounts in one relay request, + * then latest-wins dedupe addressable revisions client-side. */ -export const FEATURED_ORGANIZATION_EVENT_IDS = [ - 'd29b870d2a02f2c098380f89d435748cf8d0b89f807f1e5e673cc58e3a1a7891', - '007e55ccbf16bd21bcf0efe920eb9cc397bbfa5b97046a6fb9e76d4fc68a5353', - 'ee4cea2b458ff514fc1dc6b554da74520f4c551cfd0670768f6475de07f69ba3', - 'b0ea85386358346a4549f0ad0039e9ae00e7eebe188f08556f185a1230376739', - '790b08be1c8d90ea4738d146ce2b749b42e528bba5dd8c5704804ff52e69b715', - 'fa5159b095aa376cea652967889fd5340367ec99cec64a355cfe459a68a974fc', - '7ca2a21e9328711f7f0423cd1d9c6f3c7c23c4c7e85dd544a248f963d0d96728', +export const FEATURED_ORGANIZATION_AUTHORS = [ + '932614571afcbad4d17a191ee281e39eebbb41b93fac8fd87829622aeb112f4d', + 'be7358c4fe50148cccafc02ea205d80145e253889aa3958daafa8637047c840e', + '3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24', ] as const; export interface FeaturedOrganization { @@ -40,48 +27,37 @@ export interface FeaturedOrganization { } /** - * Fetch the hand-curated featured organizations by event ID. + * Fetch featured organizations by author. * - * Results are returned in the same order as - * {@link FEATURED_ORGANIZATION_EVENT_IDS} so the curator controls the - * shelf ordering. Events that can't be parsed as a valid community - * definition are dropped without failing the whole shelf. - * - * Single-letter \`ids\` filter is indexed by relays, so this is one - * cheap round-trip regardless of list length. + * One author-filtered query is more reliable than pinning individual event IDs + * because kind 34550 definitions are addressable and can be revised. */ export function useFeaturedOrganizations() { const { nostr } = useNostr(); return useQuery({ - queryKey: ['featured-organizations', FEATURED_ORGANIZATION_EVENT_IDS.join(',')], + queryKey: ['featured-organizations', FEATURED_ORGANIZATION_AUTHORS.join(',')], queryFn: async ({ signal }) => { const combinedSignal = AbortSignal.any([signal, AbortSignal.timeout(8000)]); const events = await nostr.query( [{ kinds: [COMMUNITY_DEFINITION_KIND], - ids: [...FEATURED_ORGANIZATION_EVENT_IDS], - limit: FEATURED_ORGANIZATION_EVENT_IDS.length, + authors: [...FEATURED_ORGANIZATION_AUTHORS], + limit: 60, }], { signal: combinedSignal }, ); - // Index by id so we can preserve the curator's ordering. - const byId = new Map(); - for (const event of events) { - byId.set(event.id, event); - } - const entries: FeaturedOrganization[] = []; - for (const id of FEATURED_ORGANIZATION_EVENT_IDS) { - const event = byId.get(id); - if (!event) continue; + for (const event of dedupeAddressableLatest(events)) { const community = parseCommunityEvent(event); if (!community) continue; entries.push({ community, event }); } + entries.sort((a, b) => b.event.created_at - a.event.created_at); + return entries; }, // 5 minutes — featured list is hand-curated, doesn't churn.