Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0f19549
integrate ndk/ndk-hooks
AnthonyMarin Jul 2, 2025
57f293c
fix issue with z-index on search suggestions
AnthonyMarin Jul 3, 2025
7c5f2c1
Merge branch 'ui/search-suggestion-z-index-issues' into feature/ndk-i…
AnthonyMarin Jul 3, 2025
9a32fe9
fix: issue with space around images
AnthonyMarin Jul 3, 2025
f39b4f4
set up profile fetching
AnthonyMarin Jul 3, 2025
04f70fe
format: modal and avatar
AnthonyMarin Jul 3, 2025
9844315
Merge branch 'main' into feature/ndk-integration
AnthonyMarin Jul 3, 2025
d27e1be
fix issue with paid subscribers (under 7)
AnthonyMarin Jul 3, 2025
d79e6e5
remove trending creators (we now use paid subscribers)
AnthonyMarin Jul 3, 2025
1ed4802
add fallback icon for profiles without img
AnthonyMarin Jul 4, 2025
f39c408
map sorted profiles instead for view all component
AnthonyMarin Jul 4, 2025
05a63a2
add subscriber ribbon to profile card
AnthonyMarin Jul 4, 2025
413add4
disable search overlay
AnthonyMarin Jul 4, 2025
f492fd8
fix issue with closing view all modal
AnthonyMarin Jul 4, 2025
df6b4c6
delete obs components
AnthonyMarin Jul 4, 2025
07e3e2a
add loading states to modal
AnthonyMarin Jul 4, 2025
736da68
move SubscriberProfile creation to util file
AnthonyMarin Jul 4, 2025
d657ee7
implement simple search to app
AnthonyMarin Jul 4, 2025
a2be3b1
move coversion function from PaidSubscriber File
AnthonyMarin Jul 4, 2025
bd4301e
fix size of loading modal
AnthonyMarin Jul 4, 2025
a2de1a5
add error message for invalid pubkey
AnthonyMarin Jul 4, 2025
4a744b3
shorten invalid bukey message
AnthonyMarin Jul 4, 2025
3bd9695
skip fetching process if using dummy data
AnthonyMarin Jul 4, 2025
9c4149d
Implement hybrid profile fetching with NIP-42 authentication
Maphikza Jul 4, 2025
5208326
Fix paid subscribers to prioritize real data over dummy data
Maphikza Jul 4, 2025
9b2fbc8
Clean up excessive console.log statements
Maphikza Jul 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ REACT_APP_ASSETS_BUCKET=http://localhost
REACT_APP_DEMO_MODE=false
REACT_APP_BASENAME=

# Nostr relay configuration for profile fetching
REACT_APP_OWN_RELAY_URL=ws://localhost:7000
REACT_APP_NOSTR_RELAY_URLS=wss://relay.damus.io,wss://relay.nostr.band,wss://relay.snort.social,wss://vault.iris.to

