diff options
| author | Adam Soltys <[email protected]> | 2025-01-21 23:26:32 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2025-01-21 23:26:32 -0800 |
| commit | a70316e64bcc9bad7fb626056f9cc58d1858dff9 (patch) | |
| tree | a185e9ee5210d5a83be08cfa15cd6343e2188b3e /src | |
| parent | dd86867462148daa88a3f3727434773d67dcab43 (diff) | |
| download | coinos-ui-a70316e64bcc9bad7fb626056f9cc58d1858dff9.tar.gz coinos-ui-a70316e64bcc9bad7fb626056f9cc58d1858dff9.zip | |
notes
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/Avatar.svelte | 4 | ||||
| -rw-r--r-- | src/components/Event.svelte | 125 | ||||
| -rw-r--r-- | src/components/Media.svelte | 32 | ||||
| -rw-r--r-- | src/lib/utils.ts | 22 | ||||
| -rw-r--r-- | src/locales/en.json | 4 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+layout.svelte | 11 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/notes/+page.server.ts | 13 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/notes/+page.svelte | 35 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/notes/new/+page.svelte | 41 | ||||
| -rw-r--r-- | src/routes/(app)/send/zap/[id]/[...amount]/+page.server.ts (renamed from src/routes/(app)/send/zap/[pubkey]/[...id]/+page.server.ts) | 22 | ||||
| -rw-r--r-- | src/routes/(app)/send/zap/[id]/[...amount]/+page.svelte (renamed from src/routes/(app)/send/zap/[pubkey]/[...id]/+page.svelte) | 6 |
11 files changed, 214 insertions, 101 deletions
diff --git a/src/components/Avatar.svelte b/src/components/Avatar.svelte index f37f3e14..09b6c037 100644 --- a/src/components/Avatar.svelte +++ b/src/components/Avatar.svelte @@ -18,7 +18,7 @@ user?.profile ? `${base}/${user.profile}.webp` : user?.picture, ); let fallback = $derived( - `${base}/punks/` + punk(user.pubkey || user?.id || "aa"), + `${base}/punks/` + punk(user?.pubkey || user?.id || "aa"), ); let tmp = $derived($avatar?.id && $avatar.id === user?.id && $avatar.src); let src = $derived(tmp || profile || fallback); @@ -31,7 +31,7 @@ <img {src} class="w-full h-full object-cover object-center overflow-hidden" - alt={user.username} + alt={user?.username} /> </div> {/snippet} diff --git a/src/components/Event.svelte b/src/components/Event.svelte index 6b974915..b94443d2 100644 --- a/src/components/Event.svelte +++ b/src/components/Event.svelte @@ -2,56 +2,77 @@ import Avatar from "$comp/Avatar.svelte"; import Media from "$comp/Media.svelte"; import { goto } from "$app/navigation"; + import { fail, ago, post } from "$lib/utils"; + import { sign } from "$lib/nostr"; + import Pin from "$comp/Pin.svelte"; + import { pin } from "$lib/store"; - let { event, minimal } = $props(); - let { author, parts, zaps, names } = $derived(event); - let go = () => goto(`/thread/${event.id}`); - console.log("AUTHOR", event.author); + let amount = 21; + let { event, minimal, heading = true } = $props(); + let { author, parts, zaps, names, created_at } = $derived(event); + let { id } = event; + + let zapping = $state(); + let zap = async () => { + zapping = true; + try { + const body = new FormData(); + + let request = await post("/post/zapRequest", { amount, id }); + let { pr: payreq } = await post("/post/zap", { + event: await sign(request), + }); + await post("/post/payments", { amount, payreq }); + } catch (e) { + fail(e.message); + } + }; </script> -<div class="space-y-5"> - <div class="flex gap-2 items-center"> - <Avatar user={author} size={16} /> - <a href={`/${author.pubkey}/notes`} class="contents"> - <div> - <div class="font-bold"> - {author.display} — {author.username} - </div> - {#if author.lud16} - <div class="flex items-center"> - <iconify-icon - noobserver - icon="ph:lightning-fill" - class="text-yellow-300" - ></iconify-icon> - {author.lud16} +<div class="space-y-2 border-b-8 dark:border-primary py-8"> + {#if heading} + <div class="flex"> + <a href={`/${author.pubkey}/notes`} class="contents"> + <div class="flex gap-2 items-center"> + <Avatar user={author} size={16} disabled={true} /> + <div> + <div> + <span class="font-bold">{author.display}</span> + <span class="text-secondary">@{author.username}</span> + <span class="text-secondary">• {ago(created_at)}</span> + </div> + {#if author.lud16} + <div class="flex items-center"> + <iconify-icon + noobserver + icon="ph:lightning-fill" + class="text-yellow-300" + ></iconify-icon> + {author.lud16} + </div> + {/if} </div> - {/if} - </div> - </a> - </div> - - <div class="text-2xl space-y-5 break-words" onclick={go}> - {#each parts as { type, value }, i} - {#if type === "text"} - {value} - {:else if type === "link" && !minimal} - <Media {value} /> - {:else if type.match(/^nostr:np(rofile|ub)$/)} - <a href={`/${value.pubkey}`} class="font-bold">@{names[value.pubkey]}</a - > - {/if} - {/each} - </div> -</div> + </div> + </a> + </div> + {/if} -{#if !minimal} - <a href={`/send/zap/${event.pubkey}/${event.id}`} class="btn btn-accent"> - <iconify-icon noobserver icon="ph:lightning-fill" class="text-yellow-300" - ></iconify-icon> - Zap + <a href={`/e/${id}`} class="contents"> + <div class="text-2xl space-y-2 break-words"> + {#each parts as { type, value }, i} + {#if type === "text"} + {value} + {:else if type === "link"} + <Media {value} {minimal} /> + {:else if type.match(/^nostr:np(rofile|ub)$/)} + <a href={`/${value.pubkey}`} class="font-bold" + >@{names[value.pubkey]}</a + > + {/if} + {/each} + </div> </a> -{/if} +</div> {#if zaps?.length} <div class="flex flex-wrap gap-2"> @@ -65,3 +86,19 @@ {/each} </div> {/if} + +{#if !minimal} + <div class="justify-center flex"> + <btn onclick={zap} class="btn btn-accent items-center"> + <div class="flex gap-1 items-center"> + <iconify-icon + noobserver + icon="ph:lightning-fill" + class="text-yellow-300" + width={32} + ></iconify-icon> + <div>Zap</div> + </div> + </btn> + </div> +{/if} diff --git a/src/components/Media.svelte b/src/components/Media.svelte index d24dd12d..aff4cf39 100644 --- a/src/components/Media.svelte +++ b/src/components/Media.svelte @@ -6,20 +6,22 @@ export const isAudio = (url: string) => url?.match(/^.*\.(ogg|mp3|wav)/gi); // eslint-disable-next-line @typescript-eslint/no-explicit-any - export let value: any; + let { value, minimal } = $props(); </script> -{#if !!isImage(value.url)} - <img src={value.url} alt={""} /> -{:else if isVideo(value.url)} - <!-- svelte-ignore a11y-media-has-caption --> - <video src={value.url} controls /> -{:else if isAudio(value.url)} - <audio src={value.url} controls> - <a href={value.url}>{value.url.replace(/https?:\/\/(www\.)?/, "")}</a> - </audio> -{:else} - <a href={value.url}> - {value.url.replace(/https?:\/\/(www\.)?/, "")} - </a> -{/if} +<div class="pt-2"> + {#if !!isImage(value.url)} + <img src={value.url} alt={""} class:w-48={minimal} /> + {:else if isVideo(value.url)} + <!-- svelte-ignore a11y-media-has-caption --> + <video src={value.url} controls /> + {:else if isAudio(value.url)} + <audio src={value.url} controls> + <a href={value.url}>{value.url.replace(/https?:\/\/(www\.)?/, "")}</a> + </audio> + {:else} + <a href={value.url}> + {value.url.replace(/https?:\/\/(www\.)?/, "")} + </a> + {/if} +</div> diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e2e19b05..2e1bb840 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,6 +4,12 @@ import { page } from "$app/stores"; import { PUBLIC_COINOS_NETWORK, PUBLIC_COINOS_URL } from "$env/static/public"; import { bytesToHex } from "@noble/hashes/utils"; import { toast } from "@zerodevx/svelte-toast"; +import { + differenceInDays, + differenceInHours, + differenceInMinutes, + differenceInSeconds, +} from "date-fns"; import { decrypt } from "nostr-tools/nip49"; import { get as getStore } from "svelte/store"; @@ -434,3 +440,19 @@ export const versions = { }[PUBLIC_COINOS_NETWORK]; export const str = (s) => s.toString(); + +export const ago = (t) => { + const now = new Date(); + const date = new Date(t * 1000); + const seconds = differenceInSeconds(now, date); + if (seconds < 60) return `${seconds}s`; + + const minutes = differenceInMinutes(now, date); + if (minutes < 60) return `${minutes}m`; + + const hours = differenceInHours(now, date); + if (hours < 24) return `${hours}h`; + + const days = differenceInDays(now, date); + return `${days}d`; +}; diff --git a/src/locales/en.json b/src/locales/en.json index 1bf17580..8b07832e 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1,4 +1,8 @@ { + "notes": { + "contents": "Note contents", + "notFound": "We didn't find any notes" + }, "accounts": { "showQR": "Show QR", "revealNwc": "Reveal connection string", diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte index d364a840..a219a487 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.svelte +++ b/src/routes/(app)/[username]/(profile)/+layout.svelte @@ -118,11 +118,16 @@ > <button type="button" - onclick={toggleDetails} class="flex text-3xl font-bold text-center mx-auto justify-center gap-2" > - <div class="my-auto break-words">{display || username}</div> - <iconify-icon noobserver icon="ph:qr-code-bold" width="32"></iconify-icon> + <div class="flex gap-1" onclick={toggleDetails}> + <div class="my-auto break-words">{display || username}</div> + <iconify-icon noobserver icon="ph:qr-code-bold" width="32" + ></iconify-icon> + </div> + <a href={`/${n}/notes`}> + <iconify-icon noobserver icon="ph:note-bold" width="32"></iconify-icon> + </a> </button> {#if subject.about} diff --git a/src/routes/(app)/[username]/(profile)/notes/+page.server.ts b/src/routes/(app)/[username]/(profile)/notes/+page.server.ts new file mode 100644 index 00000000..b7186bc0 --- /dev/null +++ b/src/routes/(app)/[username]/(profile)/notes/+page.server.ts @@ -0,0 +1,13 @@ +import { get } from "$lib/utils"; +import { error } from "@sveltejs/kit"; + +export async function load({ parent }) { + try { + const { subject } = await parent(); + const events = await get(`/${subject.pubkey}/events`); + return { events }; + } catch (e) { + console.log(e); + error(500, { message: "Failed to get events" }); + } +} diff --git a/src/routes/(app)/[username]/(profile)/notes/+page.svelte b/src/routes/(app)/[username]/(profile)/notes/+page.svelte index 0591a881..e4261483 100644 --- a/src/routes/(app)/[username]/(profile)/notes/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/notes/+page.svelte @@ -1,39 +1,24 @@ <script lang="ts"> + import { t } from "$lib/translations"; import Event from "$comp/Event.svelte"; import { get, post } from "$lib/utils"; import { onMount } from "svelte"; - import { Relay } from "nostr-tools/relay"; import { ignore } from "$lib/store"; let { data } = $props(); - let { subject } = $derived(data); - let events = $state([]); - onMount(async () => { - const relay = await Relay.connect("wss://relay.primal.net"); - const subscription = relay.subscribe( - [{ kinds: [1], authors: [subject.pubkey], limit: 20 }], - { - async onevent(event) { - // console.log(event); - const { parts, names } = await post("/post/parseEvent", { event }); - event.author = await get(`/api/users/${event.pubkey}`); - event.parts = parts; - event.names = names; - let { content, pubkey } = event; - events.push(event); - if (events.length > 20) events.shift(); - }, - }, - ); - }); + let { events, subject, user } = $derived(data); </script> <div class="container px-4 max-w-xl mx-auto space-y-5"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> - Events - </h1> - + {#if user?.id === subject.id} + <a href={`/${user.username}/notes/new`} class="btn"> + <iconify-icon noobserver icon="ph:note-bold" width="32"></iconify-icon> + <div>Post a note</div> + </a> + {/if} {#each events as event} <Event {event} minimal={true} /> + {:else} + <div class="text-2xl">{$t("notes.notFound")}</div> {/each} </div> diff --git a/src/routes/(app)/[username]/(profile)/notes/new/+page.svelte b/src/routes/(app)/[username]/(profile)/notes/new/+page.svelte new file mode 100644 index 00000000..8957034d --- /dev/null +++ b/src/routes/(app)/[username]/(profile)/notes/new/+page.svelte @@ -0,0 +1,41 @@ +<script> + import { goto } from "$app/navigation"; + import { send, sign } from "$lib/nostr"; + import { t } from "$lib/translations"; + import { focus } from "$lib/utils"; + + let { data } = $props(); + let { user } = $derived(data); + let { pubkey } = $derived(user); + let content = $state(); + + let submit = async (e) => { + try { + e.preventDefault(); + + let event = { + kind: 1, + content, + created_at: Math.round(Date.now() / 1000), + tags: [], + }; + + event = await sign(event); + await send(event); + + goto(`/${user.username}/notes`); + } catch (e) { + console.log(e); + } + }; +</script> + +<div class="container px-4 max-w-xl mx-auto space-y-5"> + <form method="POST" class="space-y-2 text-xl" onsubmit={submit}> + <textarea use:focus bind:value={content} placeholder={$t("notes.contents")} + ></textarea> + <button type="submit" class="btn btn-accent"> + {$t("payments.submit")}</button + > + </form> +</div> diff --git a/src/routes/(app)/send/zap/[pubkey]/[...id]/+page.server.ts b/src/routes/(app)/send/zap/[id]/[...amount]/+page.server.ts index 1dd80d20..2219c370 100644 --- a/src/routes/(app)/send/zap/[pubkey]/[...id]/+page.server.ts +++ b/src/routes/(app)/send/zap/[id]/[...amount]/+page.server.ts @@ -2,26 +2,26 @@ import getRates from "$lib/rates"; import { auth, fd, post } from "$lib/utils"; import { fail, redirect } from "@sveltejs/kit"; -export async function load({ params, parent }) { +export async function load({ cookies, params, parent }) { const { user } = await parent(); - const data = { ...params }; + const { amount, id } = params; const rates = await getRates(); - data.rate = rates[user.currency]; - return data; + let request; + if (amount) + request = await post("/zapRequest", { amount, id }, auth(cookies)); + + const rate = rates[user.currency]; + return { amount, id, rates, rate, request }; } export const actions = { setAmount: async ({ cookies, params, request }) => { const data = await fd(request); - const { id, pubkey } = params; + const { id } = params; const { amount } = data; - data.request = await post( - "/zapRequest", - { id, amount, pubkey }, - auth(cookies), - ); + data.request = await post("/zapRequest", { amount, id }, auth(cookies)); return data; }, @@ -32,7 +32,9 @@ export const actions = { const body = await fd(request); p = await post("/payments", body, auth(cookies)); + console.log("P", p); } catch (_) { + console.log("WHAT", _); return fail(400, { message: "payments.failedToRoute" }); } diff --git a/src/routes/(app)/send/zap/[pubkey]/[...id]/+page.svelte b/src/routes/(app)/send/zap/[id]/[...amount]/+page.svelte index eb0be4c8..e6dd1384 100644 --- a/src/routes/(app)/send/zap/[pubkey]/[...id]/+page.svelte +++ b/src/routes/(app)/send/zap/[id]/[...amount]/+page.svelte @@ -1,4 +1,5 @@ <script> + import { tick } from "svelte"; import { goto } from "$app/navigation"; import { untrack } from "svelte"; import { t } from "$lib/translations"; @@ -14,14 +15,14 @@ let { data, form } = $props(); let { id, request, user } = $derived({ ...data, ...form }); - let a = $state(); + let a = $state(data.amount); let { currency } = $derived(user); let locale = loc(user); let event = $state(); $effect(() => ($rate ||= data.rate)); $effect(async () => { - if (form) { + if (form || request) { try { loading = false; event = await sign(request); @@ -29,6 +30,7 @@ await post("/post/payments", { amount, payreq, pin: $pin }); goto(`/e/${id}`); } catch (e) { + console.log(e); fail(e.message); } } |
