From c5a2cedad335540d3a8c3762665b19bd0d162787 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Wed, 2 Jul 2025 16:37:28 -0500 Subject: [PATCH] refactor: centralize event ID extraction logic in getEventId --- src/components/feeds/ChannelEmptyState.js | 2 +- src/components/feeds/NostrFeed.js | 7 + src/hooks/useIsAdmin.js | 4 +- src/utils/nostr.js | 270 +++++++++++----------- 4 files changed, 142 insertions(+), 141 deletions(-) diff --git a/src/components/feeds/ChannelEmptyState.js b/src/components/feeds/ChannelEmptyState.js index 53ac5d3..4882c54 100644 --- a/src/components/feeds/ChannelEmptyState.js +++ b/src/components/feeds/ChannelEmptyState.js @@ -5,7 +5,7 @@ * system is unavailable, helping users understand the current state. * * @param {Object} props - Component props - * @param {string} props.mode - Current mode ('loading', 'error', 'fallback', 'no-channel') + * @param {string} props.mode - Current mode ('loading', 'error', 'no-channel') * @param {string} props.error - Error message if applicable * @param {Function} props.onRetry - Optional retry function * @param {boolean} props.canCreateChannel - Whether user can create channels diff --git a/src/components/feeds/NostrFeed.js b/src/components/feeds/NostrFeed.js index 4023360..a07dc5a 100644 --- a/src/components/feeds/NostrFeed.js +++ b/src/components/feeds/NostrFeed.js @@ -11,6 +11,7 @@ import { useCommunityNotes } from '@/hooks/nostr/useCommunityNotes'; import { useNip28Channel } from '@/hooks/nostr/useNip28Channel'; import CommunityMessage from '@/components/feeds/messages/CommunityMessage'; import ChannelEmptyState from './ChannelEmptyState'; +import { useToast } from '@/hooks/useToast'; const NostrFeed = ({ searchQuery }) => { const { communityNotes, isLoading, error, hasChannel } = useCommunityNotes(); @@ -26,6 +27,7 @@ const NostrFeed = ({ searchQuery }) => { const [authorData, setAuthorData] = useState({}); const windowWidth = useWindowWidth(); + const { showToast } = useToast(); /** * Handle admin channel creation @@ -36,6 +38,11 @@ const NostrFeed = ({ searchQuery }) => { // Channel creation will trigger a refresh automatically } catch (error) { console.error('Failed to create channel:', error); + showToast( + 'error', + 'Channel Creation Failed', + error?.message || 'An unexpected error occurred while creating the channel.' + ); } }; diff --git a/src/hooks/useIsAdmin.js b/src/hooks/useIsAdmin.js index 5ab1b94..286f4d7 100644 --- a/src/hooks/useIsAdmin.js +++ b/src/hooks/useIsAdmin.js @@ -29,7 +29,9 @@ export function useIsAdmin() { try { const user = await ndk.signer.user(); if (user?.pubkey) { - isNostrAdmin = appConfig.nip28.adminPubkeys.includes(user.pubkey); + const adminPubkeys = appConfig.nip28?.adminPubkeys; + if (Array.isArray(adminPubkeys)) + isNostrAdmin = adminPubkeys.includes(user.pubkey); } } catch (err) { console.warn('Could not get Nostr user for admin check:', err); diff --git a/src/utils/nostr.js b/src/utils/nostr.js index 7016b7a..e062644 100644 --- a/src/utils/nostr.js +++ b/src/utils/nostr.js @@ -289,7 +289,136 @@ export const parseChannelEvent = event => { console.log('Parsing channel event:', event); - // Try to get ID from the actual event data + // Use centralized event ID extraction logic + const eventId = getEventId(event); + + const eventData = { + id: eventId, + pubkey: event.pubkey || '', + content: event.content || '', + kind: event.kind || 40, + created_at: event.created_at || 0, + type: 'channel', + metadata: null, + tags: event.tags || [] + }; + + // Parse channel metadata from content + try { + if (eventData.content) { + eventData.metadata = JSON.parse(eventData.content); + } + } catch (err) { + console.warn('Error parsing channel metadata:', err); + eventData.metadata = {}; + } + + // Extract additional data from tags + event.tags.forEach(tag => { + switch (tag[0]) { + case 't': + if (!eventData.topics) eventData.topics = []; + eventData.topics.push(tag[1]); + break; + case 'r': + if (!eventData.relays) eventData.relays = []; + eventData.relays.push(tag[1]); + break; + default: + break; + } + }); + + return eventData; +}; + +/** + * Parse NIP-28 Channel Metadata Event (kind 41) + * + * @param {Object} event - The NDK event object + * @returns {Object} - Parsed channel metadata + */ +export const parseChannelMetadataEvent = event => { + const eventData = { + id: getEventId(event), + pubkey: event.pubkey || '', + content: event.content || '', + kind: event.kind || 41, + created_at: event.created_at || 0, + type: 'channel-metadata', + channelId: null, + metadata: null, + tags: event.tags || [] + }; + + // Find channel reference + event.tags.forEach(tag => { + if (tag[0] === 'e' && tag[3] === 'root') { + eventData.channelId = tag[1]; + } + }); + + // Parse metadata from content + try { + if (eventData.content) { + eventData.metadata = JSON.parse(eventData.content); + } + } catch (err) { + console.warn('Error parsing channel metadata:', err); + eventData.metadata = {}; + } + + return eventData; +}; + +/** + * Parse NIP-28 Channel Message Event (kind 42) + * + * @param {Object} event - The NDK event object + * @returns {Object} - Parsed channel message + */ +export const parseChannelMessageEvent = event => { + const eventData = { + id: getEventId(event), + pubkey: event.pubkey || '', + content: event.content || '', + kind: event.kind || 42, + created_at: event.created_at || 0, + type: 'channel-message', + channelId: null, + replyTo: null, + mentions: [], + tags: event.tags || [] + }; + + // Parse NIP-10 threading and channel references + event.tags.forEach(tag => { + switch (tag[0]) { + case 'e': + if (tag[3] === 'root') { + eventData.channelId = tag[1]; + } else if (tag[3] === 'reply') { + eventData.replyTo = tag[1]; + } + break; + case 'p': + eventData.mentions.push(tag[1]); + break; + default: + break; + } + }); + + return eventData; +}; + +/** + * Generate a proper event ID from NDK event with comprehensive fallback logic + * + * @param {Object} event - The NDK event object + * @returns {string} - The event ID + */ +export const getEventId = event => { let eventId = ''; // First try the direct properties @@ -373,143 +502,6 @@ export const parseChannelEvent = event => { eventId = `temp_${event.pubkey.slice(0, 8)}_${event.created_at}`; } } - - const eventData = { - id: eventId, - pubkey: event.pubkey || '', - content: event.content || '', - kind: event.kind || 40, - created_at: event.created_at || 0, - type: 'channel', - metadata: null, - tags: event.tags || [] - }; - - // Parse channel metadata from content - try { - if (eventData.content) { - eventData.metadata = JSON.parse(eventData.content); - } - } catch (err) { - console.warn('Error parsing channel metadata:', err); - eventData.metadata = {}; - } - - // Extract additional data from tags - event.tags.forEach(tag => { - switch (tag[0]) { - case 't': - if (!eventData.topics) eventData.topics = []; - eventData.topics.push(tag[1]); - break; - case 'r': - if (!eventData.relays) eventData.relays = []; - eventData.relays.push(tag[1]); - break; - default: - break; - } - }); - - return eventData; -}; - -/** - * Parse NIP-28 Channel Metadata Event (kind 41) - * - * @param {Object} event - The NDK event object - * @returns {Object} - Parsed channel metadata - */ -export const parseChannelMetadataEvent = event => { - const eventData = { - id: event.id || event.eventId || event.tagId() || '', - pubkey: event.pubkey || '', - content: event.content || '', - kind: event.kind || 41, - created_at: event.created_at || 0, - type: 'channel-metadata', - channelId: null, - metadata: null, - tags: event.tags || [] - }; - - // Find channel reference - event.tags.forEach(tag => { - if (tag[0] === 'e' && tag[3] === 'root') { - eventData.channelId = tag[1]; - } - }); - - // Parse metadata from content - try { - if (eventData.content) { - eventData.metadata = JSON.parse(eventData.content); - } - } catch (err) { - console.warn('Error parsing channel metadata:', err); - eventData.metadata = {}; - } - - return eventData; -}; - -/** - * Parse NIP-28 Channel Message Event (kind 42) - * - * @param {Object} event - The NDK event object - * @returns {Object} - Parsed channel message - */ -export const parseChannelMessageEvent = event => { - const eventData = { - id: event.id || event.eventId || event.tagId() || '', - pubkey: event.pubkey || '', - content: event.content || '', - kind: event.kind || 42, - created_at: event.created_at || 0, - type: 'channel-message', - channelId: null, - replyTo: null, - mentions: [], - tags: event.tags || [] - }; - - // Parse NIP-10 threading and channel references - event.tags.forEach(tag => { - switch (tag[0]) { - case 'e': - if (tag[3] === 'root') { - eventData.channelId = tag[1]; - } else if (tag[3] === 'reply') { - eventData.replyTo = tag[1]; - } - break; - case 'p': - eventData.mentions.push(tag[1]); - break; - default: - break; - } - }); - - return eventData; -}; - -/** - * Generate a proper event ID from NDK event - * - * @param {Object} event - The NDK event object - * @returns {string} - The event ID - */ -export const getEventId = event => { - // Try different methods to get the ID - if (event.id && event.id !== '') return event.id; - if (event.eventId && event.eventId !== '') return event.eventId; - if (typeof event.tagId === 'function') { - const tagId = event.tagId(); - if (tagId && tagId !== '') return tagId; - } - // If no ID available, generate one or return null - console.warn('No event ID found for event:', event); - return null; + return eventId || null; };