mirror of
https://github.com/lumina-rocks/lumina.git
synced 2026-04-17 10:56:48 +02:00
feat: Show "Follows you" when logged in (#125)
* feat: add follow list functionality (NIP-02) to ProfileInfoCard component * feat: enhance avatar display with follow indication in ProfileInfoCard component * fix: add aria-label for accessibility on follow indicator in ProfileInfoCard --------- Co-authored-by: highperfocused <highperfocused@pm.me>
This commit is contained in:
78
.github/prompts/nostr-nip02.prompt.md
vendored
Normal file
78
.github/prompts/nostr-nip02.prompt.md
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
NIP-02
|
||||
======
|
||||
|
||||
Follow List
|
||||
-----------
|
||||
|
||||
`final` `optional`
|
||||
|
||||
A special event with kind `3`, meaning "follow list" is defined as having a list of `p` tags, one for each of the followed/known profiles one is following.
|
||||
|
||||
Each tag entry should contain the key for the profile, a relay URL where events from that key can be found (can be set to an empty string if not needed), and a local name (or "petname") for that profile (can also be set to an empty string or not provided), i.e., `["p", <32-bytes hex key>, <main relay URL>, <petname>]`.
|
||||
|
||||
The `.content` is not used.
|
||||
|
||||
For example:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 3,
|
||||
"tags": [
|
||||
["p", "91cf9..4e5ca", "wss://alicerelay.com/", "alice"],
|
||||
["p", "14aeb..8dad4", "wss://bobrelay.com/nostr", "bob"],
|
||||
["p", "612ae..e610f", "ws://carolrelay.com/ws", "carol"]
|
||||
],
|
||||
"content": "",
|
||||
// other fields...
|
||||
}
|
||||
```
|
||||
|
||||
Every new following list that gets published overwrites the past ones, so it should contain all entries. Relays and clients SHOULD delete past following lists as soon as they receive a new one.
|
||||
|
||||
Whenever new follows are added to an existing list, clients SHOULD append them to the end of the list, so they are stored in chronological order.
|
||||
|
||||
## Uses
|
||||
|
||||
### Follow list backup
|
||||
|
||||
If one believes a relay will store their events for sufficient time, they can use this kind-3 event to backup their following list and recover on a different device.
|
||||
|
||||
### Profile discovery and context augmentation
|
||||
|
||||
A client may rely on the kind-3 event to display a list of followed people by profiles one is browsing; make lists of suggestions on who to follow based on the follow lists of other people one might be following or browsing; or show the data in other contexts.
|
||||
|
||||
### Relay sharing
|
||||
|
||||
A client may publish a follow list with good relays for each of their follows so other clients may use these to update their internal relay lists if needed, increasing censorship-resistance.
|
||||
|
||||
### Petname scheme
|
||||
|
||||
The data from these follow lists can be used by clients to construct local ["petname"](http://www.skyhunter.com/marcs/petnames/IntroPetNames.html) tables derived from other people's follow lists. This alleviates the need for global human-readable names. For example:
|
||||
|
||||
A user has an internal follow list that says
|
||||
|
||||
```json
|
||||
[
|
||||
["p", "21df6d143fb96c2ec9d63726bf9edc71", "", "erin"]
|
||||
]
|
||||
```
|
||||
|
||||
And receives two follow lists, one from `21df6d143fb96c2ec9d63726bf9edc71` that says
|
||||
|
||||
```json
|
||||
[
|
||||
["p", "a8bb3d884d5d90b413d9891fe4c4e46d", "", "david"]
|
||||
]
|
||||
```
|
||||
|
||||
and another from `a8bb3d884d5d90b413d9891fe4c4e46d` that says
|
||||
|
||||
```json
|
||||
[
|
||||
["p", "f57f54057d2a7af0efecc8b0b66f5708", "", "frank"]
|
||||
]
|
||||
```
|
||||
|
||||
When the user sees `21df6d143fb96c2ec9d63726bf9edc71` the client can show _erin_ instead;
|
||||
When the user sees `a8bb3d884d5d90b413d9891fe4c4e46d` the client can show _david.erin_ instead;
|
||||
When the user sees `f57f54057d2a7af0efecc8b0b66f5708` the client can show _frank.david.erin_ instead.
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
import { Input } from './ui/input';
|
||||
import { Share1Icon, LightningBoltIcon, GlobeIcon } from '@radix-ui/react-icons';
|
||||
import { toast } from './ui/use-toast';
|
||||
import { Globe } from 'lucide-react';
|
||||
import { Globe, UserCheck } from 'lucide-react';
|
||||
import { Badge } from './ui/badge';
|
||||
import { MusicIcon, ActivityIcon } from 'lucide-react';
|
||||
|
||||
@@ -60,6 +60,27 @@ const ProfileInfoCard: React.FC<ProfileInfoCardProps> = React.memo(({ pubkey })
|
||||
},
|
||||
});
|
||||
|
||||
// Fetch follow list (NIP-02) to check if profile owner follows the logged-in user
|
||||
const { events: followListEvents } = useNostrEvents({
|
||||
filter: {
|
||||
authors: [pubkey],
|
||||
kinds: [3], // NIP-02 follow list event kind
|
||||
limit: 1,
|
||||
},
|
||||
enabled: !!userPubkey, // Only fetch if a user is logged in
|
||||
});
|
||||
|
||||
// Check if the profile owner follows the logged-in user
|
||||
const isFollowingUser = useMemo(() => {
|
||||
if (!userPubkey || followListEvents.length === 0) return false;
|
||||
|
||||
const followList = followListEvents[0];
|
||||
if (!followList) return false;
|
||||
|
||||
// Look for a 'p' tag with the logged-in user's pubkey
|
||||
return followList.tags.some(tag => tag[0] === 'p' && tag[1] === userPubkey);
|
||||
}, [followListEvents, userPubkey]);
|
||||
|
||||
// Get the latest status events by type
|
||||
const userStatuses = useMemo(() => {
|
||||
const statuses: StatusMap = {};
|
||||
@@ -217,9 +238,16 @@ const ProfileInfoCard: React.FC<ProfileInfoCardProps> = React.memo(({ pubkey })
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-6">
|
||||
<Avatar className="h-24 w-24">
|
||||
<AvatarImage className="object-cover w-full h-full" src={userData?.picture} alt={title} />
|
||||
</Avatar>
|
||||
<div className="relative">
|
||||
<Avatar className={`h-24 w-24 ${userPubkey && userPubkey !== pubkey && isFollowingUser ? 'ring-2 ring-primary ring-offset-2 ring-offset-background' : ''}`}>
|
||||
<AvatarImage className="object-cover w-full h-full" src={userData?.picture} alt={title} />
|
||||
</Avatar>
|
||||
{userPubkey && userPubkey !== pubkey && isFollowingUser && (
|
||||
<div className="absolute -top-1 -right-1 bg-primary text-primary-foreground rounded-full p-1" title="Follows you">
|
||||
<UserCheck className="h-4 w-4" aria-label='Follows you' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Link href={`/profile/${nip19.npubEncode(pubkey)}`}>
|
||||
<div className="text-2xl">{title}</div>
|
||||
|
||||
Reference in New Issue
Block a user