Browse Source

fix following and add mute display on following list

imwald
Silberengel 4 months ago
parent
commit
9e05ea1c09
  1. 51
      src/components/FollowButton/index.tsx
  2. 4
      src/components/MuteButton/index.tsx
  3. 6
      src/components/OthersRelayList/index.tsx
  4. 37
      src/pages/secondary/FollowPacksPage/index.tsx
  5. 4
      src/pages/secondary/FollowingListPage/index.tsx
  6. 2
      src/pages/secondary/MuteListPage/index.tsx
  7. 2
      src/pages/secondary/OthersRelaySettingsPage/index.tsx

51
src/components/FollowButton/index.tsx

@ -11,6 +11,7 @@ import {
} from '@/components/ui/alert-dialog' } from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { useFollowList } from '@/providers/FollowListProvider' import { useFollowList } from '@/providers/FollowListProvider'
import { useMuteList } from '@/providers/MuteListProvider'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import { Loader } from 'lucide-react' import { Loader } from 'lucide-react'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
@ -21,9 +22,11 @@ export default function FollowButton({ pubkey }: { pubkey: string }) {
const { t } = useTranslation() const { t } = useTranslation()
const { pubkey: accountPubkey, checkLogin } = useNostr() const { pubkey: accountPubkey, checkLogin } = useNostr()
const { followings, follow, unfollow } = useFollowList() const { followings, follow, unfollow } = useFollowList()
const { mutePubkeySet, unmutePubkey } = useMuteList()
const [updating, setUpdating] = useState(false) const [updating, setUpdating] = useState(false)
const [hover, setHover] = useState(false) const [hover, setHover] = useState(false)
const isFollowing = useMemo(() => followings.includes(pubkey), [followings, pubkey]) const isFollowing = useMemo(() => followings.includes(pubkey), [followings, pubkey])
const isMuted = useMemo(() => mutePubkeySet.has(pubkey), [mutePubkeySet, pubkey])
if (!accountPubkey || (pubkey && pubkey === accountPubkey)) return null if (!accountPubkey || (pubkey && pubkey === accountPubkey)) return null
@ -59,6 +62,54 @@ export default function FollowButton({ pubkey }: { pubkey: string }) {
}) })
} }
const handleUnmute = async (e: React.MouseEvent) => {
e.stopPropagation()
checkLogin(async () => {
if (!isMuted) return
setUpdating(true)
try {
await unmutePubkey(pubkey)
toast.success(t('User unmuted'))
} catch (error) {
toast.error(t('Unmute failed') + ': ' + (error as Error).message)
} finally {
setUpdating(false)
}
})
}
// If following and muted, show "Muted" button instead of "Following"
if (isFollowing && isMuted) {
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
className="rounded-full min-w-28 max-w-full text-destructive whitespace-normal break-words px-3"
variant="secondary"
disabled={updating}
>
{updating ? <Loader className="animate-spin" /> : <span className="text-destructive text-center">{t('Muted')}</span>}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t('Unmute user')}?</AlertDialogTitle>
<AlertDialogDescription>
{t('Are you sure you want to unmute this user? This will restore the follow button.')}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t('Cancel')}</AlertDialogCancel>
<AlertDialogAction onClick={handleUnmute}>
{t('Unmute')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
return isFollowing ? ( return isFollowing ? (
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>

4
src/components/MuteButton/index.tsx

@ -64,12 +64,12 @@ export default function MuteButton({ pubkey }: { pubkey: string }) {
if (isMuted) { if (isMuted) {
return ( return (
<Button <Button
className="w-20 min-w-20 rounded-full" className="rounded-full min-w-20 max-w-full text-destructive whitespace-normal break-words px-3"
variant="secondary" variant="secondary"
onClick={handleUnmute} onClick={handleUnmute}
disabled={updating || changing} disabled={updating || changing}
> >
{updating ? <Loader className="animate-spin" /> : t('Unmute')} {updating ? <Loader className="animate-spin" /> : <span className="text-destructive text-center">{t('Unmute')}</span>}
</Button> </Button>
) )
} }

6
src/components/OthersRelayList/index.tsx

@ -1,4 +1,4 @@
import { useSecondaryPage } from '@/PageManager' import { useSmartRelayNavigation } from '@/PageManager'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { useFetchRelayInfo, useFetchRelayList } from '@/hooks' import { useFetchRelayInfo, useFetchRelayList } from '@/hooks'
import { toRelay } from '@/lib/link' import { toRelay } from '@/lib/link'
@ -28,12 +28,12 @@ export default function OthersRelayList({ userId }: { userId: string }) {
function RelayItem({ relay }: { relay: TMailboxRelay }) { function RelayItem({ relay }: { relay: TMailboxRelay }) {
const { t } = useTranslation() const { t } = useTranslation()
const { push } = useSecondaryPage() const { navigateToRelay } = useSmartRelayNavigation()
const { relayInfo } = useFetchRelayInfo(relay.url) const { relayInfo } = useFetchRelayInfo(relay.url)
const { url, scope } = relay const { url, scope } = relay
return ( return (
<div className="p-4 rounded-lg border clickable space-y-1" onClick={() => push(toRelay(url))}> <div className="p-4 rounded-lg border clickable space-y-1" onClick={() => navigateToRelay(toRelay(url))}>
<RelaySimpleInfo relayInfo={relayInfo} /> <RelaySimpleInfo relayInfo={relayInfo} />
<div className="flex gap-2"> <div className="flex gap-2">
{['both', 'read'].includes(scope) && ( {['both', 'read'].includes(scope) && (

37
src/pages/secondary/FollowPacksPage/index.tsx

@ -2,6 +2,7 @@ import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { useFollowList } from '@/providers/FollowListProvider' import { useFollowList } from '@/providers/FollowListProvider'
import { useMuteList } from '@/providers/MuteListProvider'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import { getPubkeysFromPTags } from '@/lib/tag' import { getPubkeysFromPTags } from '@/lib/tag'
import { Event } from 'nostr-tools' import { Event } from 'nostr-tools'
@ -22,6 +23,7 @@ const FollowPacksPage = forwardRef<HTMLDivElement, { index?: number; hideTitleba
const { t } = useTranslation() const { t } = useTranslation()
const { pubkey } = useNostr() const { pubkey } = useNostr()
const { followings, follow } = useFollowList() const { followings, follow } = useFollowList()
const { mutePubkeySet } = useMuteList()
const [packs, setPacks] = useState<Event[]>([]) const [packs, setPacks] = useState<Event[]>([])
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [_followingPacks, setFollowingPacks] = useState<Set<string>>(new Set()) const [_followingPacks, setFollowingPacks] = useState<Set<string>>(new Set())
@ -77,22 +79,29 @@ const FollowPacksPage = forwardRef<HTMLDivElement, { index?: number; hideTitleba
const packPubkeys = getPubkeysFromPTags(pack.tags) const packPubkeys = getPubkeysFromPTags(pack.tags)
const followingSet = new Set(followings) const followingSet = new Set(followings)
const toFollow = packPubkeys.filter(p => !followingSet.has(p)) // Filter out users that are already followed OR muted
const toFollow = packPubkeys.filter(p => !followingSet.has(p) && !mutePubkeySet.has(p))
if (toFollow.length === 0) { if (toFollow.length === 0) {
const mutedCount = packPubkeys.filter(p => mutePubkeySet.has(p) && !followingSet.has(p)).length
if (mutedCount > 0) {
toast.info(t('All available members are already followed or muted'))
} else {
toast.info(t('You are already following all members of this pack')) toast.info(t('You are already following all members of this pack'))
}
return return
} }
try { try {
// Follow all pubkeys in the pack // Follow all pubkeys in the pack (excluding muted users)
for (const pubkeyToFollow of toFollow) { for (const pubkeyToFollow of toFollow) {
await follow(pubkeyToFollow) await follow(pubkeyToFollow)
} }
toast.success(t('Followed {{count}} users', { count: toFollow.length })) toast.success(t('Followed {{count}} users', { count: toFollow.length }))
// Update followingPacks if all members are now followed // Update followingPacks if all non-muted members are now followed
if (packPubkeys.every(p => followingSet.has(p) || toFollow.includes(p))) { const nonMutedPackPubkeys = packPubkeys.filter(p => !mutePubkeySet.has(p))
if (nonMutedPackPubkeys.length > 0 && nonMutedPackPubkeys.every(p => followingSet.has(p) || toFollow.includes(p))) {
setFollowingPacks(prev => new Set([...prev, pack.id])) setFollowingPacks(prev => new Set([...prev, pack.id]))
} }
} catch (error) { } catch (error) {
@ -127,7 +136,7 @@ const FollowPacksPage = forwardRef<HTMLDivElement, { index?: number; hideTitleba
if (!pubkey) { if (!pubkey) {
return ( return (
<SecondaryPageLayout ref={ref} index={index} title={t('Browse Follow Packs')} hideBackButton={hideTitlebar}> <SecondaryPageLayout ref={ref} index={index} title={hideTitlebar ? undefined : t('Browse Follow Packs')} hideBackButton={hideTitlebar}>
<div className="flex flex-col items-center justify-center py-16"> <div className="flex flex-col items-center justify-center py-16">
<div className="text-lg font-semibold mb-2">{t('Please log in')}</div> <div className="text-lg font-semibold mb-2">{t('Please log in')}</div>
<div className="text-sm text-muted-foreground">{t('You need to be logged in to browse follow packs')}</div> <div className="text-sm text-muted-foreground">{t('You need to be logged in to browse follow packs')}</div>
@ -137,7 +146,7 @@ const FollowPacksPage = forwardRef<HTMLDivElement, { index?: number; hideTitleba
} }
return ( return (
<SecondaryPageLayout ref={ref} index={index} title={t('Browse Follow Packs')} hideBackButton={hideTitlebar} displayScrollToTopButton> <SecondaryPageLayout ref={ref} index={index} title={hideTitlebar ? undefined : t('Browse Follow Packs')} hideBackButton={hideTitlebar} displayScrollToTopButton>
<div className="space-y-4 p-4"> <div className="space-y-4 p-4">
{!isLoading && packs.length > 0 && ( {!isLoading && packs.length > 0 && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -178,8 +187,10 @@ const FollowPacksPage = forwardRef<HTMLDivElement, { index?: number; hideTitleba
{filteredPacks.map((pack) => { {filteredPacks.map((pack) => {
const packPubkeys = getPubkeysFromPTags(pack.tags) const packPubkeys = getPubkeysFromPTags(pack.tags)
const followingSet = new Set(followings) const followingSet = new Set(followings)
const alreadyFollowingAll = packPubkeys.length > 0 && packPubkeys.every(p => followingSet.has(p)) // Exclude muted users from calculations
const toFollowCount = packPubkeys.filter(p => !followingSet.has(p)).length const availablePubkeys = packPubkeys.filter(p => !mutePubkeySet.has(p))
const alreadyFollowingAll = availablePubkeys.length > 0 && availablePubkeys.every(p => followingSet.has(p))
const toFollowCount = availablePubkeys.filter(p => !followingSet.has(p)).length
return ( return (
<Card key={pack.id}> <Card key={pack.id}>
@ -192,12 +203,12 @@ const FollowPacksPage = forwardRef<HTMLDivElement, { index?: number; hideTitleba
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="flex items-center gap-2 text-sm text-muted-foreground"> <div className="flex items-center gap-2 text-sm text-muted-foreground">
<Users className="size-4" /> <Users className="size-4" />
<span>{t('{{count}} profiles', { count: packPubkeys.length })}</span> <span>{t('{{count}} profiles', { count: availablePubkeys.length })}</span>
</div> </div>
{packPubkeys.length > 0 && ( {availablePubkeys.length > 0 && (
<div className="flex -space-x-2"> <div className="flex -space-x-2">
{packPubkeys.slice(0, 5).map((pubkey) => ( {availablePubkeys.slice(0, 5).map((pubkey) => (
<SimpleUserAvatar <SimpleUserAvatar
key={pubkey} key={pubkey}
userId={pubkey} userId={pubkey}
@ -205,9 +216,9 @@ const FollowPacksPage = forwardRef<HTMLDivElement, { index?: number; hideTitleba
className="border-2 border-background" className="border-2 border-background"
/> />
))} ))}
{packPubkeys.length > 5 && ( {availablePubkeys.length > 5 && (
<div className="size-8 rounded-full border-2 border-background bg-muted flex items-center justify-center text-xs"> <div className="size-8 rounded-full border-2 border-background bg-muted flex items-center justify-center text-xs">
+{packPubkeys.length - 5} +{availablePubkeys.length - 5}
</div> </div>
)} )}
</div> </div>

4
src/pages/secondary/FollowingListPage/index.tsx

@ -14,7 +14,9 @@ const FollowingListPage = forwardRef(({ id, index, hideTitlebar = false }: { id?
ref={ref} ref={ref}
index={index} index={index}
title={ title={
profile?.username hideTitlebar
? undefined
: profile?.username
? t("username's following", { username: profile.username }) ? t("username's following", { username: profile.username })
: t('Following') : t('Following')
} }

2
src/pages/secondary/MuteListPage/index.tsx

@ -60,7 +60,7 @@ const MuteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb
<SecondaryPageLayout <SecondaryPageLayout
ref={ref} ref={ref}
index={index} index={index}
title={t("username's muted", { username: profile.username })} title={hideTitlebar ? undefined : t("username's muted", { username: profile.username })}
hideBackButton={hideTitlebar} hideBackButton={hideTitlebar}
displayScrollToTopButton displayScrollToTopButton
> >

2
src/pages/secondary/OthersRelaySettingsPage/index.tsx

@ -16,7 +16,7 @@ const RelaySettingsPage = forwardRef(({ id, index, hideTitlebar = false }: { id?
<SecondaryPageLayout <SecondaryPageLayout
ref={ref} ref={ref}
index={index} index={index}
title={t("username's used relays", { username: profile.username })} title={hideTitlebar ? undefined : t("username's used relays", { username: profile.username })}
hideBackButton={hideTitlebar} hideBackButton={hideTitlebar}
> >
<div className="px-4 pt-3"> <div className="px-4 pt-3">

Loading…
Cancel
Save