Skip to content

Commit 1685b8c

Browse files
Merge pull request #57 from HORNET-Storage/feature/ndk-integration-fix-cleanup
Feature/ndk integration fix cleanup
2 parents 79d5cd7 + 9b2fbc8 commit 1685b8c

30 files changed

Lines changed: 1116 additions & 858 deletions

File tree

.env.development

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ REACT_APP_ASSETS_BUCKET=http://localhost
44
REACT_APP_DEMO_MODE=false
55
REACT_APP_BASENAME=
66

7+
# Nostr relay configuration for profile fetching
8+
REACT_APP_OWN_RELAY_URL=ws://localhost:7000
9+
REACT_APP_NOSTR_RELAY_URLS=wss://relay.damus.io,wss://relay.nostr.band,wss://relay.snort.social,wss://vault.iris.to
10+
711
# More info https://create-react-app.dev/docs/advanced-configuration
812
ESLINT_NO_DEV_ERRORS=true
913
TSC_COMPILE_ON_ERROR=true

craco.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ module.exports = {
1111
webpackConfig.plugins = webpackConfig.plugins.filter(plugin =>
1212
!(plugin.constructor && plugin.constructor.name === 'PrettierPlugin')
1313
);
14+
webpackConfig.module.rules.push({
15+
test: /\.mjs$/,
16+
include: /node_modules/,
17+
type: 'javascript/auto',
18+
});
1419
return webpackConfig;
1520
},
1621
plugins: [

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
"dependencies": {
1414
"@ant-design/icons": "^4.6.2",
1515
"@babel/core": "^7.24.7",
16+
"@cashu/cashu-ts": "^2.5.2",
1617
"@craco/craco": "^6.1.2",
1718
"@lit-labs/react": "^1.0.2",
19+
"@nostr-dev-kit/ndk": "^2.14.32",
20+
"@nostr-dev-kit/ndk-hooks": "^1.2.3",
1821
"@react-google-maps/api": "^2.18.1",
1922
"@reduxjs/toolkit": "^1.7.1",
2023
"@splidejs/react-splide": "^0.7.12",

src/App.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useEffect } from 'react';
22
import { ConfigProvider } from 'antd';
33
import { HelmetProvider } from 'react-helmet-async';
44
import deDe from 'antd/lib/locale/de_DE';
@@ -13,10 +13,43 @@ import { usePWA } from './hooks/usePWA';
1313
import { useThemeWatcher } from './hooks/useThemeWatcher';
1414
import { useAppSelector } from './hooks/reduxHooks';
1515
import { themeObject } from './styles/themes/themeVariables';
16+
import NDK, { NDKEvent, NDKNip07Signer, NDKRelayAuthPolicies } from '@nostr-dev-kit/ndk';
17+
import { useNDKInit } from '@nostr-dev-kit/ndk-hooks';
18+
import config from './config/config';
19+
20+
// Configure NDK with user's relay URLs from environment variables
21+
const getRelayUrls = () => {
22+
const relayUrls = [...config.nostrRelayUrls];
23+
24+
// Add user's own relay URL as the first priority if provided
25+
if (config.ownRelayUrl) {
26+
relayUrls.unshift(config.ownRelayUrl);
27+
}
28+
29+
return relayUrls;
30+
};
31+
32+
const ndk = new NDK({
33+
explicitRelayUrls: getRelayUrls(),
34+
signer: new NDKNip07Signer(),
35+
});
36+
37+
// Set up NIP-42 authentication policy following the example
38+
ndk.relayAuthDefaultPolicy = NDKRelayAuthPolicies.signIn({ ndk });
39+
40+
ndk
41+
.connect()
42+
.then(() => console.log('NDK connected with relay URLs and NIP-42 auth policy:', getRelayUrls()))
43+
.catch((error) => console.error('NDK connection error:', error));
1644

1745
const App: React.FC = () => {
1846
const { language } = useLanguage();
1947
const theme = useAppSelector((state) => state.theme.theme);
48+
const initializeNDK = useNDKInit();
49+
50+
useEffect(() => {
51+
initializeNDK(ndk);
52+
}, [initializeNDK]);
2053

2154
usePWA();
2255

src/api/activity.api.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ export const getUserActivities = (handleLogout: () => void): Promise<WalletTrans
4040
return [];
4141
}
4242
// Assuming your backend response matches the WalletTransaction interface
43-
// eslint-disable-next-line
44-
console.log("User Activity Data: ", data)
4543
return data.map((item: any) => ({
4644
id: item.ID,
4745
witness_tx_id: item.Address,

src/components/auth/LoginForm/LoginForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ export const LoginForm: React.FC = () => {
7070
console.log('Signed event:', signedEvent);
7171

7272
const response = await verifyChallenge({
73-
challenge: signedEvent.content,
73+
challenge: event.content,
7474
signature: signedEvent.sig,
75-
messageHash: signedEvent.id,
75+
messageHash: event.id,
7676
event: signedEvent,
7777
});
7878

src/components/header/Header.styles.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { BaseCol } from '../common/BaseCol/BaseCol';
77

88
export const HeaderActionWrapper = styled.div`
99
cursor: pointer;
10+
position: relative;
1011
1112
& > .ant-btn > span[role='img'],
1213
.ant-badge {
@@ -22,6 +23,18 @@ export const HeaderActionWrapper = styled.div`
2223
}
2324
`;
2425

26+
export const InvalidPubkey = styled.div`
27+
position: absolute;
28+
top: 3rem;
29+
left: 2rem;
30+
width: 100%;
31+
color: var(--error-color);
32+
height: 100%;
33+
display: flex;
34+
align-items: center;
35+
font-weight: bold;
36+
`;
37+
2538
export const DropdownCollapse = styled(BaseCollapse)`
2639
& > .ant-collapse-item > .ant-collapse-header {
2740
font-weight: 600;

src/components/header/components/HeaderSearch/HeaderSearch.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const HeaderSearch: React.FC = () => {
3737

3838
useEffect(() => {
3939
setModalOpen(false);
40-
setOverlayOpen(false);
40+
//setOverlayOpen(false);
4141
}, [pathname]);
4242

4343
return (
@@ -59,7 +59,7 @@ export const HeaderSearch: React.FC = () => {
5959
query={query}
6060
setQuery={setQuery}
6161
data={sortedResults}
62-
isOverlayOpen={isOverlayOpen}
62+
isOverlayOpen={false}
6363
setOverlayOpen={setOverlayOpen}
6464
/>
6565
</S.SearchModal>
@@ -71,7 +71,7 @@ export const HeaderSearch: React.FC = () => {
7171
query={query}
7272
setQuery={setQuery}
7373
data={sortedResults}
74-
isOverlayOpen={isOverlayOpen}
74+
isOverlayOpen={false}
7575
setOverlayOpen={setOverlayOpen}
7676
/>
7777
)}

src/components/header/components/searchDropdown/SearchDropdown.tsx

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import React, { useEffect, useRef, useState } from 'react';
2-
import { FilterIcon } from 'components/common/icons/FilterIcon';
32
import { SearchOverlay } from './searchOverlay/SearchOverlay/SearchOverlay';
43
import { HeaderActionWrapper } from '@app/components/header/Header.styles';
54
import { CategoryComponents } from '@app/components/header/components/HeaderSearch/HeaderSearch';
65
import { Btn, InputSearch } from '../HeaderSearch/HeaderSearch.styles';
76
import { useTranslation } from 'react-i18next';
87
import { 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';
1015
interface 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-fA-F0-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
);

src/components/layouts/main/MainContent/MainContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default styled(BaseLayout.Content)<HeaderProps>`
1717
${(props) =>
1818
props?.$isDesktop &&
1919
css`
20-
z-index: 105;
20+
z-index: 0;
2121
`}
2222
2323
@media only screen and ${media.md} {

0 commit comments

Comments
 (0)