aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes
diff options
context:
space:
mode:
authorsecondl1ght <[email protected]>2022-10-25 18:01:56 -0600
committersecondl1ght <[email protected]>2022-10-25 18:01:56 -0600
commita67b375473f1f40e1c0febfac16d7e36e0d72561 (patch)
tree10a5fa750f2ccb53cff3510ab8fbbce108a35eb6 /src/routes
parentaa6debcd83c0b4067e0f8ce9a11d9c9ac02476ad (diff)
parent5a5ea3ccc327ff52fb1c313998d90e4bfefea90a (diff)
downloadcoinos-ui-a67b375473f1f40e1c0febfac16d7e36e0d72561.tar.gz
coinos-ui-a67b375473f1f40e1c0febfac16d7e36e0d72561.zip
Merge branch 'master' of github.com:coinos/coinos-ui-v2
Diffstat (limited to 'src/routes')
-rw-r--r--src/routes/+layout.server.js10
-rw-r--r--src/routes/+layout.svelte15
-rw-r--r--src/routes/[username]/+page.server.js1
-rw-r--r--src/routes/[username]/dashboard/+page.server.js1
-rw-r--r--src/routes/[username]/settings/+page.server.js4
-rw-r--r--src/routes/[username]/settings/+page.svelte41
-rw-r--r--src/routes/[username]/settings/_account.svelte35
-rw-r--r--src/routes/[username]/settings/_security.svelte109
-rw-r--r--src/routes/invoice/[id]/+page.svelte4
-rw-r--r--src/routes/invoice/[id]/paid/+page.svelte2
-rw-r--r--src/routes/launch/+page.server.js6
-rw-r--r--src/routes/launch/+page.svelte91
-rw-r--r--src/routes/launch/purchase/+page.server.js45
-rw-r--r--src/routes/launch/purchase/+page.svelte91
-rw-r--r--src/routes/launch/purchase/card/+page.server.js44
-rw-r--r--src/routes/launch/purchase/card/+page.svelte77
-rw-r--r--src/routes/launch/thanks/[asset]/+page.server.js1
-rw-r--r--src/routes/launch/thanks/[asset]/+page.svelte29
-rw-r--r--src/routes/launch/ticket/+page.server.js9
-rw-r--r--src/routes/launch/ticket/+page.svelte35
-rw-r--r--src/routes/login/+page.server.js10
-rw-r--r--src/routes/login/+page.svelte1
-rw-r--r--src/routes/register/+page.server.js4
-rw-r--r--src/routes/register/+page.svelte2
-rw-r--r--src/routes/scan/+page.svelte2
-rw-r--r--src/routes/send/+layout.svelte18
-rw-r--r--src/routes/send/[...text]/+page.server.js2
-rw-r--r--src/routes/send/[...text]/+page.svelte4
-rw-r--r--src/routes/send/[uuid=uuid]/+page.server.js15
-rw-r--r--src/routes/send/[uuid=uuid]/+page.svelte15
-rw-r--r--src/routes/send/bitcoin/[address]/+page.server.js13
-rw-r--r--src/routes/send/bitcoin/[address]/+page.svelte16
-rw-r--r--src/routes/send/lightning/[payreq]/+page.server.js16
-rw-r--r--src/routes/send/lightning/[payreq]/+page.svelte14
-rw-r--r--src/routes/send/user/[username]/[...amount]/+page.server.js15
-rw-r--r--src/routes/send/user/[username]/[...amount]/+page.svelte16
-rw-r--r--src/routes/user/+server.js1
37 files changed, 651 insertions, 163 deletions
diff --git a/src/routes/+layout.server.js b/src/routes/+layout.server.js
index e110b467..be81e19f 100644
--- a/src/routes/+layout.server.js
+++ b/src/routes/+layout.server.js
@@ -1,12 +1,12 @@
import { error, redirect } from '@sveltejs/kit';
-import cookie from 'cookie';
import { protectedRoutes } from '$lib/utils';
import { get, post, auth } from '$lib/utils';
import { env } from '$env/dynamic/public';
export const { PUBLIC_BTC: btc } = env;
-export async function load({ cookies, request, url, params }) {
+export async function load({ cookies, locals, request, url, params }) {
+ url.pathname;
let token = cookies.get('token');
let user;
let rates;
@@ -17,11 +17,7 @@ export async function load({ cookies, request, url, params }) {
}
if (token && token !== 'undefined') {
- try {
- user = await get('/me', auth(cookies));
- } catch (e) {
- throw redirect(307, '/logout');
- }
+ user = locals.user;
if (protectedRoutes.find((p) => url.pathname.match(p))) {
if (user.username !== params.username) throw error(404, 'user not found');
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 42d249f4..5cc75b30 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -3,7 +3,7 @@
import '../app.css';
import { onMount } from 'svelte';
import { connect } from '$lib/socket';
- import { selectedRate, rate } from '$lib/store';
+ import { selectedRate, rate, user } from '$lib/store';
import { page } from '$app/stores';
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
@@ -14,12 +14,17 @@
const localeLocalStorageKey = 'sveltekit-i18n-locale';
export let data;
- let { user, rate: r, token, rates } = data;
+
+ let r, u, token, rates;
+
$: update(data);
- let update = () => ({ user, rate: r, token, rates } = data);
- $rate = r;
+ let update = () => {
+ ({ user: u, rate: r, token, rates } = data);
+ $rate = r;
+ $user = u;
+ };
- $: $selectedRate = user && $rate * (rates[user.currency] / rates.USD);
+ $: $selectedRate = u && r * (rates[u.currency] / rates.USD);
onMount(() => {
let localStorageLocale = localStorage.getItem(localeLocalStorageKey);
diff --git a/src/routes/[username]/+page.server.js b/src/routes/[username]/+page.server.js
index d6f2978c..0840161a 100644
--- a/src/routes/[username]/+page.server.js
+++ b/src/routes/[username]/+page.server.js
@@ -1,4 +1,3 @@
-import cookie from 'cookie';
import { get, post } from '$lib/utils';
export async function load({ params }) {
diff --git a/src/routes/[username]/dashboard/+page.server.js b/src/routes/[username]/dashboard/+page.server.js
new file mode 100644
index 00000000..b6206324
--- /dev/null
+++ b/src/routes/[username]/dashboard/+page.server.js
@@ -0,0 +1 @@
+export const load = ({ parent }) => parent();
diff --git a/src/routes/[username]/settings/+page.server.js b/src/routes/[username]/settings/+page.server.js
index 7053a939..ae7e0a78 100644
--- a/src/routes/[username]/settings/+page.server.js
+++ b/src/routes/[username]/settings/+page.server.js
@@ -3,9 +3,11 @@ import { auth, get, post } from '$lib/utils';
export const actions = {
default: async ({ cookies, request }) => {
+ let form = Object.fromEntries(await request.formData());
+
let user = {
...(await get('/me', auth(cookies))),
- ...Object.fromEntries(await request.formData())
+ ...form
};
try {
diff --git a/src/routes/[username]/settings/+page.svelte b/src/routes/[username]/settings/+page.svelte
index c1ac20d0..7bd4ddcc 100644
--- a/src/routes/[username]/settings/+page.svelte
+++ b/src/routes/[username]/settings/+page.svelte
@@ -1,21 +1,28 @@
<script>
+ import { fly } from 'svelte/transition';
import { enhance } from '$app/forms';
import Account from './_account.svelte';
import Pos from './_pos.svelte';
import Security from './_security.svelte';
- import { Icon } from '$comp';
+ import { Icon, Spinner, Pin } from '$comp';
import { t } from '$lib/translations';
import { failure, success } from '$lib/utils';
- import { avatarUpload, bannerUpload } from '$lib/store';
+ import { avatarUpload, bannerUpload, pin } from '$lib/store';
import { upload } from '$lib/upload';
export let data;
+ export let form;
let { user, rates, token } = data;
$: update(data);
let update = () => ({ user, rates, token } = data);
+ $: if (form?.message?.startsWith('Pin')) {
+ failure('Wrong pin, try again');
+ $pin = '';
+ }
+
let tab = 'account';
let tabs = [
@@ -26,23 +33,37 @@
$: ({ comp } = tabs.find((t) => t.name === tab));
- let save = async () => {
+ let loading, submit;
+ let save = async (msg) => {
+ loading = true;
+ if (typeof msg !== 'string') msg = 'Settings saved';
+
try {
if ($avatarUpload) {
await upload($avatarUpload.file, $avatarUpload.type, $avatarUpload.progress, token);
}
+
if ($bannerUpload) {
await upload($bannerUpload.file, $bannerUpload.type, $bannerUpload.progress, token);
}
- success('Settings saved');
+
+ success(msg);
} catch (e) {
console.log(e);
failure('Something went wrong');
}
+
+ loading = false;
};
</script>
+{#if user.haspin && $pin?.length !== 6}
+ <Pin />
+{/if}
+
<form method="POST" class="mb-[154px]" use:enhance on:submit={save}>
+ <input type="hidden" name="pin" value={$pin} />
+
<div class="mt-24 mb-20 px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8">
<h1 class="text-center text-3xl md:text-4xl font-semibold mb-10">
{$t('user.settings.header')}
@@ -58,16 +79,22 @@
{/each}
</div>
- <svelte:component this={comp} {user} {rates} />
+ <svelte:component this={comp} {user} {rates} {submit} />
</div>
<div
class="z-10 fixed bottom-0 bg-white shadow border-t w-full flex justify-center items-center py-3"
>
<button
+ bind:this={submit}
type="submit"
- class="bg-black text-white rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80"
+ class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80"
+ class:bg-black={loading}
>
- <div class="my-auto">Save Settings</div>
+ {#if loading}
+ <Spinner />
+ {:else}
+ <div class="my-auto">Save Settings</div>
+ {/if}
</button>
</div>
</form>
diff --git a/src/routes/[username]/settings/_account.svelte b/src/routes/[username]/settings/_account.svelte
index bb229b93..f139b3f4 100644
--- a/src/routes/[username]/settings/_account.svelte
+++ b/src/routes/[username]/settings/_account.svelte
@@ -17,6 +17,7 @@
$: banner = files.banner.src;
$: $tempProfileFiles = { profile: files.profile.src, banner: files.banner.src };
+ let togglePassword = () => (revealPassword = !revealPassword);
let colorThemes = [
{ theme: 1, color1: 'from-[#F5F7FA]', color2: 'to-[#C3CFE2]' },
{ theme: 2, color1: 'from-[#FDFCFB]', color2: 'to-[#E2D1C3]' },
@@ -36,9 +37,14 @@
percent = Math.round((event.loaded / event.total) * 100);
};
+ let tooLarge = {};
+
let files = { profile: {}, banner: {} };
let handleFile = async ({ target }, type) => {
+ tooLarge[type] = false;
let file = target.files[0];
+ console.log('TYPE', file.size);
+ if (file.size > 5000000) return (tooLarge[type] = true);
var reader = new FileReader();
reader.onload = async (e) => {
files[type].src = e.target.result;
@@ -83,7 +89,8 @@
{/if}
<button
type="button"
- on:click={() => (revealPassword = !revealPassword)}
+ on:click={togglePassword}
+ on:keydown={togglePassword}
class="absolute right-5 top-11"
>
<Icon icon={revealPassword ? 'eye' : 'eye-off'} />
@@ -118,11 +125,13 @@
<div>
<span class="font-bold">{$t('user.settings.profileImage')}</span>
+
<div class="flex">
{#if profile}
<div
class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer"
on:click={() => select('profile')}
+ on:keydown={() => select('profile')}
>
<img
src={profile}
@@ -134,9 +143,10 @@
<div
class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer"
on:click={() => select('profile')}
+ on:keydown={() => select('profile')}
>
<img
- src={`/api/public/${user.username}-profile.png`}
+ src={`/api/public/${user.username}-profile.webp`}
class="absolute w-full h-full object-cover object-center visible overflow-hidden"
alt={user.username}
/>
@@ -145,6 +155,7 @@
<div
class="rounded-full border-4 border-white p-4 bg-gradient-to-r {$colorTheme} w-24 my-auto hover:opacity-80 cursor-pointer"
on:click={() => select('profile')}
+ on:keydown={() => select('profile')}
>
<Icon icon="logo-symbol-white" style="mx-auto" />
</div>
@@ -154,7 +165,8 @@
<button
type="button"
class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80"
- on:click={() => select('profile')}>Select</button
+ on:click={() => select('profile')}
+ on:keydown={() => select('profile')}>Select</button
>
<input
type="file"
@@ -167,6 +179,10 @@
{/if}
</div>
</div>
+
+ {#if tooLarge['profile']}
+ <div class="text-red-600">Max file size 5MB</div>
+ {/if}
</div>
<div>
@@ -180,19 +196,22 @@
src={banner}
class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80"
on:click={() => select('banner')}
+ on:keydown={() => select('banner')}
alt="Banner"
/>
{:else if user.banner}
<img
- src={`/api/public/${user.username}-banner.png`}
+ src={`/api/public/${user.username}-banner.webp`}
class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80"
on:click={() => select('banner')}
+ on:keydown={() => select('banner')}
alt="Banner"
/>
{:else}
<div
class="bg-gradient-to-r {$colorTheme} w-full h-48 mb-4 cursor-pointer hover:opacity-80"
on:click={() => select('banner')}
+ on:keydown={() => select('banner')}
alt="Banner"
/>
{/if}
@@ -201,7 +220,8 @@
<button
type="button"
class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80"
- on:click={() => select('banner')}>Select</button
+ on:click={() => select('banner')}
+ on:keydown={() => select('banner')}>Select</button
>
<input
type="file"
@@ -212,6 +232,10 @@
{#if files.banner.filename}
<div class="mt-2 text-sm">{files.banner.filename}</div>
{/if}
+
+ {#if tooLarge['banner']}
+ <div class="text-red-600">Max file size 5MB</div>
+ {/if}
</div>
<!-- TODO
@@ -226,6 +250,7 @@
? 'border-black'
: ''}"
on:click={() => handleThemeClick(colors)}
+ on:keydown={() => handleThemeClick(colors)}
/>
{/each}
</div> -->
diff --git a/src/routes/[username]/settings/_security.svelte b/src/routes/[username]/settings/_security.svelte
index 2a490aa4..98b9b503 100644
--- a/src/routes/[username]/settings/_security.svelte
+++ b/src/routes/[username]/settings/_security.svelte
@@ -4,26 +4,80 @@
import { Icon, Qr } from '$comp';
import { Pincode, PincodeInput } from 'svelte-pincode';
import { post, success, failure } from '$lib/utils';
+ import { pin as current } from '$lib/store';
- export let user;
+ export let user, submit;
let settingPin, setting2fa, disabling2fa;
- let code = [];
- let pin = '';
let token = '';
- let tokenInput, pinInput;
+ let pinCode = [];
+ let verifyCode = [];
+ let tokenInput, pinInput, verifyInput;
+ let verifying = false;
+ let old;
+ let startVerifying = () => {
+ verifying = true;
+ };
+
+ $: pin = pinCode.join('');
+
+ let locked;
+ $: verifyCode.join('').length > 5 && checkPin();
+ let checkPin = async () => {
+ old = $current;
+ if (locked) return;
+ locked = true;
+
+ let verify = verifyCode.join('');
+
+ pinCode = ['', '', '', '', '', ''];
+ verifyCode = ['', '', '', '', '', ''];
+ settingPin = false;
+
+ try {
+ if (pin.length > 5 && pin === verify) {
+ user.haspin = true;
+ $current = pin;
+ submit.click();
+ } else {
+ failure('Pin mismatch, try again');
+ settingPin = true;
+ pinInput.focusFirstInput();
+ }
+ } catch (e) {
+ failure('Problem setting PIN');
+ }
+
+ verifying = false;
+
+ await new Promise((r) => setTimeout(r, 500));
+ locked = false;
+ };
+
+ $: if (verifying) verifyInput && tick().then(verifyInput.focusFirstInput);
$: if (settingPin) pinInput && tick().then(pinInput.focusFirstInput);
- $: if (setting2fa || disabling2fa) tokenInput && tick().then(tokenInput.focusFirstInput);
+ $: if (setting2fa || disabling2fa) tokenInput && tick().then(tokenInput?.focusFirstInput);
let reset = () => {
token = '';
- code = [];
return true;
};
- let togglePin = () => (settingPin = !settingPin);
+ let togglePin = async () => {
+ if (user.haspin) {
+ try {
+ pin = null;
+ submit.click();
+ } catch (e) {
+ failure('Failed to disable pin');
+ }
+ } else {
+ settingPin = true;
+ }
+ };
+
let toggleEnabling = () => reset() && (setting2fa = !setting2fa);
let toggleDisabling = () => reset() && (disabling2fa = !disabling2fa);
@@ -58,31 +112,44 @@
$: otpUri = `otpauth://totp/coinos:${user.username}?secret=${user.otpsecret}&period=30&digits=6&algorithm=SHA1&issuer=coinos`;
</script>
-<!-- TODO
<div>
- <span class="font-bold mb-1">{$t('user.settings.securityPIN')}</span>
+ <input type="hidden" name="newpin" value={pin} />
+
+ <span class="font-bold mb-1">{verifying ? 'Verify' : 'Security'} PIN</span>
<p class="text-secondary mb-1">
{$t('user.settings.securityPINDescription')}
</p>
{#if settingPin}
<div class="flex my-4">
<div class="mx-auto">
- <Pincode bind:code bind:value={user.pin} bind:this={pinInput}>
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- </Pincode>
+ <div class:hidden={verifying}>
+ <Pincode on:complete={startVerifying} bind:code={pinCode} bind:this={pinInput}>
+ <PincodeInput />
+ <PincodeInput />
+ <PincodeInput />
+ <PincodeInput />
+ <PincodeInput />
+ <PincodeInput />
+ </Pincode>
+ </div>
+ <div class:hidden={!verifying}>
+ <Pincode bind:code={verifyCode} bind:this={verifyInput}>
+ <PincodeInput />
+ <PincodeInput />
+ <PincodeInput />
+ <PincodeInput />
+ <PincodeInput />
+ <PincodeInput />
+ </Pincode>
+ </div>
</div>
</div>
{:else}
<button type="button" class="primary" on:click={togglePin}
- ><Icon icon="lock" style="mr-1" /> {$t('user.settings.setPIN')}</button
+ ><Icon icon="lock" style="mr-1" /> {user.haspin ? 'Disable Pin' : 'Enable Pin'}</button
>
{/if}
-</div> -->
+</div>
<!-- TODO
<div>
@@ -116,7 +183,7 @@
<div class="text-center my-4">
{$t('user.settings.oneTimeCode')}<br />
- <Pincode bind:code bind:value={token} bind:this={tokenInput}>
+ <Pincode bind:value={token} bind:this={tokenInput}>
<PincodeInput />
<PincodeInput />
<PincodeInput />
@@ -128,7 +195,7 @@
{:else if disabling2fa}
<div class="text-center my-4">
{$t('user.settings.oneTimeCode')}<br />
- <Pincode bind:code bind:value={token} bind:this={tokenInput}>
+ <Pincode bind:value={token} bind:this={tokenInput}>
<PincodeInput />
<PincodeInput />
<PincodeInput />
diff --git a/src/routes/invoice/[id]/+page.svelte b/src/routes/invoice/[id]/+page.svelte
index 04a31813..84b03593 100644
--- a/src/routes/invoice/[id]/+page.svelte
+++ b/src/routes/invoice/[id]/+page.svelte
@@ -44,7 +44,9 @@
$: tip = Math.round((amount / 100) * tipPercent);
$invoices[id] = { amount, id, rate, received, text, tip, username };
- $: browser && $invoices[id]?.received >= $invoices[id]?.amount && goto(`/invoice/${id}/paid`);
+ $: browser &&
+ $invoices[id]?.received >= $invoices[id]?.amount &&
+ goto(($invoices[id].memo === 'launch' && '/launch/purchase') || `/invoice/${id}/paid`);
$: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2));
diff --git a/src/routes/invoice/[id]/paid/+page.svelte b/src/routes/invoice/[id]/paid/+page.svelte
index 95c721d0..d619b7ff 100644
--- a/src/routes/invoice/[id]/paid/+page.svelte
+++ b/src/routes/invoice/[id]/paid/+page.svelte
@@ -22,7 +22,7 @@
};
</script>
-<div class="container px-4 text-center">
+<div class="container px-4 text-center mx-auto">
<div class="flex w-full py-20 max-w-[200px] mx-auto" in:scale={{ start: 0.5 }}>
<Icon icon="check" style="mx-auto" />
</div>
diff --git a/src/routes/launch/+page.server.js b/src/routes/launch/+page.server.js
new file mode 100644
index 00000000..2b44dfae
--- /dev/null
+++ b/src/routes/launch/+page.server.js
@@ -0,0 +1,6 @@
+import { auth, get } from '$lib/utils';
+
+export const load = async ({ parent }) => {
+ let tickets = await get('/tickets');
+ return { ...(await parent()), tickets };
+};
diff --git a/src/routes/launch/+page.svelte b/src/routes/launch/+page.svelte
index 04f53086..6faf492d 100644
--- a/src/routes/launch/+page.svelte
+++ b/src/routes/launch/+page.svelte
@@ -1,48 +1,99 @@
<script>
- import { redirect } from '$lib/store';
+ import { LandingHeader } from '$comp';
+ import { loginRedirect } from '$lib/store';
import { beforeNavigate } from '$app/navigation';
import { info } from '$lib/utils';
+ import { Icon } from '$comp';
+ import { t } from '$lib/translations';
+ import { goto } from '$app/navigation';
+ import { scroll } from '$lib/utils';
export let data;
- let { user } = data;
- $: link = user ? '/launch/purchase' : '/register';
- $redirect = '/launch/purchase';
+ let { user, tickets } = data;
+ $loginRedirect = '/launch/purchase';
+
+ let hasTicket = !!user?.accounts.find((a) => tickets.includes(a.asset));
+ $: link = hasTicket ? '/launch/ticket' : user ? $loginRedirect : '/register';
+ $: text = (hasTicket ? 'View' : 'Purchase') + ' Ticket';
beforeNavigate(({ to }) => {
- console.log(link);
- if (link === '/register') info('Please sign in so we can get you a ticket');
+ if (to?.routeId === 'register') info('Please sign in so we can get you a ticket');
});
+
+ let showMobileMenu = false;
+ const mobileMenuButtonClick = (section) => {
+ showMobileMenu = false;
+ scroll(section);
+ };
</script>
-<div class="flex mt-20 px-8">
- <img src="/images/ticket.webp" class="mb-8 md:max-w-xl mx-auto border border-gray-400" />
-</div>
-<div class="mx-auto flex items-center justify-center px-8">
- <div class="flex flex-col w-full bg-white rounded shadow-lg sm:w-3/4 md:w-1/2 lg:w-3/5">
+<header class="w-full lg:w-5/6 py-5 mx-auto fixed md:sticky z-10 top-0 bg-white/90">
+ <nav class="block xl:flex flex-wrap justify-between items-center">
+ <div class="flex justify-start md:justify-center items-center md:space-x-10">
+ <a href="/" class="hidden md:block">
+ <Icon icon="logo" style="mb-5 xl:mb-0" />
+ </a>
+ <Icon icon="logo" style="block md:hidden ml-5 w-48" />
+ </div>
+ </nav>
+</header>
+
+<div class="mx-auto flex items-center justify-center px-8 mt-20">
+ <div class="flex flex-col w-full bg-white rounded shadow-lg sm:w-3/4 lg:w-3/5">
<div class="flex flex-col w-full md:flex-row">
<div
class="flex flex-row justify-around p-4 font-bold leading-none text-gray-800 uppercase bg-gray-400 rounded md:flex-col md:items-center md:justify-center md:w-1/4"
>
<div class="md:text-3xl">Oct</div>
- <div class="md:text-6xl">5</div>
+ <div class="md:text-6xl">26</div>
<div class="md:text-xl">6 pm</div>
</div>
- <div class="p-4 font-normal text-gray-800 md:w-3/4">
+ <div class="p-4 font-normal text-gray-800 md:w-3/4 space-y-5">
<h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800">
- Coinos Launch Party!
+ Launch Party!
</h1>
+
<p class="leading-normal mb-2">
- Come join us at our launch party celebrating 10 years in business! We'll be introducing
- our newly designed payments interface and afterwards we'll network and enjoy tasty food
- and drinks from <a href="https://loftondenman.com/" class="underline">The Loft</a>!
+ Come join us at our launch party celebrating our new design and 10 years serving the
+ Vancouver Bitcoin community!
+ </p>
+
+ <div class="flex">
+ <div class="space-y-5">
+ <div class="ml-8">
+ <p><b>When</b></p>
+ <p>6pm October 26</p>
+ </div>
+ <div class="ml-8">
+ <p><b>Where</b></p>
+ <p>The Loft Lounge</p>
+ <p>1184 Denman Street, #203</p>
+ <p>Vancouver, BC</p>
+ </div>
+ </div>
+
+ <img src="/icons/party.svg" alt="Partygoers" class="w-[120px] mx-auto" />
+ </div>
+
+ <p>
+ We'll introduce our new interface and then network and enjoy tasty food and drinks from <a
+ href="https://loftondenman.com/"
+ class="underline">The Loft</a
+ >!
+ </p>
+
+ <p>
+ The Loft will be accepting Lightning payments at the event and going forward into the
+ future as well! We're charging $6 for for an NFT ticket that gets you entry, appies, and the
+ chance to win some swag and sats.
</p>
<div class="flex w-full my-8">
- <a href={link}>
+ <a href={link} class="mx-auto">
<button
- class="bg-white text-black border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl"
+ class="bg-white text-black border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl"
>
- Purchase Ticket
+ {text}
</button>
</a>
</div>
diff --git a/src/routes/launch/purchase/+page.server.js b/src/routes/launch/purchase/+page.server.js
index f6314f7f..2ecce695 100644
--- a/src/routes/launch/purchase/+page.server.js
+++ b/src/routes/launch/purchase/+page.server.js
@@ -1,8 +1,45 @@
-export const load = ({ parent }) => parent();
+import { auth, get, post } from '$lib/utils';
+import tickets from '$lib/tickets';
+import { redirect } from '@sveltejs/kit';
+import { env } from '$env/dynamic/private';
+import { invalidateAll } from '$app/navigation';
+import { browser } from '$app/environment';
+import { createTicket } from '$lib/ticket';
+
+export const load = async ({ parent }) => {
+ let tickets = await get('/tickets');
+ let { user } = await parent();
+ let hasTicket = !!user?.accounts.find((a) => tickets.includes(a.asset));
+ if (hasTicket) throw redirect(307, '/launch/ticket');
+ return { tickets };
+};
export const actions = {
- default: async ({ cookies, request }) => {
- let form = Object.fromEntries(await request.formData());
- console.log(form);
+ internal: async ({ cookies, request }) => {
+ let form = await request.formData();
+ let asset = await createTicket(cookies, form.get('username'));
+ await post('/send', { amount: 22000, username: 'launch' }, auth(cookies));
+
+ throw redirect(307, `/launch/thanks/${asset}`);
+ },
+
+ lightning: async ({ cookies, request }) => {
+ let form = await request.formData();
+
+ let rates = await get('/rates');
+
+ let invoice = {
+ amount: 22000,
+ memo: 'launch',
+ network: 'lightning',
+ prompt: false,
+ rate: rates[form.get('currency')]
+ };
+
+ let user = { username: form.get('username') };
+
+ let { uuid } = await post('/invoice', { invoice, user }, auth(cookies));
+
+ throw redirect(307, `/invoice/${uuid}`);
}
};
diff --git a/src/routes/launch/purchase/+page.svelte b/src/routes/launch/purchase/+page.svelte
index df3048f3..3273752a 100644
--- a/src/routes/launch/purchase/+page.svelte
+++ b/src/routes/launch/purchase/+page.svelte
@@ -1,4 +1,5 @@
<script>
+ import { onMount } from 'svelte';
import { enhance } from '$app/forms';
import { selectedRate } from '$lib/store';
import { post } from '$lib/utils';
@@ -6,77 +7,61 @@
export let data;
- let amount = 35000;
+ let amount = 22000;
let {
+ currency,
account: { balance },
username
} = data.user;
- let invoice;
- let get = async (network) => {
- let { uuid } = await post('/invoice', {
- amount,
- rate: $selectedRate,
- network,
- username
- });
-
- goto(`/invoice/${uuid}`);
- };
-
- let lightning = () => get('lightning');
- let bitcoin = () => get('bitcoin');
-
- // if (balance < amount) throw redirect(307, `/${username}/fund/${amount - balance}`);
+ let { tickets } = data;
+ let ticket = tickets.length;
</script>
-<div class="mx-auto h-screen flex items-center justify-center px-8">
- <div class="p-4 font-normal text-gray-800 mx-auto mb-8">
- {#if balance > amount}
- <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center">
- Buy Your Launch Party Ticket?
- </h1>
+<div class="mx-auto h-screen flex items-center justify-center px-4">
+ <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8">
+ <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center">
+ Purchase Ticket
+ </h1>
- <div class="text-center text-4xl mb-12">{amount} sats</div>
+ <img
+ src={`/tickets/${(ticket + 1).toString().padStart(2, '0')}.png`}
+ class="w-full max-w-[800px]"
+ alt="Ticket"
+ />
- <div class="w-full flex">
- <form method="POST" use:enhance>
+ {#if balance >= amount}
+ <form action="?/internal" method="POST" use:enhance>
+ <input type="hidden" name="username" value={username} />
+
+ <div class="w-full flex">
<button
class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl"
>
Count me in
</button>
- </form>
- </div>
+ </div>
+ </form>
{:else}
- <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center">
- Ticket Price
- </h1>
- <div class="text-center text-4xl mb-12">{amount} sats</div>
-
- <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center">
- Your Balance
- </h1>
-
- <div class="text-center text-4xl mb-12">{balance} sats</div>
-
- <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center">
- Funding Options
+ <h1 class="mb-4 text-xl font-bold leading-none tracking-tight text-gray-800 text-center">
+ Payment Options
</h1>
- <button
- class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl"
- on:click={lightning}
- >
- Lightning
- </button>
+ <div class="flex justify-center gap-4">
+ <form action="?/lightning" method="POST">
+ <input type="hidden" name="currency" value={currency} />
+ <input type="hidden" name="username" value={username} />
+ <button class="border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl">
+ Lightning
+ </button>
+ </form>
- <button
- class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl"
- on:click={bitcoin}
- >
- Bitcoin
- </button>
+ <a href={`purchase/card`}>
+ <button class="border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl">
+ Credit Card
+ </button>
+ </a>
+ </div>
{/if}
</div>
</div>
diff --git a/src/routes/launch/purchase/card/+page.server.js b/src/routes/launch/purchase/card/+page.server.js
new file mode 100644
index 00000000..1f564f78
--- /dev/null
+++ b/src/routes/launch/purchase/card/+page.server.js
@@ -0,0 +1,44 @@
+import { env } from '$env/dynamic/private';
+import { error, redirect } from '@sveltejs/kit';
+import { post, auth } from '$lib/utils';
+import { createTicket } from '$lib/ticket';
+
+let stripe = 'https://api.stripe.com/v1';
+
+export const actions = {
+ default: async ({ cookies, request }) => {
+ let form = await request.formData();
+
+ let headers = new Headers();
+ headers.set('Authorization', 'Basic ' + new Buffer(env.STRIPE + ':').toString('base64'));
+
+ let body = new URLSearchParams({
+ 'card[number]': form.get('number').replace(/\s+/g, ''),
+ 'card[exp_month]': form.get('expiry').slice(0, 2),
+ 'card[exp_year]': form.get('expiry').slice(-2),
+ 'card[cvc]': form.get('cvc')
+ });
+
+ let method = 'POST';
+
+ let { id: source } = await fetch(`${stripe}/tokens`, { body, headers, method }).then((r) =>
+ r.json()
+ );
+
+ body = new URLSearchParams({
+ amount: 600,
+ currency: 'cad',
+ source,
+ description: 'launch ticket'
+ });
+
+ let { status } = await fetch(`${stripe}/charges`, { body, headers, method }).then((r) =>
+ r.json()
+ );
+
+ if (status !== 'succeeded') throw error(500, { message: 'Card payment failed' });
+ let asset = await createTicket(cookies, form.get('username'));
+
+ throw redirect(307, `/launch/thanks/${asset}`);
+ }
+};
diff --git a/src/routes/launch/purchase/card/+page.svelte b/src/routes/launch/purchase/card/+page.svelte
new file mode 100644
index 00000000..05d09025
--- /dev/null
+++ b/src/routes/launch/purchase/card/+page.svelte
@@ -0,0 +1,77 @@
+<script>
+ import { enhance } from '$app/forms';
+ import { Lock, Card, Calendar, Spinner } from '$comp';
+
+ export let data;
+
+ let { username } = data.user;
+ let loading;
+</script>
+
+<div class="container max-w-lg px-4 mx-auto space-y-5">
+ <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">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"
+ />
+ <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"
+ />
+ <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;"
+ />
+ <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>
diff --git a/src/routes/launch/thanks/[asset]/+page.server.js b/src/routes/launch/thanks/[asset]/+page.server.js
new file mode 100644
index 00000000..e9e01376
--- /dev/null
+++ b/src/routes/launch/thanks/[asset]/+page.server.js
@@ -0,0 +1 @@
+// export const load = ({ parent }) => parent();
diff --git a/src/routes/launch/thanks/[asset]/+page.svelte b/src/routes/launch/thanks/[asset]/+page.svelte
new file mode 100644
index 00000000..43f10057
--- /dev/null
+++ b/src/routes/launch/thanks/[asset]/+page.svelte
@@ -0,0 +1,29 @@
+<script>
+ import { page } from '$app/stores';
+
+ export let data;
+
+ let { user } = data;
+
+ let { asset } = $page.params;
+</script>
+
+<div class="container mx-auto max-w-lg px-4 mt-20">
+ <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8">
+ <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center">
+ Thanks!
+ </h1>
+
+ <p>We look forward to seeing you!</p>
+ </div>
+
+ <div class="w-full flex">
+ <a href="/launch/ticket" class="mx-auto">
+ <button
+ class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl"
+ >
+ Done
+ </button>
+ </a>
+ </div>
+</div>
diff --git a/src/routes/launch/ticket/+page.server.js b/src/routes/launch/ticket/+page.server.js
new file mode 100644
index 00000000..0362eb7d
--- /dev/null
+++ b/src/routes/launch/ticket/+page.server.js
@@ -0,0 +1,9 @@
+import { get } from '$lib/utils';
+
+export const load = async ({ cookies, parent }) => {
+ let { user } = await parent();
+ let tickets = await get('/tickets');
+ let ticket = tickets.findIndex((t) => user.accounts.find((a) => a.asset === t));
+ let asset = tickets[ticket];
+ return { asset, ticket };
+};
diff --git a/src/routes/launch/ticket/+page.svelte b/src/routes/launch/ticket/+page.svelte
new file mode 100644
index 00000000..3879abcd
--- /dev/null
+++ b/src/routes/launch/ticket/+page.svelte
@@ -0,0 +1,35 @@
+<script>
+ export let data;
+ let { asset, ticket } = data;
+</script>
+
+<div class="mx-auto h-screen flex items-center justify-center px-4">
+ <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8">
+ <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center">
+ Your Ticket
+ </h1>
+
+ <p>
+ We've minted your ticket as an <a
+ href={`https://blockstream.info/liquid/asset/${asset}`}
+ class="underline">NFT on the Liquid Blockchain</a
+ > as a proof of purchase and commemorative token.
+ </p>
+
+ <img
+ src={`/tickets/${(ticket + 1).toString().padStart(2, '0')}.png`}
+ class="w-full max-w-[800px]"
+ alt="Ticket"
+ />
+
+ <div class="w-full flex">
+ <a href="/" class="mx-auto">
+ <button
+ class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl"
+ >
+ Done
+ </button>
+ </a>
+ </div>
+ </div>
+</div>
diff --git a/src/routes/login/+page.server.js b/src/routes/login/+page.server.js
index 94981ff7..b46af49e 100644
--- a/src/routes/login/+page.server.js
+++ b/src/routes/login/+page.server.js
@@ -4,15 +4,15 @@ import { login } from '$lib/utils';
export const actions = {
default: async ({ cookies, request }) => {
let form = Object.fromEntries(await request.formData());
- let { username, password, redirect: r } = form;
- let user = { username, password };
+ let { username, password, token, loginRedirect } = form;
+ let user = { username, password, token };
try {
- await login(user, cookies);
+ let r = await login(user, cookies);
} catch (e) {
- return invalid(400, { error: 'Login failed' });
+ return invalid(400, { error: 'Login failed', message: e.message, ...form });
}
- throw redirect(303, r || `/${user.username}/dashboard`);
+ throw redirect(303, loginRedirect || `/${user.username}/dashboard`);
}
};
diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte
index cf7cb517..75998cd0 100644
--- a/src/routes/login/+page.svelte
+++ b/src/routes/login/+page.svelte
@@ -1,5 +1,6 @@
<script>
import { RegisterOrLogin } from '$comp';
+
export let form;
</script>
diff --git a/src/routes/register/+page.server.js b/src/routes/register/+page.server.js
index bfe1a9b7..c5751d4c 100644
--- a/src/routes/register/+page.server.js
+++ b/src/routes/register/+page.server.js
@@ -4,7 +4,7 @@ import { post, login } from '$lib/utils';
export const actions = {
default: async ({ cookies, request }) => {
let form = Object.fromEntries(await request.formData());
- let { username, password, redirect: r } = form;
+ let { username, password, loginRedirect } = form;
let user = { username, password };
let error;
@@ -23,6 +23,6 @@ export const actions = {
}
if (error) return invalid(400, { error });
- throw redirect(307, r || `/${user.username}/dashboard`);
+ throw redirect(307, loginRedirect || `/${user.username}/dashboard`);
}
};
diff --git a/src/routes/register/+page.svelte b/src/routes/register/+page.svelte
index aa0e66e0..fd9e4417 100644
--- a/src/routes/register/+page.svelte
+++ b/src/routes/register/+page.svelte
@@ -5,9 +5,7 @@
afterNavigate(async () => {
try {
- console.log('whoa');
await invalidateAll();
- console.log('no');
} catch (e) {
console.log(e);
}
diff --git a/src/routes/scan/+page.svelte b/src/routes/scan/+page.svelte
index 1c14cb6d..dc6e291d 100644
--- a/src/routes/scan/+page.svelte
+++ b/src/routes/scan/+page.svelte
@@ -7,7 +7,7 @@
let scanner, vid;
onMount(() => {
- scanner = new QrScanner(vid, (text) => goto(`/send/${text}`));
+ scanner = new QrScanner(vid, (text) => goto(`/send/${text}`), { highlightScanRegion: true, highlightCodeOutline: true });
scanner.start();
});
diff --git a/src/routes/send/+layout.svelte b/src/routes/send/+layout.svelte
new file mode 100644
index 00000000..c9122466
--- /dev/null
+++ b/src/routes/send/+layout.svelte
@@ -0,0 +1,18 @@
+<script>
+ import { Pin } from '$comp';
+ import { pin } from '$lib/store';
+ import { onMount } from 'svelte';
+
+ export let data;
+
+ let loaded;
+ let { user } = data;
+
+ onMount(() => setTimeout(() => (loaded = true), 50));
+</script>
+
+{#if loaded && user.haspin && $pin?.length !== 6}
+ <Pin />
+{/if}
+
+<slot />
diff --git a/src/routes/send/[...text]/+page.server.js b/src/routes/send/[...text]/+page.server.js
index 45bb597d..b909ab4d 100644
--- a/src/routes/send/[...text]/+page.server.js
+++ b/src/routes/send/[...text]/+page.server.js
@@ -55,6 +55,6 @@ export const actions = {
const form = await request.formData();
await parse(form.get('text'));
- return invalid(403, { error: 'Failed to recognize input' });
+ return invalid(403, { error: 'Does not compute, try again' });
}
};
diff --git a/src/routes/send/[...text]/+page.svelte b/src/routes/send/[...text]/+page.svelte
index 5d6d123e..fe949617 100644
--- a/src/routes/send/[...text]/+page.svelte
+++ b/src/routes/send/[...text]/+page.svelte
@@ -92,8 +92,4 @@
line-height: 20px;
@apply block border rounded-2xl p-3 py-4 w-full;
}
-
- .expandable-expandable::-webkit-resizer {
- background: transparent;
- }
</style>
diff --git a/src/routes/send/[uuid=uuid]/+page.server.js b/src/routes/send/[uuid=uuid]/+page.server.js
index 661e9e7b..c9c77f70 100644
--- a/src/routes/send/[uuid=uuid]/+page.server.js
+++ b/src/routes/send/[uuid=uuid]/+page.server.js
@@ -10,14 +10,19 @@ export async function load({ params, parent }) {
export const actions = {
default: async ({ cookies, params, request }) => {
- let body = Object.fromEntries(await request.formData());
- let { confirmed } = body;
+ try {
+ let body = Object.fromEntries(await request.formData());
+ let { confirmed } = body;
- if (!confirmed) {
- return invalid(403, { amount, confirm: true });
+ if (!confirmed) {
+ return invalid(403, { amount, confirm: true });
+ }
+
+ await post('/send', body, auth(cookies));
+ } catch (e) {
+ return invalid(400, { message: e.message });
}
- await post('/send', body, auth(cookies));
throw redirect(307, '/sent');
}
};
diff --git a/src/routes/send/[uuid=uuid]/+page.svelte b/src/routes/send/[uuid=uuid]/+page.svelte
index aa798bc1..73712f71 100644
--- a/src/routes/send/[uuid=uuid]/+page.svelte
+++ b/src/routes/send/[uuid=uuid]/+page.svelte
@@ -1,6 +1,6 @@
<script>
import { goto } from '$app/navigation';
- import { selectedRate } from '$lib/store';
+ import { pin, selectedRate } from '$lib/store';
import { enhance } from '$app/forms';
import { Icon, Numpad, Spinner } from '$comp';
import { page } from '$app/stores';
@@ -31,12 +31,24 @@
let loading;
let submit = () => (loading = true);
+
+ $: update(form);
+ let update = () => {
+ if (form?.message.includes('pin')) $pin = undefined;
+ loading = false;
+ };
</script>
<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={handleBack}>
<Icon icon="arrow-left" style="w-10" />
</button>
+{#if form?.message}
+ <div class="text-red-600 text-center">
+ {form.message}
+ </div>
+{/if}
+
<div class="container px-4 mt-20 max-w-xl mx-auto">
{#if amount}
<div class="text-center mb-8">
@@ -61,6 +73,7 @@
<input name="payreq" value={payreq} type="hidden" />
<input name="username" value={recipient.username} type="hidden" />
<input name="confirmed" value={form?.confirm} type="hidden" />
+ <input name="pin" value={$pin} type="hidden" />
<div class="flex w-full">
{#if amount}
diff --git a/src/routes/send/bitcoin/[address]/+page.server.js b/src/routes/send/bitcoin/[address]/+page.server.js
index 9b29a6da..642593ab 100644
--- a/src/routes/send/bitcoin/[address]/+page.server.js
+++ b/src/routes/send/bitcoin/[address]/+page.server.js
@@ -1,11 +1,16 @@
-import { redirect } from '@sveltejs/kit';
+import { invalid, redirect } from '@sveltejs/kit';
import { auth, post } from '$lib/utils';
export const actions = {
default: async ({ cookies, request }) => {
- let { address, amount } = Object.fromEntries(await request.formData());
- let { tx } = await post('/bitcoin/fee', { address, amount }, auth(cookies));
- await post('/bitcoin/send', { address, tx }, auth(cookies));
+ try {
+ let { address, amount, pin } = Object.fromEntries(await request.formData());
+ let { tx } = await post('/bitcoin/fee', { address, amount }, auth(cookies));
+ await post('/bitcoin/send', { address, pin, tx }, auth(cookies));
+ } catch (e) {
+ return invalid(400, { message: e.message });
+ }
+
throw redirect(307, '/sent');
}
};
diff --git a/src/routes/send/bitcoin/[address]/+page.svelte b/src/routes/send/bitcoin/[address]/+page.svelte
index 8c9e72ff..c9023180 100644
--- a/src/routes/send/bitcoin/[address]/+page.svelte
+++ b/src/routes/send/bitcoin/[address]/+page.svelte
@@ -3,19 +3,33 @@
import { Icon, Numpad, Spinner } from '$comp';
import { page } from '$app/stores';
import { back } from '$lib/utils';
+ import { pin } from '$lib/store';
+
export let data;
+ export let form;
let { address } = $page.params;
let { currency } = data.user;
let amount, loading;
let submit = () => (loading = true);
+ $: update(form);
+ let update = () => {
+ if (form?.message.includes('pin')) $pin = undefined;
+ loading = false;
+ };
</script>
<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}>
<Icon icon="arrow-left" style="w-10" />
</button>
+{#if form?.message}
+ <div class="text-red-600 text-center">
+ {form.message}
+ </div>
+{/if}
+
<div class="container px-4 mt-20 max-w-xl mx-auto">
<div class="text-center mb-8">
<h1 class="text-3xl md:text-4xl font-semibold mb-2">Send to</h1>
@@ -26,6 +40,8 @@
<form method="POST" use:enhance on:submit={submit}>
<input name="address" value={address} type="hidden" />
<input name="amount" value={amount} type="hidden" />
+ <input name="pin" value={$pin} type="hidden" />
+
<div class="flex w-full">
<button
type="submit"
diff --git a/src/routes/send/lightning/[payreq]/+page.server.js b/src/routes/send/lightning/[payreq]/+page.server.js
index f17d6a60..49ebac1c 100644
--- a/src/routes/send/lightning/[payreq]/+page.server.js
+++ b/src/routes/send/lightning/[payreq]/+page.server.js
@@ -7,14 +7,20 @@ export async function load({ params }) {
export const actions = {
default: async ({ cookies, request }) => {
- let body = Object.fromEntries(await request.formData());
- let { amount, confirmed } = body;
+ try {
+ let body = Object.fromEntries(await request.formData());
+ let { amount, confirmed } = body;
- if (!confirmed) {
- return invalid(403, { amount, confirm: true });
+ if (!confirmed) {
+ return invalid(403, { amount, confirm: true });
+ }
+
+ await post('/lightning/send', body, auth(cookies));
+ } catch (e) {
+ if (e.message.includes("unusable")) e.message = "Failed to route payment, try sending a lower amount"
+ return invalid(400, { message: e.message });
}
- await post('/lightning/send', body, auth(cookies));
throw redirect(307, '/sent');
}
};
diff --git a/src/routes/send/lightning/[payreq]/+page.svelte b/src/routes/send/lightning/[payreq]/+page.svelte
index 973550f6..bda10038 100644
--- a/src/routes/send/lightning/[payreq]/+page.svelte
+++ b/src/routes/send/lightning/[payreq]/+page.svelte
@@ -3,6 +3,8 @@
import { Icon, Numpad, Spinner } from '$comp';
import { page } from '$app/stores';
import { back, s } from '$lib/utils';
+ import { pin } from '$lib/store';
+
export let data;
export let form;
@@ -14,12 +16,23 @@
let loading;
let submit = () => (loading = true);
+ $: update(form);
+ let update = () => {
+ if (form?.message.includes('pin')) $pin = undefined;
+ loading = false;
+ };
</script>
<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}>
<Icon icon="arrow-left" style="w-10" />
</button>
+{#if form?.message}
+ <div class="text-red-600 text-center">
+ {form.message}
+ </div>
+{/if}
+
<div class="container px-4 mt-20 max-w-xl mx-auto">
{#if amount}
<div class="text-center mb-8">
@@ -38,6 +51,7 @@
<input name="payreq" value={payreq} type="hidden" />
<input name="amount" value={amount} type="hidden" />
<input name="confirmed" value={form?.confirm} type="hidden" />
+ <input name="pin" value={$pin} type="hidden" />
<div class="flex w-full">
<button
diff --git a/src/routes/send/user/[username]/[...amount]/+page.server.js b/src/routes/send/user/[username]/[...amount]/+page.server.js
index 12bae186..a659eef3 100644
--- a/src/routes/send/user/[username]/[...amount]/+page.server.js
+++ b/src/routes/send/user/[username]/[...amount]/+page.server.js
@@ -11,14 +11,19 @@ export async function load({ params, parent }) {
export const actions = {
default: async ({ cookies, request }) => {
- let body = Object.fromEntries(await request.formData());
- let { amount, confirmed } = body;
+ try {
+ let body = Object.fromEntries(await request.formData());
+ let { amount, confirmed } = body;
- if (!confirmed) {
- return invalid(403, { amount, confirm: true });
+ if (!confirmed) {
+ return invalid(403, { amount, confirm: true });
+ }
+
+ await post('/send', body, auth(cookies));
+ } catch (e) {
+ return invalid(400, { message: e.message });
}
- await post('/send', body, auth(cookies));
throw redirect(307, '/sent');
}
};
diff --git a/src/routes/send/user/[username]/[...amount]/+page.svelte b/src/routes/send/user/[username]/[...amount]/+page.svelte
index e50acd5b..6f4cb313 100644
--- a/src/routes/send/user/[username]/[...amount]/+page.svelte
+++ b/src/routes/send/user/[username]/[...amount]/+page.svelte
@@ -1,10 +1,11 @@
<script>
import { goto } from '$app/navigation';
- import { selectedRate } from '$lib/store';
+ import { pin, selectedRate } from '$lib/store';
import { enhance } from '$app/forms';
import { Avatar, Icon, Numpad, Spinner } from '$comp';
import { page } from '$app/stores';
import { back, f, s, sats } from '$lib/utils';
+
export let data;
export let form;
@@ -31,12 +32,24 @@
let loading;
let submit = () => (loading = true);
+
+ $: update(form);
+ let update = () => {
+ if (form?.message.includes('pin')) $pin = undefined;
+ loading = false;
+ };
</script>
<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={handleBack}>
<Icon icon="arrow-left" style="w-10" />
</button>
+{#if form?.message}
+ <div class="text-red-600 text-center">
+ {form.message}
+ </div>
+{/if}
+
<div class="container px-4 mt-20 max-w-xl mx-auto">
{#if amount}
<div class="text-center mb-8">
@@ -63,6 +76,7 @@
<input name="amount" value={amount} type="hidden" />
<input name="username" value={recipient.username} type="hidden" />
<input name="confirmed" value={form?.confirm} type="hidden" />
+ <input name="pin" value={$pin} type="hidden" />
<div class="flex w-full">
{#if amount}
diff --git a/src/routes/user/+server.js b/src/routes/user/+server.js
index c9490984..b9c37e6e 100644
--- a/src/routes/user/+server.js
+++ b/src/routes/user/+server.js
@@ -1,5 +1,4 @@
import { auth, post } from '$lib/utils';
-import cookie from 'cookie';
export async function PUT({ cookies, request }) {
let user = await request.json();