11 changed files with 368 additions and 76 deletions
@ -0,0 +1,113 @@
@@ -0,0 +1,113 @@
|
||||
<script lang="ts"> |
||||
import { Card, Img, Modal, Button, P } from "flowbite-svelte"; |
||||
import { onMount } from "svelte"; |
||||
import { userBadge } from "$lib/snippets/UserSnippets.svelte"; |
||||
import { type NostrProfile, toNpub } from "$lib/utils/nostrUtils.ts"; |
||||
import QrCode from "$components/util/QrCode.svelte"; |
||||
import CopyToClipboard from "$components/util/CopyToClipboard.svelte"; |
||||
import { bech32 } from 'bech32'; |
||||
import type { NDKEvent } from "@nostr-dev-kit/ndk"; |
||||
|
||||
const { event, profile } = $props<{ event: NDKEvent, profile: NostrProfile }>(); |
||||
|
||||
let lnModalOpen = $state(false); |
||||
let lnurl = $state<string | null>(null); |
||||
|
||||
onMount(async () => { |
||||
if (profile?.lud16) { |
||||
try { |
||||
// Convert LN address to LNURL |
||||
const [name, domain] = profile?.lud16.split('@'); |
||||
const url = `https://${domain}/.well-known/lnurlp/${name}`; |
||||
const words = bech32.toWords(new TextEncoder().encode(url)); |
||||
lnurl = bech32.encode('lnurl', words); |
||||
} catch { |
||||
console.log('Error converting LN address to LNURL'); |
||||
} |
||||
} |
||||
}); |
||||
</script> |
||||
|
||||
{#if profile} |
||||
<Card class="ArticleBox card-leather w-full max-w-2xl"> |
||||
<div class='space-y-4'> |
||||
{#if profile.banner} |
||||
<div class="ArticleBoxImage flex col justify-center"> |
||||
<Img src={profile.banner} class="rounded w-full max-h-72 object-cover" alt="Profile banner" onerror={(e) => { (e.target as HTMLImageElement).style.display = 'none';}} /> |
||||
</div> |
||||
{/if} |
||||
<div class='flex flex-row space-x-4 items-center'> |
||||
{#if profile.picture} |
||||
<img src={profile.picture} alt="Profile avatar" class="w-16 h-16 rounded-full border" onerror={(e) => { (e.target as HTMLImageElement).src = '/favicon.png'; }} /> |
||||
{/if} |
||||
{@render userBadge(toNpub(event.pubkey) as string, profile.displayName || profile.name || event.pubkey)} |
||||
</div> |
||||
<div> |
||||
<div class="mt-2 flex flex-col gap-4"> |
||||
<dl class="grid grid-cols-1 gap-y-2"> |
||||
{#if profile.name} |
||||
<div class="flex gap-2"> |
||||
<dt class="font-semibold min-w-[120px]">Name:</dt> |
||||
<dd>{profile.name}</dd> |
||||
</div> |
||||
{/if} |
||||
{#if profile.displayName} |
||||
<div class="flex gap-2"> |
||||
<dt class="font-semibold min-w-[120px]">Display Name:</dt> |
||||
<dd>{profile.displayName}</dd> |
||||
</div> |
||||
{/if} |
||||
{#if profile.about} |
||||
<div class="flex gap-2"> |
||||
<dt class="font-semibold min-w-[120px]">About:</dt> |
||||
<dd class="whitespace-pre-line">{profile.about}</dd> |
||||
</div> |
||||
{/if} |
||||
{#if profile.website} |
||||
<div class="flex gap-2"> |
||||
<dt class="font-semibold min-w-[120px]">Website:</dt> |
||||
<dd> |
||||
<a href={profile.website} target="_blank" class="underline text-primary-700 dark:text-primary-200">{profile.website}</a> |
||||
</dd> |
||||
</div> |
||||
{/if} |
||||
{#if profile.nip05} |
||||
<div class="flex gap-2"> |
||||
<dt class="font-semibold min-w-[120px]">NIP-05:</dt> |
||||
<dd>{profile.nip05}</dd> |
||||
</div> |
||||
{/if} |
||||
{#if profile.lud16} |
||||
<div class="flex items-center gap-2 mt-4"> |
||||
<dt class="font-semibold min-w-[120px]">Lightning Address:</dt> |
||||
<dd><Button class="btn-leather" color="primary" outline onclick={() => lnModalOpen = true}>{profile.lud16}</Button> </dd> |
||||
</div> |
||||
{/if} |
||||
</dl> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</Card> |
||||
|
||||
<Modal class='modal-leather' title='Lightning Address' bind:open={lnModalOpen} outsideclose size='sm'> |
||||
{#if profile.lud16} |
||||
<div> |
||||
<div class='flex flex-col items-center'> |
||||
{@render userBadge(toNpub(event.pubkey) as string, profile?.displayName || profile.name || event.pubkey)} |
||||
<P>{profile.lud16}</P> |
||||
</div> |
||||
<div class="flex flex-col items-center mt-3 space-y-4"> |
||||
<P>Scan the QR code or copy the address</P> |
||||
{#if lnurl} |
||||
<P style="overflow-wrap: anywhere"> |
||||
<CopyToClipboard icon={false} displayText={lnurl}></CopyToClipboard> |
||||
</P> |
||||
<QrCode value={lnurl} /> |
||||
{:else} |
||||
<P>Couldn't generate address.</P> |
||||
{/if} |
||||
</div> |
||||
</div> |
||||
{/if} |
||||
</Modal> |
||||
{/if} |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
<script lang="ts"> |
||||
import { onMount } from 'svelte'; |
||||
import QRCode from 'qrcode'; |
||||
|
||||
export let value: string; |
||||
let canvas: HTMLCanvasElement; |
||||
|
||||
async function renderQR() { |
||||
if (canvas && value) { |
||||
await QRCode.toCanvas(canvas, value, { width: 240 }); |
||||
} |
||||
} |
||||
|
||||
onMount(renderQR); |
||||
</script> |
||||
|
||||
<canvas class="qr-code" bind:this={canvas} /> |
||||
Loading…
Reference in new issue