feat: display user profile name instead of public key

- Fetch user profile from nostr relay on login
- Display NIP-05, display name, or username instead of public key
- Priority: NIP-05 > name > username > public key
- Add WebSocket connection to fetch profile events (kind 0)
- Update Login component to fetch and pass profile data
- Update Bookmarks component to show user-friendly display name
- Fallback to formatted public key if no profile available
- Improve user experience with recognizable identity
This commit is contained in:
Gigi
2025-10-02 07:21:55 +02:00
parent fe35f45a42
commit 496b19f021
7 changed files with 114 additions and 9 deletions

5
.cursor/rules/210.mdc Normal file
View File

@@ -0,0 +1,5 @@
---
alwaysApply: true
---
Keep files below 210 lines.

View File

@@ -0,0 +1,5 @@
---
alwaysApply: true
---
Commit all pending changes. Commit using conventional commits. Always commit after each implementation step or change.

5
.cursor/rules/dry.mdc Normal file
View File

@@ -0,0 +1,5 @@
---
alwaysApply: true
---
Keep things simple. Strive to keep code DRY.

2
dist/index.html vendored
View File

@@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Markr - Nostr Bookmarks</title>
<script type="module" crossorigin src="/assets/index-Cnm-Nmlg.js"></script>
<script type="module" crossorigin src="/assets/index-Dh663TVM.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D18sZheu.css">
</head>
<body>

View File

@@ -8,6 +8,7 @@ function App() {
const [eventStore, setEventStore] = useState<EventStore | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [userPublicKey, setUserPublicKey] = useState<string | null>(null)
const [userProfile, setUserProfile] = useState<{name?: string, username?: string, nip05?: string} | null>(null)
useEffect(() => {
// Initialize event store
@@ -28,16 +29,19 @@ function App() {
</header>
{!isAuthenticated ? (
<Login onLogin={(publicKey) => {
<Login onLogin={(publicKey, profile) => {
setIsAuthenticated(true)
setUserPublicKey(publicKey)
setUserProfile(profile)
}} />
) : (
<Bookmarks
userPublicKey={userPublicKey}
userProfile={userProfile}
onLogout={() => {
setIsAuthenticated(false)
setUserPublicKey(null)
setUserProfile(null)
}}
/>
)}

View File

@@ -11,12 +11,19 @@ interface Bookmark {
tags: string[][]
}
interface UserProfile {
name?: string
username?: string
nip05?: string
}
interface BookmarksProps {
userPublicKey: string | null
userProfile: UserProfile | null
onLogout: () => void
}
const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, onLogout }) => {
const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, userProfile, onLogout }) => {
const [bookmarks, setBookmarks] = useState<Bookmark[]>([])
const [loading, setLoading] = useState(true)
const eventStore = useContext(EventStoreContext)
@@ -104,8 +111,23 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, onLogout }) => {
return new Date(timestamp * 1000).toLocaleDateString()
}
const formatPublicKey = (publicKey: string) => {
return `${publicKey.slice(0, 8)}...${publicKey.slice(-8)}`
const formatUserDisplay = (profile: UserProfile | null, publicKey: string | null) => {
if (!profile || (!profile.name && !profile.username && !profile.nip05)) {
return publicKey ? `${publicKey.slice(0, 8)}...${publicKey.slice(-8)}` : 'Unknown User'
}
// Priority: NIP-05 > name > username
if (profile.nip05) {
return profile.nip05
}
if (profile.name) {
return profile.name
}
if (profile.username) {
return `@${profile.username}`
}
return publicKey ? `${publicKey.slice(0, 8)}...${publicKey.slice(-8)}` : 'Unknown User'
}
if (loading) {
@@ -115,7 +137,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, onLogout }) => {
<div>
<h2>Your Bookmarks</h2>
{userPublicKey && (
<p className="user-info">Logged in as: {formatPublicKey(userPublicKey)}</p>
<p className="user-info">Logged in as: {formatUserDisplay(userProfile, userPublicKey)}</p>
)}
</div>
<button onClick={onLogout} className="logout-button">
@@ -133,7 +155,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, onLogout }) => {
<div>
<h2>Your Bookmarks ({bookmarks.length})</h2>
{userPublicKey && (
<p className="user-info">Logged in as: {formatPublicKey(userPublicKey)}</p>
<p className="user-info">Logged in as: {formatUserDisplay(userProfile, userPublicKey)}</p>
)}
</div>
<button onClick={onLogout} className="logout-button">

View File

@@ -1,12 +1,74 @@
import { useState } from 'react'
interface UserProfile {
name?: string
username?: string
nip05?: string
}
interface LoginProps {
onLogin: (publicKey: string) => void
onLogin: (publicKey: string, profile: UserProfile) => void
}
const Login: React.FC<LoginProps> = ({ onLogin }) => {
const [isConnecting, setIsConnecting] = useState(false)
const fetchUserProfile = async (publicKey: string): Promise<UserProfile> => {
try {
// Create a simple relay connection to fetch profile
const relay = new WebSocket('wss://relay.damus.io')
return new Promise((resolve) => {
const timeout = setTimeout(() => {
relay.close()
resolve({}) // Return empty profile if timeout
}, 5000)
relay.onopen = () => {
// Request profile event (kind 0)
relay.send(JSON.stringify([
"REQ",
"profile",
{
"kinds": [0],
"authors": [publicKey]
}
]))
}
relay.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
if (data[0] === 'EVENT' && data[2]?.kind === 0) {
const profileEvent = data[2]
const profileContent = JSON.parse(profileEvent.content || '{}')
clearTimeout(timeout)
relay.close()
resolve({
name: profileContent.name,
username: profileContent.username,
nip05: profileContent.nip05
})
}
} catch (error) {
console.error('Error parsing profile:', error)
}
}
relay.onerror = () => {
clearTimeout(timeout)
relay.close()
resolve({}) // Return empty profile on error
}
})
} catch (error) {
console.error('Error fetching profile:', error)
return {}
}
}
const handleLogin = async () => {
try {
setIsConnecting(true)
@@ -20,7 +82,9 @@ const Login: React.FC<LoginProps> = ({ onLogin }) => {
const publicKey = await window.nostr.getPublicKey()
if (publicKey) {
onLogin(publicKey)
// Fetch user profile from nostr
const profile = await fetchUserProfile(publicKey)
onLogin(publicKey, profile)
} else {
throw new Error('Failed to get public key')
}