aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2025-01-21 23:26:32 -0800
committerAdam Soltys <[email protected]>2025-01-21 23:26:32 -0800
commita70316e64bcc9bad7fb626056f9cc58d1858dff9 (patch)
treea185e9ee5210d5a83be08cfa15cd6343e2188b3e /src
parentdd86867462148daa88a3f3727434773d67dcab43 (diff)
downloadcoinos-ui-a70316e64bcc9bad7fb626056f9cc58d1858dff9.tar.gz
coinos-ui-a70316e64bcc9bad7fb626056f9cc58d1858dff9.zip
notes
Diffstat (limited to 'src')
-rw-r--r--src/components/Avatar.svelte4
-rw-r--r--src/components/Event.svelte125
-rw-r--r--src/components/Media.svelte32
-rw-r--r--src/lib/utils.ts22
-rw-r--r--src/locales/en.json4
-rw-r--r--src/routes/(app)/[username]/(profile)/+layout.svelte11
-rw-r--r--src/routes/(app)/[username]/(profile)/notes/+page.server.ts13
-rw-r--r--src/routes/(app)/[username]/(profile)/notes/+page.svelte35
-rw-r--r--src/routes/(app)/[username]/(profile)/notes/new/+page.svelte41
-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} &mdash; {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">&#x2022; {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);
}
}