# More info https://create-react-app.dev/docs/advanced-configuration
ESLINT_NO_DEV_ERRORS=true
TSC_COMPILE_ON_ERROR=true
5 changes: 5 additions & 0 deletions craco.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ module.exports = {
webpackConfig.plugins = webpackConfig.plugins.filter(plugin =>
!(plugin.constructor && plugin.constructor.name === 'PrettierPlugin')
);
webpackConfig.module.rules.push({
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto',
});
return webpackConfig;
},
plugins: [
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
"dependencies": {
"@ant-design/icons": "^4.6.2",
"@babel/core": "^7.24.7",
"@cashu/cashu-ts": "^2.5.2",
"@craco/craco": "^6.1.2",
"@lit-labs/react": "^1.0.2",
"@nostr-dev-kit/ndk": "^2.14.32",
"@nostr-dev-kit/ndk-hooks": "^1.2.3",
"@react-google-maps/api": "^2.18.1",
"@reduxjs/toolkit": "^1.7.1",
"@splidejs/react-splide": "^0.7.12",
Expand Down
35 changes: 34 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import { ConfigProvider } from 'antd';
import { HelmetProvider } from 'react-helmet-async';
import deDe from 'antd/lib/locale/de_DE';
Expand All @@ -13,10 +13,43 @@ import { usePWA } from './hooks/usePWA';
import { useThemeWatcher } from './hooks/useThemeWatcher';
import { useAppSelector } from './hooks/reduxHooks';
import { themeObject } from './styles/themes/themeVariables';
import NDK, { NDKEvent, NDKNip07Signer, NDKRelayAuthPolicies } from '@nostr-dev-kit/ndk';
import { useNDKInit } from '@nostr-dev-kit/ndk-hooks';
import config from './config/config';

// Configure NDK with user's relay URLs from environment variables
const getRelayUrls = () => {
const relayUrls = [...config.nostrRelayUrls];

// Add user's own relay URL as the first priority if provided
if (config.ownRelayUrl) {
relayUrls.unshift(config.ownRelayUrl);
}

return relayUrls;
};

const ndk = new NDK({
explicitRelayUrls: getRelayUrls(),
signer: new NDKNip07Signer(),
});

// Set up NIP-42 authentication policy following the example
ndk.relayAuthDefaultPolicy = NDKRelayAuthPolicies.signIn({ ndk });

ndk
.connect()
.then(() => console.log('NDK connected with relay URLs and NIP-42 auth policy:', getRelayUrls()))
.catch((error) => console.error('NDK connection error:', error));

const App: React.FC = () => {
const { language } = useLanguage();
const theme = useAppSelector((state) => state.theme.theme);
const initializeNDK = useNDKInit();

useEffect(() => {
initializeNDK(ndk);
}, [initializeNDK]);

usePWA();

Expand Down
2 changes: 0 additions & 2 deletions src/api/activity.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ export const getUserActivities = (handleLogout: () => void): Promise<WalletTrans
return [];
}
// Assuming your backend response matches the WalletTransaction interface
// eslint-disable-next-line
console.log("User Activity Data: ", data)
return data.map((item: any) => ({
id: item.ID,
witness_tx_id: item.Address,
Expand Down
4 changes: 2 additions & 2 deletions src/components/auth/LoginForm/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ export const LoginForm: React.FC = () => {
console.log('Signed event:', signedEvent);

const response = await verifyChallenge({
challenge: signedEvent.content,
challenge: event.content,
signature: signedEvent.sig,
messageHash: signedEvent.id,
messageHash: event.id,
event: signedEvent,
});

Expand Down
13 changes: 13 additions & 0 deletions src/components/header/Header.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { BaseCol } from '../common/BaseCol/BaseCol';

export const HeaderActionWrapper = styled.div`
cursor: pointer;
position: relative;

& > .ant-btn > span[role='img'],
.ant-badge {
Expand All @@ -22,6 +23,18 @@ export const HeaderActionWrapper = styled.div`
}
`;

export const InvalidPubkey = styled.div`
position: absolute;
top: 3rem;
left: 2rem;
width: 100%;
color: var(--error-color);
height: 100%;
display: flex;
align-items: center;
font-weight: bold;
`;

export const DropdownCollapse = styled(BaseCollapse)`
& > .ant-collapse-item > .ant-collapse-header {
font-weight: 600;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const HeaderSearch: React.FC = () => {

useEffect(() => {
setModalOpen(false);
setOverlayOpen(false);
//setOverlayOpen(false);
}, [pathname]);

return (
Expand All @@ -59,7 +59,7 @@ export const HeaderSearch: React.FC = () => {
query={query}
setQuery={setQuery}
data={sortedResults}
isOverlayOpen={isOverlayOpen}
isOverlayOpen={false}
setOverlayOpen={setOverlayOpen}
/>
</S.SearchModal>
Expand All @@ -71,7 +71,7 @@ export const HeaderSearch: React.FC = () => {
query={query}
setQuery={setQuery}
data={sortedResults}
isOverlayOpen={isOverlayOpen}
isOverlayOpen={false}
setOverlayOpen={setOverlayOpen}
/>
)}
Expand Down
105 changes: 101 additions & 4 deletions src/components/header/components/searchDropdown/SearchDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import React, { useEffect, useRef, useState } from 'react';
import { FilterIcon } from 'components/common/icons/FilterIcon';
import { SearchOverlay } from './searchOverlay/SearchOverlay/SearchOverlay';
import { HeaderActionWrapper } from '@app/components/header/Header.styles';
import { CategoryComponents } from '@app/components/header/components/HeaderSearch/HeaderSearch';
import { Btn, InputSearch } from '../HeaderSearch/HeaderSearch.styles';
import { useTranslation } from 'react-i18next';
import { BasePopover } from '@app/components/common/BasePopover/BasePopover';
import { NDKUserProfile, useNDK } from '@nostr-dev-kit/ndk-hooks';
import usePaidSubscribers from '@app/hooks/usePaidSubscribers';
import { convertNDKUserProfileToSubscriberProfile } from '@app/utils/utils';
import { InvalidPubkey } from '../../Header.styles';

import { SubscriberProfile } from '@app/hooks/usePaidSubscribers';
import { SubscriberDetailModal } from '@app/components/relay-dashboard/paid-subscribers/SubscriberDetailModal';
interface SearchOverlayProps {
query: string;
setQuery: (query: string) => void;
Expand All @@ -23,7 +28,13 @@ export const SearchDropdown: React.FC<SearchOverlayProps> = ({
setOverlayOpen,
}) => {
const [isFilterOpen, setFilterOpen] = useState(false);

const [isSubscriberDetailModalOpen, setSubscriberDetailModalOpen] = useState(false);
const [fetchingProfile, setFetchingProfile] = useState(false);
const [fetchingFailed, setFetchingFailed] = useState(false);
const [subscriberProfile, setSubscriberProfile] = useState<SubscriberProfile | null>(null);
const [invalidPubkey, setInvalidPubkey] = useState(false);
const { subscribers } = usePaidSubscribers();
const ndkInstance = useNDK();
const { t } = useTranslation();

useEffect(() => {
Expand All @@ -33,6 +44,74 @@ export const SearchDropdown: React.FC<SearchOverlayProps> = ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ref = useRef<any>(null);

const fetchProfile = async (pubkey: string): Promise<NDKUserProfile | null> => {
if (!ndkInstance) return null;

try {
setFetchingProfile(true);
const profile = await ndkInstance.ndk?.getUser({ pubkey: pubkey }).fetchProfile();

if (profile) {
setFetchingProfile(false);
setFetchingFailed(false);
return profile;
} else {
console.error('Profile not found for pubkey:', pubkey);
setFetchingProfile(false);
setFetchingFailed(true);
return null;
}
} catch (error) {
console.error('Error fetching profile:', error);
setFetchingProfile(false);
setFetchingFailed(true);
return null;
}
};

const handleSearchProfile = async () => {
if (!query) return;
//verify that it's a pubkey
if (/^[a-fA-F0-9]{64}$/.test(query)) {
setSubscriberDetailModalOpen(true);

//See if the pubkey exists in the subscribers
const pubkey = query;
const subscriber = subscribers.find((sub) => sub.pubkey === query);
// If it exists, open the modal with the subscriber details. If name,picture, or about are not set, fetch profile.
//if It doesnt exist, fetch the profile from NDK
//once fetched, convert it to SubscriberProfile and open the modal
if (subscriber) {
if (!subscriber.name || !subscriber.picture || !subscriber.about) {
const profile = await fetchProfile(pubkey);
if (profile) {
const subscriberProfile: SubscriberProfile = convertNDKUserProfileToSubscriberProfile(pubkey, profile);
// Open the modal with the fetched subscriber profile
setSubscriberProfile(subscriberProfile);
}
}
} else {
const profile = await fetchProfile(pubkey);
if (profile) {
const subscriberProfile: SubscriberProfile = convertNDKUserProfileToSubscriberProfile(pubkey, profile);
// Open the modal with the fetched subscriber profile
setSubscriberProfile(subscriberProfile);
}
}
}else{
setInvalidPubkey(true);
}
};
const onCloseSubscriberDetailModal = () => {
setSubscriberDetailModalOpen(false);
setSubscriberProfile(null);
};

useEffect(() => {
if(query.length === 0) {
setInvalidPubkey(false);
}
}, [query]);
return (
<>
<BasePopover
Expand All @@ -43,25 +122,43 @@ export const SearchDropdown: React.FC<SearchOverlayProps> = ({
getPopupContainer={() => ref.current}
>
<HeaderActionWrapper>
{invalidPubkey && (
<InvalidPubkey>
{"Invalid pubkey."}
</InvalidPubkey>
)}
<InputSearch
width="100%"
value={query}
placeholder={t('header.search')}
placeholder={t('header.search') + ' hex pubkey'}
filter={
<Btn
size="small"
type={isFilterOpen ? 'ghost' : 'text'}
aria-label="Filter"
icon={<FilterIcon />}
onClick={() => setFilterOpen(!isFilterOpen)}
/>
}
onChange={(event) => setQuery(event.target.value)}
enterButton={null}
addonAfter={null}
onKeyDown={(event) => {
if (event.key === 'Enter') {
handleSearchProfile();
}
}}
/>
<div ref={ref} />
</HeaderActionWrapper>
{isSubscriberDetailModalOpen && (
<SubscriberDetailModal
loading={fetchingProfile}
fetchFailed={fetchingFailed}
isVisible={isSubscriberDetailModalOpen}
subscriber={subscriberProfile}
onClose={onCloseSubscriberDetailModal}
/>
)}
</BasePopover>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/layouts/main/MainContent/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default styled(BaseLayout.Content)<HeaderProps>`
${(props) =>
props?.$isDesktop &&
css`
z-index: 105;
z-index: 0;
`}

@media only screen and ${media.md} {
Expand Down
Loading
Loading