Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 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
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
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
17 changes: 16 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,25 @@ import { usePWA } from './hooks/usePWA';
import { useThemeWatcher } from './hooks/useThemeWatcher';
import { useAppSelector } from './hooks/reduxHooks';
import { themeObject } from './styles/themes/themeVariables';
import NDK from '@nostr-dev-kit/ndk';
import { useNDKInit } from '@nostr-dev-kit/ndk-hooks';

const ndk = new NDK({
explicitRelayUrls: ['wss://relay.damus.io', 'wss://relay.nostr.band', 'wss://relay.snort.social', 'vault.iris.to'],
});
ndk
.connect()
.then(() => console.log('NDK connected'))
.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
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