mirror of
https://github.com/dergigi/boris.git
synced 2025-12-17 06:34:24 +01:00
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:
5
.cursor/rules/210.mdc
Normal file
5
.cursor/rules/210.mdc
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Keep files below 210 lines.
|
||||||
5
.cursor/rules/always-commit.mdc
Normal file
5
.cursor/rules/always-commit.mdc
Normal 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
5
.cursor/rules/dry.mdc
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Keep things simple. Strive to keep code DRY.
|
||||||
2
dist/index.html
vendored
2
dist/index.html
vendored
@@ -5,7 +5,7 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Markr - Nostr Bookmarks</title>
|
<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">
|
<link rel="stylesheet" crossorigin href="/assets/index-D18sZheu.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ function App() {
|
|||||||
const [eventStore, setEventStore] = useState<EventStore | null>(null)
|
const [eventStore, setEventStore] = useState<EventStore | null>(null)
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||||
const [userPublicKey, setUserPublicKey] = useState<string | null>(null)
|
const [userPublicKey, setUserPublicKey] = useState<string | null>(null)
|
||||||
|
const [userProfile, setUserProfile] = useState<{name?: string, username?: string, nip05?: string} | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialize event store
|
// Initialize event store
|
||||||
@@ -28,16 +29,19 @@ function App() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{!isAuthenticated ? (
|
{!isAuthenticated ? (
|
||||||
<Login onLogin={(publicKey) => {
|
<Login onLogin={(publicKey, profile) => {
|
||||||
setIsAuthenticated(true)
|
setIsAuthenticated(true)
|
||||||
setUserPublicKey(publicKey)
|
setUserPublicKey(publicKey)
|
||||||
|
setUserProfile(profile)
|
||||||
}} />
|
}} />
|
||||||
) : (
|
) : (
|
||||||
<Bookmarks
|
<Bookmarks
|
||||||
userPublicKey={userPublicKey}
|
userPublicKey={userPublicKey}
|
||||||
|
userProfile={userProfile}
|
||||||
onLogout={() => {
|
onLogout={() => {
|
||||||
setIsAuthenticated(false)
|
setIsAuthenticated(false)
|
||||||
setUserPublicKey(null)
|
setUserPublicKey(null)
|
||||||
|
setUserProfile(null)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -11,12 +11,19 @@ interface Bookmark {
|
|||||||
tags: string[][]
|
tags: string[][]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UserProfile {
|
||||||
|
name?: string
|
||||||
|
username?: string
|
||||||
|
nip05?: string
|
||||||
|
}
|
||||||
|
|
||||||
interface BookmarksProps {
|
interface BookmarksProps {
|
||||||
userPublicKey: string | null
|
userPublicKey: string | null
|
||||||
|
userProfile: UserProfile | null
|
||||||
onLogout: () => void
|
onLogout: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, onLogout }) => {
|
const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, userProfile, onLogout }) => {
|
||||||
const [bookmarks, setBookmarks] = useState<Bookmark[]>([])
|
const [bookmarks, setBookmarks] = useState<Bookmark[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const eventStore = useContext(EventStoreContext)
|
const eventStore = useContext(EventStoreContext)
|
||||||
@@ -104,8 +111,23 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, onLogout }) => {
|
|||||||
return new Date(timestamp * 1000).toLocaleDateString()
|
return new Date(timestamp * 1000).toLocaleDateString()
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatPublicKey = (publicKey: string) => {
|
const formatUserDisplay = (profile: UserProfile | null, publicKey: string | null) => {
|
||||||
return `${publicKey.slice(0, 8)}...${publicKey.slice(-8)}`
|
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) {
|
if (loading) {
|
||||||
@@ -115,7 +137,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, onLogout }) => {
|
|||||||
<div>
|
<div>
|
||||||
<h2>Your Bookmarks</h2>
|
<h2>Your Bookmarks</h2>
|
||||||
{userPublicKey && (
|
{userPublicKey && (
|
||||||
<p className="user-info">Logged in as: {formatPublicKey(userPublicKey)}</p>
|
<p className="user-info">Logged in as: {formatUserDisplay(userProfile, userPublicKey)}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button onClick={onLogout} className="logout-button">
|
<button onClick={onLogout} className="logout-button">
|
||||||
@@ -133,7 +155,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({ userPublicKey, onLogout }) => {
|
|||||||
<div>
|
<div>
|
||||||
<h2>Your Bookmarks ({bookmarks.length})</h2>
|
<h2>Your Bookmarks ({bookmarks.length})</h2>
|
||||||
{userPublicKey && (
|
{userPublicKey && (
|
||||||
<p className="user-info">Logged in as: {formatPublicKey(userPublicKey)}</p>
|
<p className="user-info">Logged in as: {formatUserDisplay(userProfile, userPublicKey)}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button onClick={onLogout} className="logout-button">
|
<button onClick={onLogout} className="logout-button">
|
||||||
|
|||||||
@@ -1,12 +1,74 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
interface UserProfile {
|
||||||
|
name?: string
|
||||||
|
username?: string
|
||||||
|
nip05?: string
|
||||||
|
}
|
||||||
|
|
||||||
interface LoginProps {
|
interface LoginProps {
|
||||||
onLogin: (publicKey: string) => void
|
onLogin: (publicKey: string, profile: UserProfile) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Login: React.FC<LoginProps> = ({ onLogin }) => {
|
const Login: React.FC<LoginProps> = ({ onLogin }) => {
|
||||||
const [isConnecting, setIsConnecting] = useState(false)
|
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 () => {
|
const handleLogin = async () => {
|
||||||
try {
|
try {
|
||||||
setIsConnecting(true)
|
setIsConnecting(true)
|
||||||
@@ -20,7 +82,9 @@ const Login: React.FC<LoginProps> = ({ onLogin }) => {
|
|||||||
const publicKey = await window.nostr.getPublicKey()
|
const publicKey = await window.nostr.getPublicKey()
|
||||||
|
|
||||||
if (publicKey) {
|
if (publicKey) {
|
||||||
onLogin(publicKey)
|
// Fetch user profile from nostr
|
||||||
|
const profile = await fetchUserProfile(publicKey)
|
||||||
|
onLogin(publicKey, profile)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Failed to get public key')
|
throw new Error('Failed to get public key')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user