11import React , { useEffect , useRef , useState } from 'react' ;
2- import { FilterIcon } from 'components/common/icons/FilterIcon' ;
32import { SearchOverlay } from './searchOverlay/SearchOverlay/SearchOverlay' ;
43import { HeaderActionWrapper } from '@app/components/header/Header.styles' ;
54import { CategoryComponents } from '@app/components/header/components/HeaderSearch/HeaderSearch' ;
65import { Btn , InputSearch } from '../HeaderSearch/HeaderSearch.styles' ;
76import { useTranslation } from 'react-i18next' ;
87import { BasePopover } from '@app/components/common/BasePopover/BasePopover' ;
8+ import { NDKUserProfile , useNDK } from '@nostr-dev-kit/ndk-hooks' ;
9+ import usePaidSubscribers from '@app/hooks/usePaidSubscribers' ;
10+ import { convertNDKUserProfileToSubscriberProfile } from '@app/utils/utils' ;
11+ import { InvalidPubkey } from '../../Header.styles' ;
912
13+ import { SubscriberProfile } from '@app/hooks/usePaidSubscribers' ;
14+ import { SubscriberDetailModal } from '@app/components/relay-dashboard/paid-subscribers/SubscriberDetailModal' ;
1015interface SearchOverlayProps {
1116 query : string ;
1217 setQuery : ( query : string ) => void ;
@@ -23,7 +28,13 @@ export const SearchDropdown: React.FC<SearchOverlayProps> = ({
2328 setOverlayOpen,
2429} ) => {
2530 const [ isFilterOpen , setFilterOpen ] = useState ( false ) ;
26-
31+ const [ isSubscriberDetailModalOpen , setSubscriberDetailModalOpen ] = useState ( false ) ;
32+ const [ fetchingProfile , setFetchingProfile ] = useState ( false ) ;
33+ const [ fetchingFailed , setFetchingFailed ] = useState ( false ) ;
34+ const [ subscriberProfile , setSubscriberProfile ] = useState < SubscriberProfile | null > ( null ) ;
35+ const [ invalidPubkey , setInvalidPubkey ] = useState ( false ) ;
36+ const { subscribers } = usePaidSubscribers ( ) ;
37+ const ndkInstance = useNDK ( ) ;
2738 const { t } = useTranslation ( ) ;
2839
2940 useEffect ( ( ) => {
@@ -33,6 +44,74 @@ export const SearchDropdown: React.FC<SearchOverlayProps> = ({
3344 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3445 const ref = useRef < any > ( null ) ;
3546
47+ const fetchProfile = async ( pubkey : string ) : Promise < NDKUserProfile | null > => {
48+ if ( ! ndkInstance ) return null ;
49+
50+ try {
51+ setFetchingProfile ( true ) ;
52+ const profile = await ndkInstance . ndk ?. getUser ( { pubkey : pubkey } ) . fetchProfile ( ) ;
53+
54+ if ( profile ) {
55+ setFetchingProfile ( false ) ;
56+ setFetchingFailed ( false ) ;
57+ return profile ;
58+ } else {
59+ console . error ( 'Profile not found for pubkey:' , pubkey ) ;
60+ setFetchingProfile ( false ) ;
61+ setFetchingFailed ( true ) ;
62+ return null ;
63+ }
64+ } catch ( error ) {
65+ console . error ( 'Error fetching profile:' , error ) ;
66+ setFetchingProfile ( false ) ;
67+ setFetchingFailed ( true ) ;
68+ return null ;
69+ }
70+ } ;
71+
72+ const handleSearchProfile = async ( ) => {
73+ if ( ! query ) return ;
74+ //verify that it's a pubkey
75+ if ( / ^ [ a - f A - F 0 - 9 ] { 64 } $ / . test ( query ) ) {
76+ setSubscriberDetailModalOpen ( true ) ;
77+
78+ //See if the pubkey exists in the subscribers
79+ const pubkey = query ;
80+ const subscriber = subscribers . find ( ( sub ) => sub . pubkey === query ) ;
81+ // If it exists, open the modal with the subscriber details. If name,picture, or about are not set, fetch profile.
82+ //if It doesnt exist, fetch the profile from NDK
83+ //once fetched, convert it to SubscriberProfile and open the modal
84+ if ( subscriber ) {
85+ if ( ! subscriber . name || ! subscriber . picture || ! subscriber . about ) {
86+ const profile = await fetchProfile ( pubkey ) ;
87+ if ( profile ) {
88+ const subscriberProfile : SubscriberProfile = convertNDKUserProfileToSubscriberProfile ( pubkey , profile ) ;
89+ // Open the modal with the fetched subscriber profile
90+ setSubscriberProfile ( subscriberProfile ) ;
91+ }
92+ }
93+ } else {
94+ const profile = await fetchProfile ( pubkey ) ;
95+ if ( profile ) {
96+ const subscriberProfile : SubscriberProfile = convertNDKUserProfileToSubscriberProfile ( pubkey , profile ) ;
97+ // Open the modal with the fetched subscriber profile
98+ setSubscriberProfile ( subscriberProfile ) ;
99+ }
100+ }
101+ } else {
102+ setInvalidPubkey ( true ) ;
103+ }
104+ } ;
105+ const onCloseSubscriberDetailModal = ( ) => {
106+ setSubscriberDetailModalOpen ( false ) ;
107+ setSubscriberProfile ( null ) ;
108+ } ;
109+
110+ useEffect ( ( ) => {
111+ if ( query . length === 0 ) {
112+ setInvalidPubkey ( false ) ;
113+ }
114+ } , [ query ] ) ;
36115 return (
37116 < >
38117 < BasePopover
@@ -43,25 +122,43 @@ export const SearchDropdown: React.FC<SearchOverlayProps> = ({
43122 getPopupContainer = { ( ) => ref . current }
44123 >
45124 < HeaderActionWrapper >
125+ { invalidPubkey && (
126+ < InvalidPubkey >
127+ { "Invalid pubkey." }
128+ </ InvalidPubkey >
129+ ) }
46130 < InputSearch
47131 width = "100%"
48132 value = { query }
49- placeholder = { t ( 'header.search' ) }
133+ placeholder = { t ( 'header.search' ) + ' hex pubkey' }
50134 filter = {
51135 < Btn
52136 size = "small"
53137 type = { isFilterOpen ? 'ghost' : 'text' }
54138 aria-label = "Filter"
55- icon = { < FilterIcon /> }
56139 onClick = { ( ) => setFilterOpen ( ! isFilterOpen ) }
57140 />
58141 }
59142 onChange = { ( event ) => setQuery ( event . target . value ) }
60143 enterButton = { null }
61144 addonAfter = { null }
145+ onKeyDown = { ( event ) => {
146+ if ( event . key === 'Enter' ) {
147+ handleSearchProfile ( ) ;
148+ }
149+ } }
62150 />
63151 < div ref = { ref } />
64152 </ HeaderActionWrapper >
153+ { isSubscriberDetailModalOpen && (
154+ < SubscriberDetailModal
155+ loading = { fetchingProfile }
156+ fetchFailed = { fetchingFailed }
157+ isVisible = { isSubscriberDetailModalOpen }
158+ subscriber = { subscriberProfile }
159+ onClose = { onCloseSubscriberDetailModal }
160+ />
161+ ) }
65162 </ BasePopover >
66163 </ >
67164 ) ;
0 commit comments