7 changed files with 309 additions and 108 deletions
@ -0,0 +1,107 @@ |
|||||||
|
<script lang="ts"> |
||||||
|
import { onMount } from 'svelte'; |
||||||
|
import { NostrClient } from '../services/nostr/nostr-client.js'; |
||||||
|
import { DEFAULT_NOSTR_RELAYS } from '../config.js'; |
||||||
|
import { KIND } from '../types/nostr.js'; |
||||||
|
import { nip19 } from 'nostr-tools'; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
pubkey: string; |
||||||
|
} |
||||||
|
|
||||||
|
let { pubkey }: Props = $props(); |
||||||
|
|
||||||
|
let userProfile = $state<{ name?: string; picture?: string } | null>(null); |
||||||
|
let loading = $state(true); |
||||||
|
|
||||||
|
const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS); |
||||||
|
|
||||||
|
onMount(async () => { |
||||||
|
await loadUserProfile(); |
||||||
|
}); |
||||||
|
|
||||||
|
async function loadUserProfile() { |
||||||
|
try { |
||||||
|
// Fetch user profile (kind 0 - metadata) |
||||||
|
const profileEvents = await nostrClient.fetchEvents([ |
||||||
|
{ |
||||||
|
kinds: [0], |
||||||
|
authors: [pubkey], |
||||||
|
limit: 1 |
||||||
|
} |
||||||
|
]); |
||||||
|
|
||||||
|
if (profileEvents.length > 0) { |
||||||
|
try { |
||||||
|
const profile = JSON.parse(profileEvents[0].content); |
||||||
|
userProfile = { |
||||||
|
name: profile.name, |
||||||
|
picture: profile.picture |
||||||
|
}; |
||||||
|
} catch { |
||||||
|
// Invalid JSON, ignore |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (err) { |
||||||
|
console.warn('Failed to load user profile:', err); |
||||||
|
} finally { |
||||||
|
loading = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function getShortNpub(): string { |
||||||
|
try { |
||||||
|
const npub = nip19.npubEncode(pubkey); |
||||||
|
return `${npub.slice(0, 8)}...${npub.slice(-4)}`; |
||||||
|
} catch { |
||||||
|
return `${pubkey.slice(0, 8)}...${pubkey.slice(-4)}`; |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<div class="user-badge"> |
||||||
|
{#if userProfile?.picture} |
||||||
|
<img src={userProfile.picture} alt="Profile" class="user-badge-avatar" /> |
||||||
|
{:else} |
||||||
|
<img src="/favicon.png" alt="Profile" class="user-badge-avatar user-badge-avatar-fallback" /> |
||||||
|
{/if} |
||||||
|
<span class="user-badge-name">{userProfile?.name || getShortNpub()}</span> |
||||||
|
</div> |
||||||
|
|
||||||
|
<style> |
||||||
|
.user-badge { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 0.5rem; |
||||||
|
padding: 0.25rem 0.75rem; |
||||||
|
border-radius: 1.5rem; |
||||||
|
background: var(--card-bg); |
||||||
|
border: 1px solid var(--border-color); |
||||||
|
transition: all 0.2s ease; |
||||||
|
} |
||||||
|
|
||||||
|
.user-badge:hover { |
||||||
|
border-color: var(--accent); |
||||||
|
background: var(--bg-secondary); |
||||||
|
} |
||||||
|
|
||||||
|
.user-badge-avatar { |
||||||
|
width: 24px; |
||||||
|
height: 24px; |
||||||
|
border-radius: 50%; |
||||||
|
object-fit: cover; |
||||||
|
flex-shrink: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.user-badge-avatar-fallback { |
||||||
|
filter: grayscale(100%); |
||||||
|
opacity: 0.7; |
||||||
|
} |
||||||
|
|
||||||
|
.user-badge-name { |
||||||
|
font-size: 0.875rem; |
||||||
|
color: var(--text-primary); |
||||||
|
font-weight: 500; |
||||||
|
white-space: nowrap; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -1,22 +1,67 @@ |
|||||||
/** |
/** |
||||||
* Pino logger service |
* Pino logger service |
||||||
* Provides structured logging with pino-pretty for development |
* Provides structured logging with pino-pretty for development |
||||||
|
* Browser-safe: falls back to console in browser environments |
||||||
*/ |
*/ |
||||||
|
|
||||||
import pino from 'pino'; |
function createConsoleLogger() { |
||||||
|
return { |
||||||
const logger = pino({ |
info: (...args: any[]) => console.log('[INFO]', ...args), |
||||||
level: process.env.LOG_LEVEL || 'info', |
error: (...args: any[]) => console.error('[ERROR]', ...args), |
||||||
...(process.env.NODE_ENV === 'development' && { |
warn: (...args: any[]) => console.warn('[WARN]', ...args), |
||||||
transport: { |
debug: (...args: any[]) => console.debug('[DEBUG]', ...args), |
||||||
target: 'pino-pretty', |
trace: (...args: any[]) => console.trace('[TRACE]', ...args), |
||||||
options: { |
fatal: (...args: any[]) => console.error('[FATAL]', ...args) |
||||||
colorize: true, |
}; |
||||||
translateTime: 'HH:MM:ss Z', |
} |
||||||
ignore: 'pid,hostname' |
|
||||||
} |
// Check if we're in a Node.js environment
|
||||||
|
const isNode = typeof process !== 'undefined' && process.versions?.node; |
||||||
|
|
||||||
|
let logger: any; |
||||||
|
|
||||||
|
if (isNode) { |
||||||
|
// Server-side: use pino
|
||||||
|
// Use dynamic import to avoid bundling for browser
|
||||||
|
const initPino = async () => { |
||||||
|
try { |
||||||
|
const pinoModule = await import('pino'); |
||||||
|
const pino = pinoModule.default; |
||||||
|
const logLevel = (typeof process !== 'undefined' && process.env?.LOG_LEVEL) || 'info'; |
||||||
|
const isDev = typeof process !== 'undefined' && process.env?.NODE_ENV === 'development'; |
||||||
|
|
||||||
|
return pino({ |
||||||
|
level: logLevel, |
||||||
|
...(isDev && { |
||||||
|
transport: { |
||||||
|
target: 'pino-pretty', |
||||||
|
options: { |
||||||
|
colorize: true, |
||||||
|
translateTime: 'HH:MM:ss Z', |
||||||
|
ignore: 'pid,hostname' |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
}); |
||||||
|
} catch { |
||||||
|
return createConsoleLogger(); |
||||||
} |
} |
||||||
}) |
}; |
||||||
}); |
|
||||||
|
// Initialize with console logger first
|
||||||
|
logger = createConsoleLogger(); |
||||||
|
|
||||||
|
// Upgrade to pino asynchronously (non-blocking)
|
||||||
|
initPino().then(pinoLogger => { |
||||||
|
// Replace the logger object
|
||||||
|
Object.setPrototypeOf(logger, pinoLogger); |
||||||
|
Object.assign(logger, pinoLogger); |
||||||
|
}).catch(() => { |
||||||
|
// Keep console logger if pino fails
|
||||||
|
}); |
||||||
|
} else { |
||||||
|
// Browser-side: use console with similar API
|
||||||
|
logger = createConsoleLogger(); |
||||||
|
} |
||||||
|
|
||||||
export default logger; |
export default logger; |
||||||
|
|||||||
Loading…
Reference in new issue