aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/Balance.svelte2
-rw-r--r--src/lib/nostr.js3
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.server.js23
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte191
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/+page.server.js1
-rw-r--r--src/routes/(app)/buy/+page.server.js19
-rw-r--r--src/routes/(app)/buy/+page.svelte126
-rw-r--r--src/routes/(app)/send/lightning/[payreq]/+page.svelte2
8 files changed, 306 insertions, 61 deletions
diff --git a/src/components/Balance.svelte b/src/components/Balance.svelte
index e95e5dde..5d993c28 100644
--- a/src/components/Balance.svelte
+++ b/src/components/Balance.svelte
@@ -9,7 +9,7 @@
<div class="space-y-2">
<div class="text-5xl font-bold tabular-nums">
{#if isNaN($animatedRate)}
- <div class="text-gray-200">---</div>
+ <div class="text-gray-200">&mdash;</div>
{:else}
{f((user.balance * $animatedRate) / sats, user.currency)}
{/if}
diff --git a/src/lib/nostr.js b/src/lib/nostr.js
index de19b477..13251856 100644
--- a/src/lib/nostr.js
+++ b/src/lib/nostr.js
@@ -80,7 +80,7 @@ export let decrypt = async ({ event, user }) => {
return Buffer.from(message).toString('utf8');
} catch (e) {
- console.log(e);
+ // console.log(e);
}
};
@@ -119,6 +119,7 @@ let getPrivateKey = async (user) => {
seed = mnemonicToSeedSync(mnemonic);
key = bip32.fromSeed(seed);
child = key.derivePath("m/44'/1237'/0'/0/0");
+
return child.privateKey;
};
diff --git a/src/routes/(app)/[username]/(profile)/+page.server.js b/src/routes/(app)/[username]/(profile)/+page.server.js
index c68cfecc..d1fa2f41 100644
--- a/src/routes/(app)/[username]/(profile)/+page.server.js
+++ b/src/routes/(app)/[username]/(profile)/+page.server.js
@@ -1,22 +1,33 @@
import { auth, get } from '$lib/utils';
export async function load({ cookies, params, parent, url }) {
- let { subject } = await parent();
+ let { user, subject } = await parent();
let { pubkey } = subject;
+ let { since = 0 } = params;
- let events = [];
+ let messages = [];
+
+ if (user) {
+ try {
+ messages = await get(`/${user.pubkey}/${since}/messages`);
+ } catch (e) {
+ console.log(`failed to fetch nostr messages`, e);
+ }
+ }
+
+ let notes = [];
try {
- events = await get(`/${pubkey}/notes`);
+ notes = await get(`/${pubkey}/notes`);
} catch (e) {
- console.log(`failed to fetch nostr events for ${pubkey}`, e);
+ console.log(`failed to fetch nostr notes for ${pubkey}`, e);
}
- events.map((e) => {
+ notes.map((e) => {
e.seen = e.created_at;
});
let { invoices, sent, received } = await get('/requests', auth(cookies));
- return { invoices, events, sent, received };
+ 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 c704c00d..45fe1d84 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -1,75 +1,162 @@
<script>
- import { Avatar, Balance, Feed } from '$comp';
+ import { onMount } from 'svelte';
+ import { Icon, Avatar, Balance, Feed } from '$comp';
import { t } from '$lib/translations';
import { f, sat, sats } from '$lib/utils';
+ import { sign, send, encrypt, decrypt } from '$lib/nostr';
+ import { browser } from '$app/environment';
export let data;
- let { events, invoices, sent, received, subject, user } = data;
+ let { messages, notes, invoices, sent, received, subject, user } = data;
+
+ let keys = new Set();
+ let latest = [];
+ let i = 0;
+
+ onMount(async () => {
+ if (!browser) return;
+ while (latest.length < 3 && i < messages.length) {
+ let event = messages[i];
+ let { pubkey: k } = event.user;
+ if (k !== user.pubkey && !keys.has(k)) {
+ keys.add(k);
+ event.content = await decrypt({ event, user });
+ if (event.content) latest.push(event);
+ }
+ i++;
+ }
+
+ latest = latest;
+ });
</script>
-<div class="flex justify-center lg:justify-start">
+<div class="space-y-8">
{#if user?.id === subject.id}
- <Balance {user} />
- {/if}
-</div>
+ <div>
+ {#if user.balance > 0 && false}
+ <h1 class="text-secondary text-xl">Balance</h1>
+ <div class="flex justify-center lg:justify-start">
+ <Balance {user} />
+ </div>
+ {:else}
+ <div class="space-y-3">
+ <h1 class="text-2xl">Welcome</h1>
+
+ <p class="text-secondary">
+ Get started by making a deposit or getting someone to pay you in Bitcoin!
+ </p>
-<div>
- {#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 class="flex gap-4 justify-center w-full">
+ <a href={`/${user.username}/receive`}>
+ <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60">
+ <div class="mx-auto flex">
+ <Icon icon="numpad" style="my-auto h-6 mr-2" />
+ <div class="my-auto mt-1">Receive a Payment</div>
</div>
+ </button>
+ </a>
+
+ <!-- <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" /> -->
+ <!-- <div class="my-auto mt-1">Buy Bitcoin</div> -->
+ <!-- </div> -->
+ <!-- </button> -->
+ <!-- </a> -->
+ </div>
+ </div>
+ {/if}
+ </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>
- {: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 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>
- {/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}
+ </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>
+ {#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">
- <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 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>
- <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
- >
+ </a>
+ {/each}
+ </div>
+ {/if}
+
+ {#if latest.length}
+ <div>
+ <h1 class="text-2xl">Messages</h1>
+ {#each latest as { content, user }}
+ <div class="flex">
+ <div class="my-auto">
+ <Avatar {user} size={12} disabled={true} />
</div>
+ <div class="my-auto ml-1 text-secondary">{user.username}</div>
+ {content}
</div>
- </div>
- </a>
- {/each}
+ {/each}
+ </div>
+ {/if}
</div>
+
+<style>
+ h1 {
+ @apply mb-2;
+ }
+</style>
diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.server.js b/src/routes/(app)/[username]/settings/[...tab]/+page.server.js
index dd16459f..94240adc 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/+page.server.js
+++ b/src/routes/(app)/[username]/settings/[...tab]/+page.server.js
@@ -9,6 +9,7 @@ export function load({ params, url }) {
export const actions = {
default: async ({ cookies, request }) => {
let form = Object.fromEntries(await request.formData());
+ form.prompt = form.prompt === 'on'
let user = { ...(await get('/me', auth(cookies))), ...form };
try {
diff --git a/src/routes/(app)/buy/+page.server.js b/src/routes/(app)/buy/+page.server.js
new file mode 100644
index 00000000..e03d6c0a
--- /dev/null
+++ b/src/routes/(app)/buy/+page.server.js
@@ -0,0 +1,19 @@
+import { env } from '$env/dynamic/private';
+import { error, redirect } from '@sveltejs/kit';
+import { post, auth } from '$lib/utils';
+import { createTicket } from '$lib/ticket';
+
+export const actions = {
+ default: async ({ cookies, request }) => {
+ let form = await request.formData();
+
+ await post('/buy', {
+ number: form.get('number').replace(/\s+/g, ''),
+ month: form.get('expiry').slice(0, 2),
+ year: form.get('expiry').slice(-2),
+ cvc: form.get('cvc')
+ }, auth(cookies));
+
+ throw redirect(307, `/launch/thanks/${asset}`);
+ }
+};
diff --git a/src/routes/(app)/buy/+page.svelte b/src/routes/(app)/buy/+page.svelte
new file mode 100644
index 00000000..34cfc2e5
--- /dev/null
+++ b/src/routes/(app)/buy/+page.svelte
@@ -0,0 +1,126 @@
+<script>
+ import { enhance } from '$app/forms';
+ import { Icon, Lock, Card, Calendar, Spinner } from '$comp';
+
+ export let data;
+
+ let { username } = data.user;
+ let loading;
+
+ let amount = 100;
+ let n = '4242 4242 4242 4242';
+ let d = '10/25';
+ let cvc = 444;
+</script>
+
+<div class="container px-4 max-w-xl mx-auto mt-10 space-y-5">
+ <div class="w-full flex">
+ <a href="/" class="mx-auto">
+ <Icon icon="logo" />
+ </a>
+ </div>
+
+ <div class="flex justify-center items-center text-center">
+ <div class="shadow-xl rounded-3xl px-10 pt-5 pb-10 space-y-5 w-full mx-5">
+ <div class="relative">
+ <!-- <p class="absolute right-0 top-1 underline"><Icon icon="settings" /></p> -->
+ <h1 class="text-2xl md:text-3xl font-semibold">Buy Bitcoin</h1>
+ </div>
+ <p class="text-secondary">Purchase up to $500 worth of Bitcoin from us.</p>
+
+ <p class="text-secondary">
+ If you want more, try <a href="https://www.kraken.com/" target="_blank" class="text-black"
+ >Kraken</a
+ >
+ or
+ <a href="https://shakepay.com/" target="_blank" class="text-black">ShakePay</a> or ask
+ around at our next
+ <a href="https://www.meetup.com/vancouver-bitcoiners" target="_blank" class="text-black"
+ >meetup</a
+ >.
+ </p>
+
+ <form
+ method="POST"
+ class="flex flex-wrap gap-5 w-full p-5 mt-20"
+ use:enhance={() => {
+ loading = true;
+ return ({ update }) => update() && (loading = false);
+ }}
+ >
+ <input type="hidden" name="username" value={username} />
+
+ <label class="relative w-full flex flex-col">
+ <span class="font-bold mb-3">Amount</span>
+ <input
+ class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300"
+ type="text"
+ name="number"
+ placeholder="500"
+ bind:value={amount}
+ />
+ <span class="absolute text-gray-300 bottom-[9px] left-0 translate-x-1/2 w-6">$</span>
+ </label>
+
+ <label class="relative w-full flex flex-col">
+ <span class="font-bold mb-3">Card number</span>
+ <input
+ class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300"
+ type="text"
+ name="number"
+ placeholder="0000 0000 0000 0000"
+ bind:value={n}
+ />
+ <Card />
+ </label>
+
+ <label class="relative flex-1 flex flex-col">
+ <span class="font-bold mb-3">Expire date</span>
+ <input
+ class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300"
+ type="text"
+ name="expiry"
+ placeholder="MM/YY"
+ bind:value={d}
+ />
+ <Calendar />
+ </label>
+
+ <label class="relative flex-1 flex flex-col">
+ <span class="font-bold flex items-center gap-3 mb-3">
+ CVC/CVV
+ <span class="relative group">
+ <span
+ class="hidden group-hover:flex justify-center items-center px-2 py-1 text-xs absolute -right-2 transform translate-x-full -translate-y-1/2 w-max top-1/2 bg-black text-white"
+ >
+ 3-digit number on the back of your card</span
+ >
+ </span>
+ </span>
+ <input
+ class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300"
+ type="text"
+ name="cvc"
+ placeholder="&bull;&bull;&bull;"
+ bind:value={cvc}
+ />
+ <Lock />
+ </label>
+
+ <div class="w-full flex mt-8">
+ <button
+ class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-xl"
+ type="submit"
+ disabled={loading}
+ >
+ {#if loading}
+ <Spinner />
+ {:else}
+ Submit
+ {/if}
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.svelte b/src/routes/(app)/send/lightning/[payreq]/+page.svelte
index 6ae699d2..e9502636 100644
--- a/src/routes/(app)/send/lightning/[payreq]/+page.svelte
+++ b/src/routes/(app)/send/lightning/[payreq]/+page.svelte
@@ -89,7 +89,7 @@
</div>
{/if}
{:else}
- <form method="POST" action="?/setAmount" class="w-[300px] mx-auto">
+ <form method="POST" action="?/setAmount" class="w-[300px] mx-auto" use:enhance>
<input type="hidden" value={a} name="amount" />
<Numpad bind:amount={a} {currency} />
<button