aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/AppHeader.svelte1
-rw-r--r--src/locales/en.json1
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.server.js35
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte184
-rw-r--r--src/routes/(app)/[username]/messages/+page.server.js38
-rw-r--r--src/routes/(app)/[username]/messages/+page.svelte226
-rw-r--r--src/routes/(app)/[username]/messages/[recipient]/+page.server.js25
-rw-r--r--src/routes/(app)/[username]/messages/[recipient]/+page.svelte191
-rw-r--r--src/routes/(app)/send/[...text]/+page.svelte46
9 files changed, 329 insertions, 418 deletions
diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte
index 57892b18..a2103cf5 100644
--- a/src/components/AppHeader.svelte
+++ b/src/components/AppHeader.svelte
@@ -23,6 +23,7 @@
},
{ key: "nav.support", icon: "support", href: `/support` },
{ key: "nav.map", icon: "map", href: `/${user.username}/map` },
+ { key: "nav.messages", icon: "message", href: `/${user.username}/messages` },
{ key: "nav.merch", icon: "shop", href: `https://coinosmerch.com` },
{ key: "nav.signOut", icon: "logout", href: `/logout` },
];
diff --git a/src/locales/en.json b/src/locales/en.json
index 84f58ced..c4a272b6 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -154,6 +154,7 @@
"subheader": "Latest Updates"
},
"user": {
+ "noMessages": "You have not sent or received any messages yet",
"profilePage": "Profile page",
"nostrPubkey": "Nostr pubkey",
"requestPayment": "Request Payment",
diff --git a/src/routes/(app)/[username]/(profile)/+page.server.js b/src/routes/(app)/[username]/(profile)/+page.server.js
deleted file mode 100644
index 3110ec40..00000000
--- a/src/routes/(app)/[username]/(profile)/+page.server.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { auth, get } from "$lib/utils";
-
-export async function load({ cookies, params, parent, url }) {
- let { user, subject } = await parent();
- let { pubkey } = subject;
- let { since = 0 } = params;
-
- let messages, invoices, sent, received, notes;
- messages = invoices = sent = received = notes = [];
-
- if (pubkey) {
- if (user) {
- try {
- messages = await get(`/${user.pubkey}/${since}/messages`);
- messages = messages.sort((a, b) => b.created_at - a.created_at);
- } catch (e) {
- console.log(`failed to fetch nostr messages`, e);
- }
-
- ({ invoices, sent, received } = await get("/requests", auth(cookies)));
- }
-
- try {
- notes = await get(`/${pubkey}/notes`);
- } catch (e) {
- console.log(`failed to fetch nostr notes for ${pubkey}`, e);
- }
-
- notes.map((e) => {
- e.seen = e.created_at;
- });
- }
-
- return { invoices, messages, notes, sent, received };
-}
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte
index 9dd968b6..420cc5fa 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -1,70 +1,13 @@
<script>
import { onMount } from "svelte";
import Icon from "$comp/Icon.svelte";
- import Avatar from "$comp/Avatar.svelte";
import Balance from "$comp/Balance.svelte";
- import Feed from "$comp/Feed.svelte";
import { t } from "$lib/translations";
- import { ease, f, sat, sats } from "$lib/utils";
- import { sign, send, encrypt, decrypt } from "$lib/nostr";
- import { event as e, password } from "$lib/store";
- import { browser } from "$app/environment";
export let data;
- let { messages, notes, invoices, sent, received, subject, user, rates } =
- data;
+ let { subject, user, rates } = data;
$: refresh(data);
- let refresh = (d) =>
- ({ messages, notes, invoices, sent, received, subject, user, rates } = d);
-
- let keys = new Set();
- let latest = [];
- let ready;
-
- e.subscribe(async (event) => {
- if (!(browser && event && ready)) return;
- if (
- event.recipient?.id === user.id &&
- !~latest.findIndex((m) => m.id === event.id)
- ) {
- event.content = await decrypt({ event, user });
-
- let i = latest.findIndex((m) => m.pubkey === event.pubkey);
- let popped;
- if (~i) popped = latest.splice(i, 1);
- else popped = latest.pop();
-
- latest.unshift(event);
- latest = latest;
- }
- });
-
- $: initialize($password);
- let initialize = async (p) => {
- let i = 0;
- ready = false;
- if (!(browser && user)) return;
- while (i < messages.length) {
- let event = messages[i];
- i++;
-
- if (!(event.author && event.recipient)) continue;
-
- let k =
- event.author.id === user.id
- ? event.recipient?.pubkey
- : event.author.pubkey;
-
- if (!keys.has(k)) {
- keys.add(k);
- event.content = await decrypt({ event, user });
- if (event.content) latest.push(event);
- }
- }
-
- latest = latest;
- ready = true;
- };
+ let refresh = (d) => ({ subject, user, rates } = d);
</script>
<div class="space-y-5">
@@ -82,10 +25,10 @@
</div>
{/if}
- <div class="flex flex-wrap sm:flex-nowrap gap-2 justify-center w-full">
+ <div class="flex flex-wrap sm:flex-nowrap gap-3 justify-center w-full text-xl">
<a href={`/send`} class="w-full">
<button
- class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full"
+ class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-full bg-primary"
>
<div class="mx-auto flex">
<Icon icon="send" style="my-auto h-6 mr-2" />
@@ -96,7 +39,7 @@
<a href={`/${user.username}/receive`} class="w-full">
<button
- class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full"
+ class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-full bg-primary"
>
<div class="mx-auto flex">
<Icon icon="numpad" style="my-auto h-6 mr-2" />
@@ -104,124 +47,7 @@
</div>
</button>
</a>
-
- {#if user.eligible}
- <a href={`/buy`}>
- <button
- class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60"
- >
- <div class="mx-auto flex">
- <!-- <Icon icon="plus" style="my-auto h-6 mr-2" /> -->
-
- <img
- src="/images/bitcoin.svg"
- class="w-8 border-4 border-transparent mr-2"
- alt="Bitcoin"
- />
- <div class="my-auto mt-1">Buy Bitcoin</div>
- </div>
- </button>
- </a>
- {/if}
</div>
</div>
{/if}
-
- {#if invoices.length}
- <div>
- <h1 class="text-secondary text-xl">Invoices</h1>
- {#each invoices as { amount, currency, rate, request: { requester, recipient }, hash }}
- {@const requested = user.username === requester?.username}
- <a
- href={`/${recipient.username}/invoice/${hash}` +
- (requested ? "/tip" : "")}
- >
- <div class="border-b p-2 last:border-b-0 hover:bg-gray-100">
- <div class="flex gap-2">
- <div>
- {#if requested}
- <div class="flex">
- <Avatar user={recipient} size={20} />
- <div class="my-auto text-left">
- <p class="text-secondary">{$t("payments.invoiceFrom")}</p>
- <p class="ml-1 text-lg break-words">
- {recipient.username}
- </p>
- </div>
- </div>
- {:else}
- <div class="flex">
- <Avatar user={requester} size={20} />
- <div class="my-auto text-left">
- <p class="text-secondary">{$t("payments.awaiting")}</p>
- <p class="ml-1 text-lg break-words">
- {requester.username}
- </p>
- </div>
- </div>
- {/if}
- </div>
- <div class="whitespace-nowrap my-auto ml-auto flex gap-2">
- <div class="font-bold">
- {f(amount * (rate / sats), currency)}
- </div>
-
- <div class="text-secondary">{sat(amount)}</div>
- </div>
- </div>
- </div>
- </a>
- {/each}
- </div>
- {/if}
- {#if received.length}
- <div>
- <h1 class="text-secondary text-xl">Requests</h1>
-
- {#each received as { id, invoice, memo, requester: r }}
- <a href={`/${user.username}/receive/${id}`}>
- <div class="border-b p-2 last:border-b-0 hover:bg-gray-100">
- <div class="flex">
- <div>
- <div class="flex">
- <Avatar user={r} size={20} />
- <div class="my-auto text-left">
- <p class="ml-1 text-lg break-words">{r.username}</p>
- <p class="ml-1 text-secondary">{memo}</p>
- </div>
- </div>
- </div>
- <div class="whitespace-nowrap my-auto ml-auto flex gap-2">
- <button
- class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full"
- >Invoice</button
- >
- </div>
- </div>
- </div>
- </a>
- {/each}
- </div>
- {/if}
-
- {#if latest.length && user?.id === subject.id}
- <div class="relative">
- {#each latest as { content, pubkey, author, recipient }}
- {@const user = author.id === user.id ? recipient : author}
- <a href={`/${user.username}/messages`}>
- <div class="flex hover:bg-gray-100 p-4 rounded-2xl">
- <div class="my-auto">
- <Avatar {user} size={"20"} disabled={true} />
- </div>
- <div class="my-auto truncate">
- <div class="my-auto ml-1 text-lg font-bold">{user.username}</div>
- <div class="my-auto ml-1 text-secondary text-lg truncate">
- {content}
- </div>
- </div>
- </div>
- </a>
- {/each}
- </div>
- {/if}
</div>
diff --git a/src/routes/(app)/[username]/messages/+page.server.js b/src/routes/(app)/[username]/messages/+page.server.js
index f0148be7..eeb883ee 100644
--- a/src/routes/(app)/[username]/messages/+page.server.js
+++ b/src/routes/(app)/[username]/messages/+page.server.js
@@ -1,25 +1,33 @@
-import { get } from "$lib/utils";
+import { auth, get } from "$lib/utils";
-export async function load({ params, parent, url }) {
+export async function load({ cookies, params, parent, url }) {
let { user, subject } = await parent();
+ let { pubkey } = subject;
let { since = 0 } = params;
- let messages = [];
+ let messages = [],
+ notes = [];
- try {
- messages = await get(`/${user.pubkey}/${since}/messages`);
+ if (pubkey) {
+ if (user) {
+ try {
+ messages = await get(`/${user.pubkey}/${since}/messages`);
+ messages = messages.sort((a, b) => b.created_at - a.created_at);
+ } catch (e) {
+ console.log(`failed to fetch nostr messages`, e);
+ }
+ }
- messages = messages
- .filter(
- (m) => m.recipient?.id === subject.id || m.author?.id === subject.id
- )
- .sort((a, b) => a.created_at - b.created_at);
+ try {
+ notes = await get(`/${pubkey}/notes`);
+ } catch (e) {
+ console.log(`failed to fetch nostr notes for ${pubkey}`, e);
+ }
- if (user.id === subject.id)
- messages = messages.filter((m) => m.recipient.id === m.author.id);
- } catch (e) {
- console.log(`failed to fetch nostr messages`, e);
+ notes.map((e) => {
+ e.seen = e.created_at;
+ });
}
- return { messages, user };
+ return { messages, notes }
}
diff --git a/src/routes/(app)/[username]/messages/+page.svelte b/src/routes/(app)/[username]/messages/+page.svelte
index 48855a26..4738ea2c 100644
--- a/src/routes/(app)/[username]/messages/+page.svelte
+++ b/src/routes/(app)/[username]/messages/+page.svelte
@@ -1,182 +1,92 @@
<script>
- import { cubicInOut } from "svelte/easing";
- import { browser } from "$app/environment";
import { t } from "$lib/translations";
- import { format } from "date-fns";
- import { scale, fade, fly } from "svelte/transition";
- import { enhance } from "$app/forms";
import Avatar from "$comp/Avatar.svelte";
- import Icon from "$comp/Icon.svelte";
- import { back, fail, focus } from "$lib/utils";
- import { sign, send, encrypt, decrypt } from "$lib/nostr";
import { event as e, password } from "$lib/store";
- import { tick, onMount } from "svelte";
- import { getEventHash } from "nostr-tools";
+ import { browser } from "$app/environment";
+ import { decrypt } from "$lib/nostr";
export let data;
- function fadeScale(
- node,
- { delay = 0, duration = 200, easing = (x) => x, baseScale = 0 }
- ) {
- const o = +getComputedStyle(node).opacity;
- const m = getComputedStyle(node).transform.match(/scale\(([0-9.]+)\)/);
- const s = m ? m[1] : 1;
- const is = 1 - baseScale;
-
- return {
- delay,
- duration,
- css: (t) => {
- const eased = easing(t);
- return `opacity: ${eased * o}; transform: scale(${
- eased * s * is + baseScale
- })`;
- },
- };
- }
+ let { messages, notes, invoices, sent, received, subject, user, rates } =
+ data;
+ $: refresh(data);
+ let refresh = (d) =>
+ ({ messages, notes, invoices, sent, received, subject, user, rates } = d);
- let { messages, subject, user } = data;
- let input, pane;
+ let keys = new Set();
+ let latest = [];
+ let ready;
- $: initialize($password);
- let initialize = async (p) => {
- await Promise.all(
- messages.map(
- async (event) => (event.message = await decrypt({ event, user }))
- )
- );
+ e.subscribe(async (event) => {
+ if (!(browser && event && ready)) return;
+ if (
+ event.recipient?.id === user.id &&
+ !~latest.findIndex((m) => m.id === event.id)
+ ) {
+ event.content = await decrypt({ event, user });
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
- };
+ let i = latest.findIndex((m) => m.pubkey === event.pubkey);
+ let popped;
+ if (~i) popped = latest.splice(i, 1);
+ else popped = latest.pop();
- e.subscribe(async (event) => {
- let found = ~messages.findIndex((m) => m.id === event?.id);
- if (event?.recipient.id === user.id && !found) {
- event.message = await decrypt({ event, user });
- messages.push(event);
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ latest.unshift(event);
+ latest = latest;
}
});
- let sent, submitting, message;
- let submit = async () => {
- submitting = true;
-
- let event = {
- pubkey: user.pubkey,
- created_at: Math.floor(Date.now() / 1000),
- kind: 4,
- tags: [["p", subject.pubkey]],
- };
-
- event.message = message;
-
- message = "";
-
- event.author = user;
- event.recipient = subject;
+ $: initialize($password);
+ let initialize = async (p) => {
+ let i = 0;
+ ready = false;
+ if (!(browser && user)) return;
+ while (i < messages.length) {
+ let event = messages[i];
+ i++;
- messages.push(event);
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ if (!(event.author && event.recipient)) continue;
- try {
- event.content = await encrypt({
- message: event.message,
- recipient: subject.pubkey,
- user,
- });
- event.id = getEventHash(event);
- await sign({ event, user });
- await send(event);
+ let k =
+ event.author.id === user.id
+ ? event.recipient?.pubkey
+ : event.author.pubkey;
- sent = true;
- } catch (e) {
- console.log(e);
- fail("Failed to send message");
+ if (!keys.has(k)) {
+ keys.add(k);
+ event.content = await decrypt({ event, user });
+ if (event.content) latest.push(event);
+ }
}
- message = "";
- submitting = false;
- };
-
- let keydown = (e) => {
- if (e.key === "Enter") {
- e.preventDefault();
- submit();
- }
+ latest = latest;
+ ready = true;
};
</script>
-<div class="container max-w-xl mx-auto px-4 space-y-5">
- <div
- class="h-[50vh] max-h-[50vh] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8"
- bind:this={pane}
- >
- {#each messages as { id, author, message, created_at, pubkey }}
- {@const ours = pubkey === user.pubkey}
- {@const theirs = !ours}
-
- {#if message}
- <div
- class="flex gap-2"
- class:flex-row-reverse={theirs}
- class:justify-end={theirs}
- in:fadeScale={{
- easing: cubicInOut,
- baseScale: 0.5,
- }}
- >
- <div
- class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg"
- class:ours
- class:theirs
- :key={id}
- >
- {message}
+<div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
+ <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10">
+ {$t("user.messages")}
+ </h1>
+ {#if latest.length && user?.id === subject.id}
+ <div class="relative">
+ {#each latest as { content, pubkey, author, recipient }}
+ {@const user = author.id === user.id ? recipient : author}
+ <a href={`/${subject.username}/messages/${user.username}`}>
+ <div class="flex hover:bg-gray-100 p-4 rounded-2xl">
+ <div class="my-auto">
+ <Avatar {user} size={"20"} disabled={true} />
+ </div>
+ <div class="my-auto truncate">
+ <div class="my-auto ml-1 text-lg font-bold">{user.username}</div>
+ <div class="my-auto ml-1 text-secondary text-lg truncate">
+ {content}
+ </div>
+ </div>
</div>
- <div class="mt-auto">
- <Avatar user={author} size={"12"} />
- </div>
- </div>
- <div class="text-sm text-gray-400 mb-6" class:text-right={ours}>
- {format(new Date(created_at * 1000), "MMM d")},
- {format(new Date(created_at * 1000), "h:mm aa")}
- </div>
- {/if}
- {/each}
- </div>
-
- <form
- method="POST"
- class="w-full border rounded-xl outline-none gap-4 flex p-0 pr-2"
- on:submit|preventDefault={submit}
- >
- <input type="hidden" name="requester_id" value={user.id} />
- <input type="hidden" name="recipient" value={subject.username} />
-
- <div
- use:focus
- contenteditable
- class="grow break-all py-4 outline-none mt-0 pl-4"
- bind:innerHTML={message}
- on:keydown={keydown}
- />
- <button type="submit" class="my-auto shrink-0">
- <Icon icon="send" style="w-8" />
- </button>
- </form>
+ </a>
+ {/each}
+ </div>
+ {:else}
+ <div class="text-center">{$t("user.noMessages")}</div>
+ {/if}
</div>
-
-<style>
- .ours {
- @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto;
- }
-
- .theirs {
- @apply bg-gray-100 text-black rounded-bl-none;
- }
-</style>
diff --git a/src/routes/(app)/[username]/messages/[recipient]/+page.server.js b/src/routes/(app)/[username]/messages/[recipient]/+page.server.js
new file mode 100644
index 00000000..f0148be7
--- /dev/null
+++ b/src/routes/(app)/[username]/messages/[recipient]/+page.server.js
@@ -0,0 +1,25 @@
+import { get } from "$lib/utils";
+
+export async function load({ params, parent, url }) {
+ let { user, subject } = await parent();
+ let { since = 0 } = params;
+
+ let messages = [];
+
+ try {
+ messages = await get(`/${user.pubkey}/${since}/messages`);
+
+ messages = messages
+ .filter(
+ (m) => m.recipient?.id === subject.id || m.author?.id === subject.id
+ )
+ .sort((a, b) => a.created_at - b.created_at);
+
+ if (user.id === subject.id)
+ messages = messages.filter((m) => m.recipient.id === m.author.id);
+ } catch (e) {
+ console.log(`failed to fetch nostr messages`, e);
+ }
+
+ return { messages, user };
+}
diff --git a/src/routes/(app)/[username]/messages/[recipient]/+page.svelte b/src/routes/(app)/[username]/messages/[recipient]/+page.svelte
new file mode 100644
index 00000000..dfff6ed2
--- /dev/null
+++ b/src/routes/(app)/[username]/messages/[recipient]/+page.svelte
@@ -0,0 +1,191 @@
+<script>
+ import { cubicInOut } from "svelte/easing";
+ import { browser } from "$app/environment";
+ import { t } from "$lib/translations";
+ import { format } from "date-fns";
+ import { scale, fade, fly } from "svelte/transition";
+ import { enhance } from "$app/forms";
+ import Avatar from "$comp/Avatar.svelte";
+ import Icon from "$comp/Icon.svelte";
+ import { back, fail, focus } from "$lib/utils";
+ import { sign, send, encrypt, decrypt } from "$lib/nostr";
+ import { event as e, password } from "$lib/store";
+ import { tick, onMount } from "svelte";
+ import { getEventHash } from "nostr-tools";
+
+ export let data;
+
+ function fadeScale(
+ node,
+ { delay = 0, duration = 200, easing = (x) => x, baseScale = 0 }
+ ) {
+ const o = +getComputedStyle(node).opacity;
+ const m = getComputedStyle(node).transform.match(/scale\(([0-9.]+)\)/);
+ const s = m ? m[1] : 1;
+ const is = 1 - baseScale;
+
+ return {
+ delay,
+ duration,
+ css: (t) => {
+ const eased = easing(t);
+ return `opacity: ${eased * o}; transform: scale(${
+ eased * s * is + baseScale
+ })`;
+ },
+ };
+ }
+
+ let { messages, subject, user } = data;
+ let input, pane;
+
+ $: initialize($password);
+ let initialize = async (p) => {
+ await Promise.all(
+ messages.map(
+ async (event) => (event.message = await decrypt({ event, user }))
+ )
+ );
+
+ messages = messages;
+ tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ };
+
+ e.subscribe(async (event) => {
+ let found = ~messages.findIndex((m) => m.id === event?.id);
+ if (event?.recipient.id === user.id && !found) {
+ event.message = await decrypt({ event, user });
+ messages.push(event);
+ messages = messages;
+ tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ }
+ });
+
+ let sent, submitting, message;
+ let submit = async () => {
+ submitting = true;
+
+ let event = {
+ pubkey: user.pubkey,
+ created_at: Math.floor(Date.now() / 1000),
+ kind: 4,
+ tags: [["p", subject.pubkey]],
+ };
+
+ event.message = message;
+
+ message = "";
+
+ event.author = user;
+ event.recipient = subject;
+
+ messages.push(event);
+ messages = messages;
+ tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+
+ try {
+ event.content = await encrypt({
+ message: event.message,
+ recipient: subject.pubkey,
+ user,
+ });
+ event.id = getEventHash(event);
+ await sign({ event, user });
+ await send(event);
+
+ sent = true;
+ } catch (e) {
+ console.log(e);
+ fail("Failed to send message");
+ }
+
+ message = "";
+ submitting = false;
+ };
+
+ let keydown = (e) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ submit();
+ }
+ };
+</script>
+
+<div class="container max-w-xl mx-auto px-4 space-y-5">
+ <button
+ type="button"
+ class="hover:opacity-80"
+ data-sveltekit-preload-data="false"
+ on:click={back}
+ >
+ <Icon icon="arrow-left" style="w-10" />
+ </button>
+
+ <div
+ class="h-[50vh] max-h-[50vh] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8"
+ bind:this={pane}
+ >
+ {#each messages as { id, author, message, created_at, pubkey }}
+ {@const ours = pubkey === user.pubkey}
+ {@const theirs = !ours}
+
+ {#if message}
+ <div
+ class="flex gap-2"
+ class:flex-row-reverse={theirs}
+ class:justify-end={theirs}
+ in:fadeScale={{
+ easing: cubicInOut,
+ baseScale: 0.5,
+ }}
+ >
+ <div
+ class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg"
+ class:ours
+ class:theirs
+ :key={id}
+ >
+ {message}
+ </div>
+ <div class="mt-auto">
+ <Avatar user={author} size={"12"} />
+ </div>
+ </div>
+ <div class="text-sm text-gray-400 mb-6" class:text-right={ours}>
+ {format(new Date(created_at * 1000), "MMM d")},
+ {format(new Date(created_at * 1000), "h:mm aa")}
+ </div>
+ {/if}
+ {/each}
+ </div>
+
+ <form
+ method="POST"
+ class="w-full border rounded-xl outline-none gap-4 flex p-0 pr-2"
+ on:submit|preventDefault={submit}
+ >
+ <input type="hidden" name="requester_id" value={user.id} />
+ <input type="hidden" name="recipient" value={subject.username} />
+
+ <div
+ use:focus
+ contenteditable
+ class="grow break-all py-4 outline-none mt-0 pl-4"
+ bind:innerHTML={message}
+ on:keydown={keydown}
+ />
+ <button type="submit" class="my-auto shrink-0">
+ <Icon icon="send" style="w-8" />
+ </button>
+ </form>
+</div>
+
+<style>
+ .ours {
+ @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto;
+ }
+
+ .theirs {
+ @apply bg-gray-100 text-black rounded-bl-none;
+ }
+</style>
diff --git a/src/routes/(app)/send/[...text]/+page.svelte b/src/routes/(app)/send/[...text]/+page.svelte
index 489650a5..e7490d63 100644
--- a/src/routes/(app)/send/[...text]/+page.svelte
+++ b/src/routes/(app)/send/[...text]/+page.svelte
@@ -20,7 +20,6 @@
let el, text;
-
let keypress = (e) => e.key === "Enter" && (e.preventDefault() || el.click());
let paste = async () => {
@@ -36,7 +35,7 @@
{$t("payments.send")}
</h1>
- <form method="POST" use:enhance>
+ <form method="POST" use:enhance class="space-y-2 text-xl">
{#if form?.error}
<div class="mb-5">
<div class="text-red-600">
@@ -49,31 +48,18 @@
<textarea
use:focus
name="text"
- placeholder={$t("user.send.placeholder")}
+ placeholder={$t("user.send.placeholder")}
on:keypress={keypress}
- class="w-full p-4 border rounded-xl h-48"
+ class="w-full p-4 border rounded-xl h-48 text-xl"
bind:value={text}
/>
</div>
- <div class="grid grid-cols-2 md:grid-cols-4 gap-2">
- <a
- href={`/qr/${encodeURIComponent(text)}`}
- class:invisible={!text?.length}
- >
- <button
- type="button"
- class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1 w-full"
- >
- <Icon icon="qr" style="mr-2 w-6 my-auto invert" />
- <div class="my-auto">{$t("user.send.makeQR")}</div>
- </button>
- </a>
-
+ <div class="flex gap-2">
<a href="/scan" class="w-full">
<button
type="button"
- class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1 w-full"
+ class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center"
>
<Icon icon="camera" style="mr-2 w-6 my-auto" />
<div class="my-auto">{$t("user.send.scan")}</div>
@@ -82,28 +68,26 @@
<button
type="button"
- class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1"
+ class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center"
on:click={paste}
>
<Icon icon="paste" style="mr-2 w-6 my-auto" />
<div class="my-auto">{$t("user.send.paste")}</div>
</button>
-
- <button
- bind:this={el}
- type="submit"
- class="{!text
- ? 'opacity-50'
- : 'opacity-100 hover:opacity-80'} bg-black text-white border rounded-full px-6 py-2 font-bold"
- >
- {$t("user.send.next")}
- </button>
</div>
+
+ <button
+ bind:this={el}
+ type="submit"
+ class="bg-black text-white border rounded-2xl px-6 py-5 w-full font-bold"
+ >
+ {$t("user.send.next")}
+ </button>
</form>
{#if contacts.length}
<div class="space-y-5">
- <h1 class="px-3 md:px-0 text-xl font-semibold mt-10">
+ <h1 class="px-3 md:px-0 text-2xl font-semibold mt-10">
{$t("user.send.contacts")}
</h1>
<div>