Browse Source

fix bugs

imwald
Silberengel 4 weeks ago
parent
commit
ec4539a664
  1. 21
      src/components/Nip05/index.tsx
  2. 54
      src/components/Profile/FollowedBy.tsx
  3. 30
      src/components/Profile/index.tsx
  4. 3
      src/constants.ts
  5. 2
      src/hooks/useFetchProfile.tsx
  6. 24
      src/hooks/useProfileAuthorFeedSubRequests.ts
  7. 7
      src/services/client-replaceable-events.service.ts

21
src/components/Nip05/index.tsx

@ -6,12 +6,19 @@ import { SecondaryPageLink } from '@/PageManager' @@ -6,12 +6,19 @@ import { SecondaryPageLink } from '@/PageManager'
import { BadgeAlert, BadgeCheck } from 'lucide-react'
import { Favicon } from '../Favicon'
export default function Nip05({ pubkey, append }: { pubkey: string; append?: string }) {
const { profile } = useFetchProfile(pubkey)
const { nip05IsVerified, nip05Name, nip05Domain, isFetching } = useFetchNip05(
profile?.nip05,
pubkey
)
export default function Nip05({
pubkey,
nip05: nip05Prop,
append
}: {
pubkey: string
/** When set (e.g. profile page), skip a second {@link useFetchProfile} network pass. */
nip05?: string
append?: string
}) {
const { profile } = useFetchProfile(nip05Prop === undefined ? pubkey : undefined)
const resolvedNip05 = nip05Prop ?? profile?.nip05
const { nip05IsVerified, nip05Name, nip05Domain, isFetching } = useFetchNip05(resolvedNip05, pubkey)
if (isFetching) {
return (
@ -21,7 +28,7 @@ export default function Nip05({ pubkey, append }: { pubkey: string; append?: str @@ -21,7 +28,7 @@ export default function Nip05({ pubkey, append }: { pubkey: string; append?: str
)
}
if (!profile?.nip05 || !nip05Name || !nip05Domain) return null
if (!resolvedNip05 || !nip05Name || !nip05Domain) return null
return (
<div

54
src/components/Profile/FollowedBy.tsx

@ -1,54 +0,0 @@ @@ -1,54 +0,0 @@
import UserAvatar from '@/components/UserAvatar'
import { useNostr } from '@/providers/NostrProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { replaceableEventService } from '@/services/client.service'
import { getPubkeysFromPTags } from '@/lib/tag'
import { kinds } from 'nostr-tools'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
export default function FollowedBy({ pubkey }: { pubkey: string }) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
const [followedBy, setFollowedBy] = useState<string[]>([])
const { pubkey: accountPubkey } = useNostr()
useEffect(() => {
if (!pubkey || !accountPubkey) return
const init = async () => {
const followListEvent = await replaceableEventService.fetchReplaceableEvent(accountPubkey, kinds.Contacts)
const followings = followListEvent ? getPubkeysFromPTags(followListEvent.tags).reverse() : []
const followingsOfFollowings = await Promise.all(
followings.map(async (following) => {
const followListEvent = await replaceableEventService.fetchReplaceableEvent(following, kinds.Contacts)
return followListEvent ? getPubkeysFromPTags(followListEvent.tags) : []
})
)
const _followedBy: string[] = []
const limit = isSmallScreen ? 3 : 5
for (const [index, following] of followings.entries()) {
if (following === pubkey) continue
if (followingsOfFollowings[index].includes(pubkey)) {
_followedBy.push(following)
}
if (_followedBy.length >= limit) {
break
}
}
setFollowedBy(_followedBy)
}
init()
}, [pubkey, accountPubkey])
if (followedBy.length === 0) return null
return (
<div className="flex items-center gap-1">
<div className="text-muted-foreground">{t('Followed by')}</div>
{followedBy.map((p) => (
<UserAvatar userId={p} key={p} size="xSmall" />
))}
</div>
)
}

30
src/components/Profile/index.tsx

@ -62,7 +62,6 @@ import { useTranslation } from 'react-i18next' @@ -62,7 +62,6 @@ import { useTranslation } from 'react-i18next'
import logger from '@/lib/logger'
import { AlexandriaEventsSearchEmptyCta } from '@/components/AlexandriaEventsSearchEmptyCta'
import NotFound from '../NotFound'
import FollowedBy from './FollowedBy'
import ProfileBadges from './ProfileBadges'
import ProfileFeed from './ProfileFeed'
import ProfileReportsDialog from './ProfileReportsDialog'
@ -195,9 +194,14 @@ export default function Profile({ @@ -195,9 +194,14 @@ export default function Profile({
}, [syncAuthorReplaceablesFromCache])
useEffect(() => {
if (!profile?.pubkey) return
void client.refreshAuthorPublishedReplaceablesOnProfileView(profile.pubkey)
}, [profile?.pubkey])
if (!profile?.pubkey || profile.batchPlaceholder) return
const pk = profile.pubkey
// Defer wide replaceable refresh so initial kind-0 / feed relay setup can finish first.
const timer = window.setTimeout(() => {
void client.refreshAuthorPublishedReplaceablesOnProfileView(pk)
}, 2_000)
return () => clearTimeout(timer)
}, [profile?.pubkey, profile?.batchPlaceholder])
useEffect(() => {
if (!isSelf || !profile?.pubkey || !accountProfileEvent) return
@ -213,7 +217,7 @@ export default function Profile({ @@ -213,7 +217,7 @@ export default function Profile({
const onAuthorReplaceablesRefreshed: EventListener = (domEvt) => {
const detailPk = (domEvt as CustomEvent<{ pubkey?: string }>).detail?.pubkey?.toLowerCase()
if (detailPk !== pk) return
void syncAuthorReplaceablesFromCache(profile.pubkey, { bustCache: true })
void syncAuthorReplaceablesFromCache(profile.pubkey)
}
window.addEventListener(
ReplaceableEventService.AUTHOR_REPLACEABLES_REFRESHED_EVENT,
@ -226,10 +230,6 @@ export default function Profile({ @@ -226,10 +230,6 @@ export default function Profile({
)
}, [profile?.pubkey, syncAuthorReplaceablesFromCache])
const isFollowingYou = useMemo(() => {
// This will be handled by the FollowedBy component
return false
}, [profile, accountPubkey])
const defaultImage = useMemo(
() => (profile?.pubkey ? generateImageByPubkey(profile?.pubkey) : ''),
[profile]
@ -524,13 +524,8 @@ export default function Profile({ @@ -524,13 +524,8 @@ export default function Profile({
<div className="pt-2 pb-4 md:pl-56">
<div className="flex flex-wrap gap-2 items-center min-w-0">
<div className="text-xl font-semibold truncate select-text max-w-full">{username}</div>
{isFollowingYou && (
<div className="text-muted-foreground rounded-full bg-muted text-xs h-fit px-2 shrink-0">
{t('Follows you')}
</div>
)}
</div>
<Nip05 pubkey={pubkey} />
<Nip05 pubkey={pubkey} nip05={profile.nip05} />
{/* Display multiple NIP-05 values if available, with verification */}
{nip05List && nip05List.length > 1 && (
<Nip05List nip05List={nip05List.slice(1)} pubkey={pubkey} />
@ -608,14 +603,11 @@ export default function Profile({ @@ -608,14 +603,11 @@ export default function Profile({
defaultLightningAddress={zapLightningDefault}
prefetchedPayment={prefetchedZapPayment}
/>
<div className="flex flex-wrap justify-between items-center gap-x-4 gap-y-2 mt-2 text-sm min-w-0">
<div className="flex flex-wrap gap-4 items-center min-w-0">
<div className="flex flex-wrap gap-4 items-center gap-x-4 gap-y-2 mt-2 text-sm min-w-0">
<SmartFollowings pubkey={pubkey} />
<SmartRelays pubkey={pubkey} />
{isSelf && <SmartMuteLink />}
</div>
{!isSelf && <FollowedBy pubkey={pubkey} />}
</div>
<ProfileBadges pubkey={pubkey} profileEventId={effectiveProfileEvent?.id} />
</div>
</div>

3
src/constants.ts

@ -406,7 +406,7 @@ export const NIP66_DISCOVERY_RELAY_URLS = [ @@ -406,7 +406,7 @@ export const NIP66_DISCOVERY_RELAY_URLS = [
// Relay with bookstr composite index support
export const BOOKSTR_RELAY_URLS = [
'wss://orly-relay.imwald.eu'
'wss://thecitadel.nostr1.com'
]
/**
@ -469,7 +469,6 @@ export const FAST_READ_RELAY_URLS = [ @@ -469,7 +469,6 @@ export const FAST_READ_RELAY_URLS = [
'wss://theforest.nostr1.com',
'wss://nostr.land',
'wss://nostr.wine',
'wss://orly-relay.imwald.eu',
'wss://nostr21.com'
]

2
src/hooks/useFetchProfile.tsx

@ -691,6 +691,8 @@ export function useFetchProfile(id?: string, skipCache = false) { @@ -691,6 +691,8 @@ export function useFetchProfile(id?: string, skipCache = false) {
const onAuthorReplaceablesRefreshed: EventListener = (domEvt) => {
const detailPk = (domEvt as CustomEvent<{ pubkey?: string }>).detail?.pubkey?.toLowerCase()
if (detailPk !== pkLowerResolved) return
// Background profile-view refresh already persisted kind 0 — avoid a second full fetchProfileEvent pass.
if (initializedPubkeysRef.current.has(pkLowerResolved)) return
void checkProfile(pkLowerResolved, { current: profileRefreshCancelledRef.current })
}
window.addEventListener(

24
src/hooks/useProfileAuthorFeedSubRequests.ts

@ -88,11 +88,7 @@ export function useProfileAuthorFeedSubRequests({ @@ -88,11 +88,7 @@ export function useProfileAuthorFeedSubRequests({
let cancelled = false
const socialKinds = kinds.some(isSocialKindBlockedKind)
void client
.fetchRelayList(pubkey)
.catch(() => emptyAuthor)
.then((authorRl) => {
if (cancelled) return
const applyRelayList = (authorRl: typeof emptyAuthor) => {
const urls = buildProfilePageReadRelayUrls(
favoriteRelays,
blockedRelays,
@ -102,7 +98,25 @@ export function useProfileAuthorFeedSubRequests({ @@ -102,7 +98,25 @@ export function useProfileAuthorFeedSubRequests({
kinds,
useGlobalRelayBootstrap
)
if (urls.length > 0) {
setRelayUrls(urls)
}
}
void client
.peekRelayListFromStorage(pubkey)
.then((cached) => {
if (cancelled) return
applyRelayList(cached)
})
.catch(() => {})
void client
.fetchRelayList(pubkey)
.catch(() => emptyAuthor)
.then((authorRl) => {
if (cancelled) return
applyRelayList(authorRl)
})
return () => {

7
src/services/client-replaceable-events.service.ts

@ -87,6 +87,8 @@ export class ReplaceableEventService { @@ -87,6 +87,8 @@ export class ReplaceableEventService {
})
/** One in-flight profile replaceables pull per author (avoids stacked REQs when profile UI remounts). */
private authorReplaceablesRefreshByPubkey = new Map<string, Promise<void>>()
/** Per-author cooldown after a successful profile-view replaceable sweep (avoids reopen loops). */
private authorProfileViewRefreshNotBeforeMs = new Map<string, number>()
private replaceableEventFromBigRelaysDataloader: DataLoader<
{ pubkey: string; kind: number },
NEvent | null,
@ -1409,6 +1411,9 @@ export class ReplaceableEventService { @@ -1409,6 +1411,9 @@ export class ReplaceableEventService {
const pk = pubkey.trim().toLowerCase()
if (!/^[0-9a-f]{64}$/.test(pk)) return
const notBefore = this.authorProfileViewRefreshNotBeforeMs.get(pk) ?? 0
if (Date.now() < notBefore) return
const inFlight = this.authorReplaceablesRefreshByPubkey.get(pk)
if (inFlight) return inFlight
@ -1502,6 +1507,8 @@ export class ReplaceableEventService { @@ -1502,6 +1507,8 @@ export class ReplaceableEventService {
})
)
this.authorProfileViewRefreshNotBeforeMs.set(pk, Date.now() + 90_000)
if (typeof window !== 'undefined') {
window.dispatchEvent(
new CustomEvent(ReplaceableEventService.AUTHOR_REPLACEABLES_REFRESHED_EVENT, { detail: { pubkey: pk } })

Loading…
Cancel
Save