feat: add badge for suspicious and spam users
This commit is contained in:
parent
2b4f673df1
commit
c84c479871
23 changed files with 185 additions and 22 deletions
|
|
@ -16,6 +16,7 @@ import Nip05 from '../Nip05'
|
|||
import NoteOptions from '../NoteOptions'
|
||||
import ParentNotePreview from '../ParentNotePreview'
|
||||
import TranslateButton from '../TranslateButton'
|
||||
import TrustScoreBadge from '../TrustScoreBadge'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
import CommunityDefinition from './CommunityDefinition'
|
||||
|
|
@ -125,6 +126,7 @@ export default function Note({
|
|||
skeletonClassName={size === 'small' ? 'h-3' : 'h-4'}
|
||||
/>
|
||||
<FollowingBadge pubkey={event.pubkey} />
|
||||
<TrustScoreBadge pubkey={event.pubkey} />
|
||||
<ClientTag event={event} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -19,13 +19,14 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import NotFound from '../NotFound'
|
||||
import SearchInput from '../SearchInput'
|
||||
import TextWithEmojis from '../TextWithEmojis'
|
||||
import TrustScoreBadge from '../TrustScoreBadge'
|
||||
import AvatarWithLightbox from './AvatarWithLightbox'
|
||||
import BannerWithLightbox from './BannerWithLightbox'
|
||||
import FollowedBy from './FollowedBy'
|
||||
import Followings from './Followings'
|
||||
import ProfileFeed from './ProfileFeed'
|
||||
import Relays from './Relays'
|
||||
import TextWithEmojis from '../TextWithEmojis'
|
||||
import AvatarWithLightbox from './AvatarWithLightbox'
|
||||
import BannerWithLightbox from './BannerWithLightbox'
|
||||
|
||||
export default function Profile({ id }: { id?: string }) {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -143,6 +144,7 @@ export default function Profile({ id }: { id?: string }) {
|
|||
emojis={emojis}
|
||||
className="text-xl font-semibold truncate select-text"
|
||||
/>
|
||||
<TrustScoreBadge pubkey={pubkey} />
|
||||
{isFollowingYou && (
|
||||
<div className="text-muted-foreground rounded-full bg-muted text-xs h-fit px-2 shrink-0">
|
||||
{t('Follows you')}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { useMemo } from 'react'
|
|||
import FollowButton from '../FollowButton'
|
||||
import Nip05 from '../Nip05'
|
||||
import ProfileAbout from '../ProfileAbout'
|
||||
import TextWithEmojis from '../TextWithEmojis'
|
||||
import TrustScoreBadge from '../TrustScoreBadge'
|
||||
import { SimpleUserAvatar } from '../UserAvatar'
|
||||
|
||||
export default function ProfileCard({ userId }: { userId: string }) {
|
||||
|
|
@ -18,7 +20,14 @@ export default function ProfileCard({ userId }: { userId: string }) {
|
|||
<FollowButton pubkey={pubkey} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-lg font-semibold truncate">{username}</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<TextWithEmojis
|
||||
text={username || ''}
|
||||
emojis={emojis}
|
||||
className="text-lg font-semibold truncate"
|
||||
/>
|
||||
<TrustScoreBadge pubkey={pubkey} />
|
||||
</div>
|
||||
<Nip05 pubkey={pubkey} />
|
||||
</div>
|
||||
{about && (
|
||||
|
|
|
|||
64
src/components/TrustScoreBadge/index.tsx
Normal file
64
src/components/TrustScoreBadge/index.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import trustScoreService from '@/services/trust-score.service'
|
||||
import { AlertTriangle, ShieldAlert } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function TrustScoreBadge({
|
||||
pubkey,
|
||||
className
|
||||
}: {
|
||||
pubkey: string
|
||||
className?: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { pubkey: currentPubkey } = useNostr()
|
||||
const [percentile, setPercentile] = useState<number | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPubkey === pubkey) {
|
||||
setLoading(false)
|
||||
setPercentile(null)
|
||||
return
|
||||
}
|
||||
|
||||
const fetchScore = async () => {
|
||||
try {
|
||||
const data = await trustScoreService.fetchTrustScore(pubkey)
|
||||
if (data) {
|
||||
setPercentile(data.percentile)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch trust score:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchScore()
|
||||
}, [pubkey, currentPubkey])
|
||||
|
||||
if (loading || percentile === null) return null
|
||||
|
||||
// percentile < 50: likely spam (red alert)
|
||||
// percentile < 75: suspicious (yellow warning)
|
||||
if (percentile < 50) {
|
||||
return (
|
||||
<div title={t('Likely spam account (Trust score: {{percentile}}%)', { percentile })}>
|
||||
<ShieldAlert className={cn('!size-4 text-red-500', className)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (percentile < 75) {
|
||||
return (
|
||||
<div title={t('Suspicious account (Trust score: {{percentile}}%)', { percentile })}>
|
||||
<AlertTriangle className={cn('!size-4 text-yellow-600 dark:text-yellow-500', className)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import { userIdToPubkey } from '@/lib/pubkey'
|
|||
import { cn } from '@/lib/utils'
|
||||
import { useMemo } from 'react'
|
||||
import FollowingBadge from '../FollowingBadge'
|
||||
import TrustScoreBadge from '../TrustScoreBadge'
|
||||
|
||||
export default function UserItem({
|
||||
userId,
|
||||
|
|
@ -32,6 +33,7 @@ export default function UserItem({
|
|||
skeletonClassName="h-4"
|
||||
/>
|
||||
{showFollowingBadge && <FollowingBadge pubkey={pubkey} />}
|
||||
<TrustScoreBadge pubkey={pubkey} />
|
||||
</div>
|
||||
<Nip05 pubkey={userId} />
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue