aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdrien Hopkins <[email protected]>2022-08-08 07:42:39 -0500
committerAdrien Hopkins <[email protected]>2022-08-08 07:42:39 -0500
commitf7aaa86c851d456a3426157bc04cdbfeecd1716b (patch)
tree09892fccf026827fade28f4d76922fdcd514db64 /src
parentffc36d48b11045708967637832d959cade58b8eb (diff)
parent93f9d0abf58c5b5154e35b63524405554042bef1 (diff)
downloadcoinos-ui-f7aaa86c851d456a3426157bc04cdbfeecd1716b.tar.gz
coinos-ui-f7aaa86c851d456a3426157bc04cdbfeecd1716b.zip
Merge branch 'master' into i18n
Diffstat (limited to 'src')
-rw-r--r--src/components/404.svelte4
-rw-r--r--src/components/About.svelte8
-rw-r--r--src/components/AppHeader.svelte69
-rw-r--r--src/components/Footer.svelte1
-rw-r--r--src/components/LandingHeader.svelte4
-rw-r--r--src/components/LandingHero.svelte4
-rw-r--r--src/components/RegisterOrLogin.svelte5
-rw-r--r--src/components/Toggle.svelte12
-rw-r--r--src/components/index.js1
-rw-r--r--src/lib/socket.js13
-rw-r--r--src/lib/store.js18
-rw-r--r--src/lib/utils.js53
-rw-r--r--src/routes/[username]/dashboard.svelte120
-rw-r--r--src/routes/[username]/index.svelte34
-rw-r--r--src/routes/[username]/receive.svelte22
-rw-r--r--src/routes/[username]/settings.svelte170
-rw-r--r--src/routes/[username]/support.svelte60
-rw-r--r--src/routes/[username]/transactions.svelte57
-rw-r--r--src/routes/__error.svelte8
-rw-r--r--src/routes/__layout.svelte7
-rw-r--r--src/routes/community.svelte0
-rw-r--r--src/routes/index.svelte2
-rw-r--r--src/routes/invoice/[id].svelte30
-rw-r--r--src/routes/jobs.svelte32
-rw-r--r--src/routes/support.svelte5
-rw-r--r--src/routes/withdraw.js9
26 files changed, 619 insertions, 129 deletions
diff --git a/src/components/404.svelte b/src/components/404.svelte
index 503316bb..426f1f96 100644
--- a/src/components/404.svelte
+++ b/src/components/404.svelte
@@ -5,10 +5,10 @@
<div class="flex justify-center items-center mt-20">
<div class="space-y-5 text-center">
<a href="/">
- <Icon icon="logo" />
+ <Icon icon="logo" style="mx-auto" />
</a>
- <Icon icon="question" style="mx-auto w-full" />
+ <Icon icon="question" style="mx-auto w-56" />
<h1 class="font-bold text-5xl">404</h1>
<p>Page not found.</p>
diff --git a/src/components/About.svelte b/src/components/About.svelte
index 3731bf9b..84e1761b 100644
--- a/src/components/About.svelte
+++ b/src/components/About.svelte
@@ -2,10 +2,10 @@
import { Image, Icon } from '$comp';
const aboutAdam = [
- 'Running Bitcoin since 2011',
+ 'Running bitcoin since 2011',
'20+ years web development experience in government and industry',
- 'Early Bitcoin Vancouver meetup organizer',
- 'Founder of The Bitcoin Co-op and early supporter of Decentral Vancouver',
+ 'Early bitcoin Vancouver meetup organizer',
+ 'Founder of The bitcoin Co-op and early supporter of Decentral Vancouver',
'Yogi, permaculturist, anarchist, decentralist',
'Husband, father, brother, son'
];
@@ -21,7 +21,7 @@
as a way to provide local merchants with a convenient way to accept bitcoin payments.
<br /><br />
The feature set has grown over the years to accomodate a wide variety of use cases with the goal
- of making it as easy as possible for anyone to get started using Bitcoin quickly, cheaply, and securely.
+ of making it as easy as possible for anyone to get started using bitcoin quickly, cheaply, and securely.
<br /><br />
</p>
<p class="w-full lg:w-5/12">
diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte
index 242e6924..9ebf28bf 100644
--- a/src/components/AppHeader.svelte
+++ b/src/components/AppHeader.svelte
@@ -1,14 +1,15 @@
<script>
// we will want to dynamically use the users avatar on this screen if they have uploaded one
- import { user } from '$lib/store';
+ import OutClick from 'svelte-outclick';
+ import { user, newPayment, colorTheme } from '$lib/store';
import { goto } from '$app/navigation';
import { Icon, LocaleSelector } from '$comp';
export let avatarPosition = 'left-[calc(50vw-48px)]';
const menuButtons = [
- { title: 'Customers', icon: 'customers', goto: 'customers' },
+ // { title: 'Customers', icon: 'customers', goto: 'customers' },
{ title: 'Dashboard', icon: 'dash', goto: 'dashboard' },
{ title: 'Settings', icon: 'settings', goto: 'settings' },
{ title: 'Support', icon: 'support', goto: 'support' },
@@ -21,12 +22,13 @@
if (item === 'logout') {
goto('/logout');
} else {
+ showMenu = false;
goto(`/${$user.username + '/' + item}`);
}
};
</script>
-<header class="bg-gradient-to-r from-primary to-gradient h-[175px] w-full relative">
+<header class="bg-gradient-to-r {$colorTheme} h-[175px] w-full relative">
<nav class="flex justify-end items-center space-x-4 p-5">
<LocaleSelector />
{#if $user}
@@ -36,36 +38,51 @@
</button>
</a>
<a href={`/${$user.username}/transactions`}>
- <button class="bg-white p-2 rounded-full w-12 h-12"
+ <button class="bg-white p-2 rounded-full w-12 h-12 relative"
><Icon icon="clock" style="mx-auto" />
+ {#if $newPayment}
+ <span class="absolute top-0 right-0">
+ <span class="flex h-3 w-3">
+ <span
+ class="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#F7931A] opacity-75"
+ />
+ <span class="relative inline-flex rounded-full h-3 w-3 bg-[#F7931A]" />
+ </span>
+ </span>
+ {/if}
</button>
</a>
<a href={`/${$user.username}`}>
- <button class="bg-white p-2 rounded-full w-12 h-12"
+ <button class="bg-white p-2 rounded-full w-12 h-12"
><Icon icon="profile" style="mx-auto" />
</button>
</a>
<div class="relative">
- <button class="bg-white p-2 rounded-full w-12 h-12" on:click={() => (showMenu = !showMenu)}
- ><Icon icon="menu" style="mx-auto" />
- </button>
- <div
- class="{showMenu
- ? 'block'
- : 'hidden'} absolute top-14 right-0 bg-white rounded-3xl p-8 shadow-xl z"
- >
- <ul class="space-y-5 w-32">
- {#each menuButtons as button}
- <li>
- <button
- class="flex justify-center items-center font-semibold text-sm"
- on:click={() => handleMenuItemClick(button.goto)}
- ><Icon icon={button.icon} style="mr-1" /> {button.title}
- </button>
- </li>
- {/each}
- </ul>
- </div>
+ <OutClick on:outclick={() => (showMenu = false)}>
+ <button
+ class="bg-white p-2 rounded-full w-12 h-12"
+ on:click={() => (showMenu = !showMenu)}
+ ><Icon icon="menu" style="mx-auto" />
+ </button>
+
+ <div
+ class="{showMenu
+ ? 'block'
+ : 'hidden'} absolute top-14 right-0 bg-white rounded-3xl p-8 shadow-xl z"
+ >
+ <ul class="space-y-5 w-32">
+ {#each menuButtons as button}
+ <li>
+ <button
+ class="flex justify-center items-center font-semibold text-sm"
+ on:click={() => handleMenuItemClick(button.goto)}
+ ><Icon icon={button.icon} style="mr-1" /> {button.title}
+ </button>
+ </li>
+ {/each}
+ </ul>
+ </div>
+ </OutClick>
</div>
{:else}
<a href="/login">
@@ -75,7 +92,7 @@
>>>>>>> master
</nav>
<div
- class="absolute top-[calc(175px-48px)] {avatarPosition} rounded-full border-4 border-white p-4 bg-gradient-to-r from-primary to-gradient w-24 h-24"
+ class="absolute top-[calc(175px-48px)] {avatarPosition} rounded-full border-4 border-white p-4 bg-gradient-to-r {$colorTheme} w-24 h-24"
>
<Icon icon="logo-symbol-white" style="mx-auto" />
</div>
diff --git a/src/components/Footer.svelte b/src/components/Footer.svelte
index 0c682179..f1de0173 100644
--- a/src/components/Footer.svelte
+++ b/src/components/Footer.svelte
@@ -9,7 +9,6 @@
const companyLinks = [
{ url: 'mailto:[email protected]', title: 'Support' },
- { url: '/community', title: 'Community' },
{ url: '/jobs', title: 'Jobs' }
];
</script>
diff --git a/src/components/LandingHeader.svelte b/src/components/LandingHeader.svelte
index 352b200c..9c4d43a8 100644
--- a/src/components/LandingHeader.svelte
+++ b/src/components/LandingHeader.svelte
@@ -16,9 +16,7 @@
};
</script>
-<header
- class="w-full lg:w-5/6 py-5 xl:pb-0 xl:pt-10 mx-auto fixed md:sticky z-10 top-0 bg-white/90"
->
+<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">
<a href="/" class="hidden md:block">
<Icon icon="logo" style="mx-auto mb-5 xl:mb-0" />
diff --git a/src/components/LandingHero.svelte b/src/components/LandingHero.svelte
index 93bea8c2..750f5626 100644
--- a/src/components/LandingHero.svelte
+++ b/src/components/LandingHero.svelte
@@ -12,12 +12,12 @@
The <span class="relative">
easiest
<Icon icon="rays" style="absolute -top-16 left-5 md:left-8 lg:left-14 2xl:left-24" />
- </span> way to accept Bitcoin in your store.
+ </span> way to accept bitcoin in your store.
</h1>
<h2
class="text-center text-secondary w-full md:w-3/5 lg:w-2/5 max-w-3xl mx-auto text-xl leading-normal font-medium"
>
- Grow your customer base and revenue by accepting Bitcoin with an easy-to-use and managed Point
+ Grow your customer base and revenue by accepting bitcoin with an easy-to-use and managed Point
of Sale. No software or hardware required.
</h2>
{#if !$user}
diff --git a/src/components/RegisterOrLogin.svelte b/src/components/RegisterOrLogin.svelte
index 157c2821..eade96d8 100644
--- a/src/components/RegisterOrLogin.svelte
+++ b/src/components/RegisterOrLogin.svelte
@@ -1,7 +1,6 @@
<script>
- import { toast } from '@zerodevx/svelte-toast';
import { Icon } from '$comp';
- import { post } from '$lib/utils';
+ import { post, failure } from '$lib/utils';
import { user, token } from '$lib/store';
import { auth } from '$lib/socket';
import { goto } from '$app/navigation';
@@ -26,7 +25,7 @@
auth();
} catch (e) {
if (!e.message) e.message = 'Login failed';
- toast.push(e.message);
+ failure(e.message);
}
};
diff --git a/src/components/Toggle.svelte b/src/components/Toggle.svelte
new file mode 100644
index 00000000..85fd08d5
--- /dev/null
+++ b/src/components/Toggle.svelte
@@ -0,0 +1,12 @@
+<script>
+ export let id;
+ export let checked;
+ export let value;
+</script>
+
+<label for={id} class="inline-flex relative items-center mr-5 cursor-pointer">
+ <input type="checkbox" {value} {checked} {id} class="sr-only peer" />
+ <div
+ class="w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-focus:ring-4 peer-focus:ring-green-300 dark:peer-focus:ring-green-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-green-600"
+ />
+</label>
diff --git a/src/components/index.js b/src/components/index.js
index 1d5b369d..39a4e5e7 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -12,3 +12,4 @@ export { default as LoadingSplash } from './LoadingSplash.svelte';
export { default as PageNotFound } from './404.svelte';
export { default as About } from './About.svelte';
export { default as Footer } from './Footer.svelte';
+export { default as Toggle } from './Toggle.svelte';
diff --git a/src/lib/socket.js b/src/lib/socket.js
index 66091d76..a30d4376 100644
--- a/src/lib/socket.js
+++ b/src/lib/socket.js
@@ -1,5 +1,6 @@
import { get } from 'svelte/store';
-import { rate, user, token, invoices, preferredCurrency } from '$lib/store';
+import { rate, user, token, invoices, preferredCurrency, newPayment } from '$lib/store';
+import { success } from '$lib/utils';
let interval, socket;
@@ -18,14 +19,20 @@ export const messages = (data) => ({
},
payment() {
- invoices.set({ ...get(invoices), [data.invoice.uuid]: data.invoice });
+ if (data.invoice) invoices.set({ ...get(invoices), [data.invoice.uuid]: data.invoice });
+ newPayment.set(true);
+ if (data.amount > 0) {
+ success(`Received ${data.amount} sats!`);
+ } else {
+ success(`Sent ${-data.amount} sats!`);
+ }
},
connected: auth,
login() {
user.set(data);
- preferredCurrency.set(data.currency);
+ preferredCurrency.set('USD');
}
});
diff --git a/src/lib/store.js b/src/lib/store.js
index b20fd10f..2693e945 100644
--- a/src/lib/store.js
+++ b/src/lib/store.js
@@ -1,7 +1,7 @@
import { get, writable } from 'svelte/store';
import { browser } from '$app/env';
-const persisted = (k, i) => {
+const persistSession = (k, i) => {
let s = writable(
browser && sessionStorage.getItem(k) && sessionStorage.getItem(k) !== 'undefined'
? JSON.parse(sessionStorage.getItem(k))
@@ -13,10 +13,24 @@ const persisted = (k, i) => {
return s;
};
+const persistLocal = (k, i) => {
+ let s = writable(
+ browser && localStorage.getItem(k) && localStorage.getItem(k) !== 'undefined'
+ ? JSON.parse(localStorage.getItem(k))
+ : i
+ );
+
+ s.subscribe((v) => browser && localStorage.setItem(k, JSON.stringify(v)));
+
+ return s;
+};
+
export const rate = writable();
export const user = writable();
export const invoiceAmount = writable();
export const invoiceAmountFiat = writable();
-export const token = persisted('token');
+export const token = persistSession('token');
export const invoices = writable({});
export const preferredCurrency = writable();
+export const newPayment = persistLocal();
+export const colorTheme = writable('from-primary to-gradient');
diff --git a/src/lib/utils.js b/src/lib/utils.js
index 669b2b5e..b42fe970 100644
--- a/src/lib/utils.js
+++ b/src/lib/utils.js
@@ -1,4 +1,5 @@
import { browser } from '$app/env';
+import { toast } from '@zerodevx/svelte-toast';
export function scroll(section) {
section.scrollIntoView({ behavior: 'smooth' });
@@ -17,11 +18,12 @@ export const get = (url, headers = { accept: 'application/json' }) =>
}
});
-export const post = (url, body) =>
- fetch(base + url, {
+export const post = (url, body, headers) => {
+ headers = { ...headers, accept: 'application/json', 'content-type': 'application/json' };
+ return fetch(base + url, {
method: 'POST',
body: JSON.stringify(body),
- headers: { accept: 'application/json', 'content-type': 'application/json' }
+ headers
})
.then((r) => r.text())
.then((body) => {
@@ -36,9 +38,11 @@ export const post = (url, body) =>
return body;
});
+};
export const copy = (text) => {
navigator.clipboard.writeText(text);
+ success('Copied!');
};
export function reverseFormat(val, locale) {
@@ -50,4 +54,45 @@ export function reverseFormat(val, locale) {
return Number.isNaN(reversedVal) ? 0 : +reversedVal;
}
-export let protectedRoutes = [/receive/, /customers/, /dashboard/, /settings/, /transactions/];
+export let protectedRoutes = [
+ /receive/,
+ /customers/,
+ /dashboard/,
+ /settings/,
+ /transactions/,
+ /support/
+];
+
+export const success = (m) => {
+ toast.pop();
+ toast.push(m, {
+ theme: {
+ '--toastBarBackground': '#16A34A'
+ }
+ });
+};
+
+export const warning = (m) => {
+ toast.pop();
+ toast.push(m, {
+ theme: {
+ '--toastBarBackground': '#FFCE22'
+ }
+ });
+};
+export const failure = (m) => {
+ toast.pop();
+ toast.push(m, {
+ theme: {
+ '--toastBarBackground': '#E93535'
+ }
+ });
+};
+export const info = (m) => {
+ toast.pop();
+ toast.push(m, {
+ theme: {
+ '--toastBarBackground': '#1C69FF'
+ }
+ });
+};
diff --git a/src/routes/[username]/dashboard.svelte b/src/routes/[username]/dashboard.svelte
index 1935b156..d056399f 100644
--- a/src/routes/[username]/dashboard.svelte
+++ b/src/routes/[username]/dashboard.svelte
@@ -1,5 +1,121 @@
<script>
- import { AppHeader } from '$comp';
+ import { post, failure } from '$lib/utils';
+ import { tick } from 'svelte';
+ import { AppHeader, Icon } from '$comp';
+ import { rate, user, preferredCurrency } from '$lib/store';
+
+ let payreq = '',
+ payreqField,
+ withdrawing;
+
+ let toggle = async () => {
+ withdrawing = !withdrawing;
+ await tick();
+ if (withdrawing) payreqField.focus();
+ };
+
+ let withdraw = async () => {
+ try {
+ await post('/withdraw', { payreq });
+ withdrawing = false;
+ } catch (e) {
+ failure(e.message);
+ }
+ };
+
+ $: btcPrice = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD'
+ }).format($rate);
+
+ $: accountBalanceFiat = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD'
+ }).format($user.account.balance * ($rate / 100000000));
+
+ $: accountBalanceBtc = $user.account.balance / 100000000;
+
+ $: accountBalanceSats = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(
+ $user.account.balance
+ );
+
+ $: feepayreq = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD'
+ }).format(($user.account.balance * ($rate / 100000000)) / 100);
</script>
-<AppHeader />
+{#if $user}
+ <AppHeader avatarPosition="left-[calc(50vw-48px)] lg:left-[calc(15vw-48px)]" />
+
+ <div
+ class="text-center mx-auto lg:text-left lg:mx-0 lg:ml-[calc(15vw-48px)] mt-12 w-full md:w-72"
+ >
+ <h2 class="text-2xl font-semibold">{$user.username}</h2>
+ <span class="text-secondary">Address goes here</span>
+ </div>
+
+ <div class="px-3 md:px-0 flex justify-center items-center mt-10 lg:mt-0 mb-20">
+ <div>
+ <h1 class="text-3xl md:text-4xl font-semibold mb-8">
+ {withdrawing ? 'Withdraw' : 'Dashboard'}
+ </h1>
+ {#if !withdrawing}
+ <span class="text-secondary mx-auto text-lg font-bold"
+ >Bitcoin Price: <span class="text-black">{$rate ? btcPrice : 'fetching rate...'}</span>
+ {$preferredCurrency}
+ </span>
+
+ <span class="text-secondary block mt-1"
+ >Data from <span class="text-black">Binance</span>.
+ </span>
+
+ <h3 class="text-secondary font-bold border-b pb-1 mt-10 mb-6 w-full md:w-[500px]">
+ ACCOUNT BALANCE
+ </h3>
+
+ <span class="text-3xl font-bold block mb-1"
+ >{$rate ? accountBalanceFiat : 'fetching rate...'}</span
+ >
+
+ <span class="text-secondary text-xl block">{accountBalanceSats} SATS</span>
+
+ <button class="rounded-full border py-2 font-bold w-28 mt-4" on:click={toggle}
+ >Withdraw</button
+ >
+ <p class="text-secondary my-2">You will be charged a 1% withdrawal fee on the amount.</p>
+ {:else}
+ <div class="space-y-3 w-[250px] md:w-[500px] md:pr-20">
+ <button on:click={() => (withdrawing = false)}>
+ <Icon icon="arrow-left" style="w-8" />
+ </button>
+
+ <div>
+ <label for="invoice" class="font-bold mb-1 block">Invoice</label>
+ <input
+ bind:this={payreqField}
+ type="text"
+ name="payreq"
+ required
+ bind:value={payreq}
+ class="block rounded-2xl p-3 w-full bg-primary"
+ />
+ </div>
+
+ <div>
+ <label for="note" class="font-bold mb-1 block">Note (Optional)</label>
+ <textarea name="note" rows={2} class="block rounded-2xl p-3 w-full bg-primary" />
+ </div>
+
+ <button
+ disabled={!payreq}
+ class="{!payreq
+ ? 'opacity-50'
+ : 'opacity-100'} rounded-2xl border py-3 font-bold w-full mt-2 bg-black text-white"
+ on:click={withdraw}>Send</button
+ >
+ </div>
+ {/if}
+ </div>
+ </div>
+{/if}
diff --git a/src/routes/[username]/index.svelte b/src/routes/[username]/index.svelte
index 6002efdc..41d60849 100644
--- a/src/routes/[username]/index.svelte
+++ b/src/routes/[username]/index.svelte
@@ -1,5 +1,4 @@
<script>
- import { toast } from '@zerodevx/svelte-toast';
import { browser } from '$app/env';
import { onMount } from 'svelte';
import { AppHeader, Icon, PageNotFound } from '$comp';
@@ -14,7 +13,7 @@
let { default: QRCodeStyling } = await import('qr-code-styling');
const qrCode = new QRCodeStyling({
width: window.screen.width < 640 ? 250 : 300,
- type: 'svg',
+ type: 'canvas',
data: 'add static qr here',
image: '/images/invoice.svg',
backgroundOptions: {
@@ -33,18 +32,6 @@
});
let followed = false;
-
- let qrSkeleton = true;
- setTimeout(() => (qrSkeleton = false), 100);
-
- const handleCopy = () => {
- copy(lightningAddress);
- toast.push('Copied!', {
- theme: {
- '--toastBarBackground': '#2F855A'
- }
- });
- };
</script>
{#if username}
@@ -57,33 +44,30 @@
<p class="text-secondary">Address goes here</p>
- <button
+ <!--
+ we need to hook this up
+ <button
class="border rounded-full py-2 w-28 font-semibold flex justify-center items-center text-sm"
on:click={() => (followed = !followed)}
><Icon icon={followed ? 'minus' : 'plus'} style="mr-1" />
{followed ? 'Remove' : 'Follow'}
</button>
+ -->
</div>
<div class="space-y-5 static xl:absolute top-[255px] left-[calc(50vw-150px)]">
<div
id="qr"
- class="border border-lightgrey rounded-3xl flex md:p-5 justify-center items-center relative"
- >
- <div
- class="z-100 h-[300px] md:h-[342px] w-[250px] md:w-[342px] animate-pulse absolute top-0 left-0 bg-gray-400 rounded-3xl {qrSkeleton
- ? 'block'
- : 'hidden'}"
- />
- </div>
+ class="w-[292px] md:w-[342px] h-[342px] border border-lightgrey rounded-3xl flex p-5 justify-center items-center relative"
+ />
- <p class="text-secondary text-center font-semibold">Scan to pay with Bitcoin</p>
+ <p class="text-secondary text-center font-semibold">Scan to pay with bitcoin</p>
<div
class="bg-primary font-semibold rounded-xl text-sm py-2 px-3 flex flex-wrap justify-center items-center"
>
<span>{lightningAddress}</span>
- <button on:click={handleCopy}>
+ <button on:click={() => copy(lightningAddress)}>
<Icon icon="copy" style="ml-2" />
</button>
</div>
diff --git a/src/routes/[username]/receive.svelte b/src/routes/[username]/receive.svelte
index 50fddbed..f0fa0fbd 100644
--- a/src/routes/[username]/receive.svelte
+++ b/src/routes/[username]/receive.svelte
@@ -3,13 +3,12 @@
import { AppHeader, Icon } from '$comp';
import { goto } from '$app/navigation';
import { invoiceAmount, invoiceAmountFiat, rate, user } from '$lib/store';
- import { post } from '$lib/utils';
+ import { post, warning } from '$lib/utils';
import { page } from '$app/stores';
let useFiat = true;
let amountFiat = 0;
let amountSats = 0;
- let message = '';
$: amount = useFiat ? Math.round(amountFiat / ($rate / 100000000)) : amountSats;
@@ -33,9 +32,6 @@
const numPad = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0', '<'];
const handleInput = (value) => {
- if (message) {
- message = '';
- }
if (useFiat) {
if (amountFiat === 0 && value !== '.' && value !== '<' && value !== '0') {
amountFiat = value;
@@ -54,7 +50,7 @@
}
}
} else if (value !== '.' && value !== '<' && parseInt(amountFiat + value) > $rate) {
- message = 'Please enter an amount less than 1 bitcoin.';
+ warning('Please enter an amount less than 1 bitcoin.');
} else if (amountFiat !== 0 && amountFiat.match(/\.../)) {
return;
} else {
@@ -74,7 +70,7 @@
}
}
} else if (value !== '<' && parseInt(amountSats + value) > 100000000) {
- message = 'Please enter an amount less than 1 bitcoin.';
+ warning('Please enter an amount less than 1 bitcoin.');
} else {
amountSats = amountSats + value;
}
@@ -118,16 +114,6 @@
>
</div>
- <!-- error message -->
- {#if message}
- <div
- class="text-orange-500 text-sm text-center font-bold bg-primary p-2 rounded-xl flex justify-center items-center space-x-1"
- >
- <Icon icon="info" style="w-5" />
- <p>{message}</p>
- </div>
- {/if}
-
<!-- numpad -->
<div class="grid grid-cols-3 gap-2 w-full md:w-[300px] mx-auto">
{#each numPad as value}
@@ -172,10 +158,12 @@
on:click={submit}
disabled={$invoiceAmount === 0}><Icon icon="qr" style="mr-2" /> Show QR</button
>
+ <!--
<button
class="bg-primary rounded-xl w-full h-[48px] flex justify-center items-center font-semibold text-sm"
disabled={true}><Icon icon="send" style="mr-2" /> Send Invoice</button
>
+ -->
</div>
</div>
{:else}
diff --git a/src/routes/[username]/settings.svelte b/src/routes/[username]/settings.svelte
index 1935b156..5abe58e0 100644
--- a/src/routes/[username]/settings.svelte
+++ b/src/routes/[username]/settings.svelte
@@ -1,5 +1,171 @@
<script>
- import { AppHeader } from '$comp';
+ import { AppHeader, Icon, Toggle } from '$comp';
+ import { user, colorTheme } from '$lib/store';
+
+ let setting = 'account';
+ let password;
+ let revealPassword = false;
+
+ const colorThemes = [
+ { theme: 1, color1: 'from-[#F5F7FA]', color2: 'to-[#C3CFE2]' },
+ { theme: 2, color1: 'from-[#FDFCFB]', color2: 'to-[#E2D1C3]' },
+ { theme: 3, color1: 'from-[#E6E9F0]', color2: 'to-[#EEF1F5]' },
+ { theme: 4, color1: 'from-[#D5D4D0]', color2: 'to-[#EEEEEC]' },
+ { theme: 5, color1: 'from-[#F3E7E9]', color2: 'to-[#E3EEFF]' },
+ { theme: 6, color1: 'from-[#F5F7FA]', color2: 'via-[#EAF2FF] to-[#DEDFFF]' }
+ ];
+ // need to update this to use the new backend field
+ let selectedTheme = 1;
+
+ const handleThemeClick = (colors) => {
+ selectedTheme = colors.theme;
+ $colorTheme = colors.color1 + ' ' + colors.color2;
+ };
</script>
-<AppHeader />
+{#if $user}
+ <AppHeader />
+
+ <div class="my-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">Settings</h1>
+
+ <div class="font-bold flex justify-between items-center border-b pb-3 text-secondary">
+ <button class:selected={setting === 'account'} on:click={() => (setting = 'account')}
+ >ACCOUNT</button
+ >
+
+ <button class:selected={setting === 'pos'} on:click={() => (setting = 'pos')}
+ >POINT OF SALE</button
+ >
+
+ <button class:selected={setting === 'security'} on:click={() => (setting = 'security')}
+ >SECURITY</button
+ >
+ </div>
+
+ {#if setting === 'account'}
+ <div>
+ <label for="username" class="font-bold mb-1 block">Username</label>
+ <input
+ type="text"
+ disabled
+ name="username"
+ value={$user.username}
+ class="block border rounded-xl p-3 w-full"
+ />
+ </div>
+
+ <div class="relative">
+ <label for="password" class="block font-bold block mb-1">New Password</label>
+ {#if revealPassword}
+ <input type="text" class="block border rounded-xl p-3 w-full" bind:value={password} />
+ {:else}
+ <input type="password" class="block border rounded-xl p-3 w-full" bind:value={password} />
+ {/if}
+ <button
+ type="button"
+ on:click={() => (revealPassword = !revealPassword)}
+ class="absolute right-5 top-10"
+ >
+ <Icon icon={revealPassword ? 'eye' : 'eye-off'} />
+ </button>
+ </div>
+
+ <div>
+ <label for="address" class="font-bold mb-1 block">Business Address</label>
+ <input type="text" name="address" class="block border rounded-xl p-3 w-full" />
+ </div>
+
+ <div>
+ <div class="flex justify-between items-center">
+ <span class="font-bold">Email Notifications</span>
+ <Toggle id="email-notify" />
+ </div>
+ <p class="text-secondary mt-1 w-9/12">
+ Get notified via email when a change occurs on your account or when people send you
+ payments.
+ </p>
+ </div>
+
+ <div>
+ <div class="flex justify-between items-center">
+ <span class="font-bold">SMS Notifications</span>
+ <Toggle id="sms-notify" />
+ </div>
+ <p class="text-secondary mt-1 w-9/12">
+ CoinOS will send you text notifications when this is enabled.
+ </p>
+ </div>
+
+ <div>
+ <span class="font-bold mb-1 block">Color Theme</span>
+ <p class="text-secondary mb-1">Select from the default color themes.</p>
+ {#each colorThemes as colors}
+ <button
+ class="mr-2 w-10 h-10 bg-gradient-to-r {colors.color1} {colors.color2} rounded-xl border-2 {selectedTheme ===
+ colors.theme
+ ? 'border-black'
+ : ''}"
+ on:click={() => handleThemeClick(colors)}
+ />
+ {/each}
+ </div>
+ {:else if setting === 'pos'}
+ <div>
+ <label for="currency" class="font-bold block mb-1">Local Currency</label>
+ <select name="currency" class="block py-3 w-full">
+ <option value="USD">USD</option>
+ </select>
+ </div>
+
+ <div>
+ <label for="btc-unit" class="font-bold block mb-1">Display bitcoin in:</label>
+ <select disabled name="btc-unit" class="block py-3 w-full">
+ <option value="SATS">Satoshi (SATS)</option>
+ <option value="BTC">Bitcoin (BTC)</option>
+ </select>
+ </div>
+ {:else}
+ <div>
+ <span class="font-bold mb-1">Security Pin</span>
+ <p class="text-secondary mb-1">
+ Set a security pin that will be used to unlock your settings and to send outgoing
+ payments.
+ </p>
+ <button class="p-3 border rounded-3xl font-bold flex justify-center items-center"
+ ><Icon icon="lock" style="mr-1" /> Set Pin</button
+ >
+ </div>
+
+ <div>
+ <label for="auto-lock" class="font-bold mb-1">Auto-Lock Timer</label>
+ <p class="text-secondary mb-1">
+ Your account will go into a secure mode automatically after a specified time:
+ </p>
+ <select name="auto-lock" class="block py-3 w-full">
+ <option value="5">5 min</option>
+ <option value="10">10 min</option>
+ <option value="30">30 min</option>
+ <option value="60">1 hour</option>
+ <option value="480">8 hours</option>
+ </select>
+ </div>
+
+ <div>
+ <span class="font-bold mb-1">2-Factor Authentication</span>
+ <p class="text-secondary mb-1">
+ Add an extra level of security to your device by enableing 2-factor authentication.
+ </p>
+ <button class="p-3 border rounded-3xl font-bold flex justify-center items-center"
+ ><Icon icon="mobile" style="mr-1" /> Set up 2FA</button
+ >
+ </div>
+ {/if}
+ </div>
+{/if}
+
+<style>
+ .selected {
+ color: black;
+ }
+</style>
diff --git a/src/routes/[username]/support.svelte b/src/routes/[username]/support.svelte
new file mode 100644
index 00000000..335c804f
--- /dev/null
+++ b/src/routes/[username]/support.svelte
@@ -0,0 +1,60 @@
+<script>
+ // this needs to be hooked up to the backend
+ import { AppHeader } from '$comp';
+ import { user } from '$lib/store';
+ let email;
+ let message;
+</script>
+
+{#if $user}
+ <AppHeader />
+
+ <div class="my-20">
+ <h1 class="text-center text-3xl md:text-4xl font-semibold mb-10">Support</h1>
+
+ <form class="space-y-5 mx-auto w-11/12 md:w-[350px]">
+ <div>
+ <label for="email" class="font-semibold">Email</label>
+ <input
+ class="block border rounded-xl p-2 w-full"
+ type="email"
+ name="email"
+ bind:value={email}
+ required
+ />
+ </div>
+
+ <div>
+ <label for="account" class="font-semibold">Account Name</label>
+ <input
+ class="block border rounded-xl p-2 w-full"
+ type="text"
+ name="account"
+ required
+ disabled
+ value={$user.username}
+ />
+ </div>
+
+ <div>
+ <label for="message" class="font-semibold">Message</label>
+ <textarea
+ rows={5}
+ class="block border rounded-xl p-2 w-full"
+ type="text"
+ name="message"
+ bind:value={message}
+ required
+ />
+ </div>
+
+ <button
+ disabled={!email || !message}
+ class="{!email || !message
+ ? 'opacity-50'
+ : 'opacity-100'} bg-black text-white font-bold rounded-xl py-3 w-full mx-auto"
+ >Send</button
+ >
+ </form>
+ </div>
+{/if}
diff --git a/src/routes/[username]/transactions.svelte b/src/routes/[username]/transactions.svelte
index 1935b156..c91fd900 100644
--- a/src/routes/[username]/transactions.svelte
+++ b/src/routes/[username]/transactions.svelte
@@ -1,5 +1,60 @@
<script>
import { AppHeader } from '$comp';
+ import { user, newPayment } from '$lib/store';
+
+ $newPayment = false;
+
+ $: transactions = $user.payments;
</script>
-<AppHeader />
+{#if $user}
+ <AppHeader />
+
+ <div class="my-20">
+ <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10">
+ Transaction History
+ </h1>
+
+ <div class="max-w-5xl w-11/12 md:w-3/4 xl:w-1/2 mx-auto md:text-lg">
+ <div class="text-secondary grid grid-cols-3 mb-5">
+ <h2>AMOUNT</h2>
+ <h2 class="text-center">TYPE</h2>
+ <h2 class="text-right">TIME</h2>
+ </div>
+
+ <div class="space-y-10">
+ {#if transactions.length}
+ {#each transactions as tx}
+ <div class="grid grid-cols-3 border-b pb-5">
+ <div class="font-bold">
+ <span class="block mb-1"
+ >{tx.amount > 0 ? '+' : ''}{new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD'
+ }).format(tx.amount * (tx.rate / 100000000))}
+ </span>
+
+ <span class="text-secondary"
+ >{tx.amount > 0 ? '+' : ''}{new Intl.NumberFormat('en-US', {
+ maximumFractionDigits: 0
+ }).format(tx.amount)}
+ SATS
+ </span>
+ </div>
+
+ <div class="text-center">
+ <span class="text-secondary">{tx.amount < 0 ? 'Sent' : 'Received'}</span>
+ </div>
+
+ <div class="text-secondary text-right">
+ {new Date(tx.createdAt).toLocaleDateString(undefined, { dateStyle: 'medium' })}
+ </div>
+ </div>
+ {/each}
+ {:else}
+ <p class="text-secondary text-lg text-center">No transactions yet.</p>
+ {/if}
+ </div>
+ </div>
+ </div>
+{/if}
diff --git a/src/routes/__error.svelte b/src/routes/__error.svelte
index 2d617f9d..b62b158f 100644
--- a/src/routes/__error.svelte
+++ b/src/routes/__error.svelte
@@ -1,7 +1,7 @@
<script context="module">
export function load({ error, status }) {
if (error.message.includes('not found')) {
- error.message = 'Page not found';
+ error.message = 'Page not found.';
status = 404;
}
@@ -25,13 +25,13 @@
<div class="flex justify-center items-center mt-20">
<div class="space-y-5 text-center">
<a href="/">
- <Icon icon="logo" />
+ <Icon icon="logo" style="mx-auto" />
</a>
- <Icon icon="error" style="mx-auto w-full" />
+ <Icon icon="error" style="mx-auto w-56" />
<h1 class="font-bold text-5xl">Error</h1>
- <p>{error.message}</p>
+ <p class="px-1 w-full md:w-96 break-all font-bold">{error.message.toUpperCase()}</p>
<p>Please try again or contact support.</p>
</div>
</div>
diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte
index 9ec36c57..158397c5 100644
--- a/src/routes/__layout.svelte
+++ b/src/routes/__layout.svelte
@@ -1,6 +1,5 @@
<script>
import { SvelteToast } from '@zerodevx/svelte-toast';
- import { toast } from '@zerodevx/svelte-toast';
import '../app.css';
import '$lib/i18n';
import { onMount } from 'svelte';
@@ -10,16 +9,17 @@
import { browser } from '$app/env';
import { goto } from '$app/navigation';
import { LoadingSplash } from '$comp';
- import { protectedRoutes } from '$lib/utils';
+ import { warning, protectedRoutes } from '$lib/utils';
browser && ($token ||= $session.token);
+
let ready = false;
onMount(() => {
if (protectedRoutes.find((p) => $page.url.pathname.match(p))) {
if (!$token) {
goto('/login');
- toast.push('Please sign in to continue.');
+ warning('Please sign in to continue.');
}
user.subscribe((u) => u?.username && (ready = true));
} else {
@@ -43,5 +43,6 @@
--toastContainerRight: auto;
--toastContainerBottom: 8rem;
--toastContainerLeft: calc(50vw - 8rem);
+ --toastBackground: #292929;
}
</style>
diff --git a/src/routes/community.svelte b/src/routes/community.svelte
deleted file mode 100644
index e69de29b..00000000
--- a/src/routes/community.svelte
+++ /dev/null
diff --git a/src/routes/index.svelte b/src/routes/index.svelte
index faa881bb..f50db726 100644
--- a/src/routes/index.svelte
+++ b/src/routes/index.svelte
@@ -23,7 +23,7 @@
{
image: 'bolt',
step: '2. Ask for payments',
- description: 'The same as you normally would, but with Bitcoin.'
+ description: 'The same as you normally would, but with bitcoin.'
},
{
image: 'smile',
diff --git a/src/routes/invoice/[id].svelte b/src/routes/invoice/[id].svelte
index 17c7de1f..9ef25251 100644
--- a/src/routes/invoice/[id].svelte
+++ b/src/routes/invoice/[id].svelte
@@ -1,5 +1,4 @@
<script>
- import { toast } from '@zerodevx/svelte-toast';
import { get, post, reverseFormat } from '$lib/utils';
import { browser } from '$app/env';
import { invoices, user, preferredCurrency } from '$lib/store';
@@ -83,7 +82,6 @@
$: generate(text);
let generate = async (ready) => {
- console.log('GENERATE', text);
if (!ready) return;
let { default: QRCodeStyling } = await import('qr-code-styling');
@@ -122,15 +120,6 @@
let customInput;
- const handleCopy = () => {
- copy(text);
- toast.push('Copied!', {
- theme: {
- '--toastBarBackground': '#2F855A'
- }
- });
- };
-
const handleDoneClick = () => {
if ($user) {
goto(`/${$user.username}/receive`);
@@ -158,7 +147,7 @@
<div
class="w-[100%] md:w-auto absolute {showMobileTip
? 'bottom-0'
- : '-bottom-full'} bg-white p-6 md:p-0 rounded-t-3xl md:rounded-none transition-all md:transition-none ease-in-out duration-500 left-0 md:static text-center space-y-5"
+ : '-top-full'} bg-white p-6 md:p-0 rounded-t-3xl md:rounded-none left-0 md:static text-center space-y-5"
>
<h1 class="hidden md:block text-4xl font-semibold">Add a tip?</h1>
@@ -231,7 +220,7 @@
/>
<!-- invoice section -->
- <div class="text-center space-y-5">
+ <div class="w-[325px] text-center space-y-5">
<button
class="block md:hidden bg-black {showMobileTip
? 'text-white/50'
@@ -258,15 +247,15 @@
</button>
<span class="text-secondary block"
- >Scan to pay <span class="text-black font-semibold">{username}</span></span
+ >Scan to pay <a href="/{username}" class="text-black font-semibold">{username}</a></span
>
<div
bind:this={qr}
- on:click={handleCopy}
+ on:click={() => copy(text)}
class="border {showMobileTip
? 'border-gray-400'
- : 'border-lightgrey'} rounded-3xl block md:flex justify-center items-center cursor-pointer"
+ : 'border-lightgrey'} px-5 md:px-0 w-[292px] h-[302px] md:w-[300px] md:h-[300px] rounded-3xl block md:flex justify-center items-center cursor-pointer mx-auto"
/>
<div class="px-5 space-y-3">
@@ -286,8 +275,8 @@
>
</div>
- <div class="flex justify-between">
- <span class="font-bold">Total</span>
+ <div class="flex flex-wrap justify-between">
+ <span class="font-bold mr-1">Total</span>
<span class="font-bold"
>{totalAmountFormatted}
<span class="text-secondary font-normal">{`(${totalAmountSats} SATS)`}</span></span
@@ -302,7 +291,10 @@
<div class="text-center mt-20 md:mt-0">
<Image image="success" style="w-full md:w-3/4 xl:w-1/2 mx-auto max-w-3xl" />
<h1 class="text-3xl md:text-4xl font-bold mb-6">Payment Successful!</h1>
- <h2 class="text-2xl md:text-3xl font-semibold">{totalAmountFormatted} {$preferredCurrency}</h2>
+ <h2 class="text-2xl md:text-3xl font-semibold">
+ {totalAmountFormatted}
+ {$preferredCurrency}
+ </h2>
<h3 class="text-secondary md:text-lg mb-6 mt-1">({totalAmountSats} SATS)</h3>
<button class="bg-black text-white rounded-2xl w-20 py-3 font-bold" on:click={handleDoneClick}>
Done
diff --git a/src/routes/jobs.svelte b/src/routes/jobs.svelte
index e69de29b..d864cf88 100644
--- a/src/routes/jobs.svelte
+++ b/src/routes/jobs.svelte
@@ -0,0 +1,32 @@
+<script>
+ import { Icon } from '$comp';
+ import { goto } from '$app/navigation';
+</script>
+
+<a href="/">
+ <Icon icon="logo" style="mx-auto mt-10" />
+</a>
+<div class="w-11/12 md:w-3/4 lg:w-1/2 2xl:w-1/3 max-w-3xl mx-auto my-20 space-y-5">
+ <button on:click={() => goto('/')}>
+ <Icon icon="arrow-left" style="w-8" />
+ </button>
+ <h1 class="text-5xl font-medium">Jobs</h1>
+
+ <h2 class="text-2xl font-medium">Current Openings</h2>
+
+ <p class="text-justify text-xl text-secondary">
+ CoinOS is actively looking for developers to join our growing team (full-time or part-time,
+ remote working OK).
+ <br /><br />
+ Excited about working on open source bitcoin? Then take a look at our repositories on
+ <a
+ href="https://github.com/coinos"
+ target="_blank"
+ rel="noreferrer"
+ class="underline underline-offset-4">GitHub</a
+ >.
+ <br /><br />
+ If you think you can make an impact, we'd love to hear from you. Send us your resume to
+ <a href="mailto:[email protected]" class="underline underline-offset-4">[email protected]</a>.
+ </p>
+</div>
diff --git a/src/routes/support.svelte b/src/routes/support.svelte
deleted file mode 100644
index 1935b156..00000000
--- a/src/routes/support.svelte
+++ /dev/null
@@ -1,5 +0,0 @@
-<script>
- import { AppHeader } from '$comp';
-</script>
-
-<AppHeader />
diff --git a/src/routes/withdraw.js b/src/routes/withdraw.js
new file mode 100644
index 00000000..818d39da
--- /dev/null
+++ b/src/routes/withdraw.js
@@ -0,0 +1,9 @@
+import { post } from '$lib/utils';
+export async function POST({ locals, request }) {
+ let { token } = locals;
+ let body = await request.json();
+ body = await post('/lightning/send', body, {
+ authorization: `Bearer ${token}`
+ });
+ return { body };
+}