aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/(app)
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-09-04 18:59:30 -0700
committerAdam Soltys <[email protected]>2024-09-17 09:18:32 -0700
commitc5ef18aec5de023732eb6b86f768f206462fea93 (patch)
tree8f8eb333344d430eed1b22b9f13e9c7228d06ac5 /src/routes/(app)
parent55297eeaae86fee264f6fc05c95bfc703be1056f (diff)
downloadcoinos-ui-c5ef18aec5de023732eb6b86f768f206462fea93.tar.gz
coinos-ui-c5ef18aec5de023732eb6b86f768f206462fea93.zip
wip
Diffstat (limited to 'src/routes/(app)')
-rw-r--r--src/routes/(app)/[username]/(profile)/+layout.svelte66
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte282
-rw-r--r--src/routes/(app)/[username]/account/setup/+page.svelte64
-rw-r--r--src/routes/(app)/[username]/account/setup/step2/+page.svelte46
4 files changed, 167 insertions, 291 deletions
diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte
index c4750581..2caf9b30 100644
--- a/src/routes/(app)/[username]/(profile)/+layout.svelte
+++ b/src/routes/(app)/[username]/(profile)/+layout.svelte
@@ -84,11 +84,10 @@
<div
class="w-full lg:w-[280px] xl:w-[360px] lg:absolute space-y-5 left-20 mx-auto mb-5"
>
- <div
- class="flex text-3xl font-bold text-center mx-auto justify-center gap-2"
- >
- <div class="my-auto break-words w-full">{display || username}</div>
- </div>
+ <button type="button" on:click={toggleDetails} class="flex text-3xl font-bold text-center mx-auto justify-center gap-2">
+ <div class="my-auto break-words">{display || username}</div>
+ <Icon icon="qr" style="w-8 invert my-auto" />
+ </button>
{#if subject.address}
<div
@@ -112,68 +111,51 @@
<span class="text-secondary">{$t("user.followers")}</span></a
>
</div>
-
- <button
- type="button"
- on:click={toggleDetails}
- class="flex text-base text-secondary mx-auto"
- >
- <div class="mt-1">
- {$t("user.moreDetails")}
- </div>
- <div
- class:-rotate-90={showDetails}
- class:rotate-180={!showDetails}
- class="my-auto"
- >
- <Icon icon="chevron-left" style="w-6 mr-2" />
- </div>
- </button>
</div>
{#if showDetails}
- <div class="text-xl space-y-5">
+ <div class="space-y-5 pb-20">
<div>
- <div class="font-bold">{$t("user.profilePage")}</div>
+ <div class="text-secondary">{$t("user.lightningAddress")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
- {profile}
+ <div class="break-all grow text-xl">
+ {lnaddr}
</div>
<div class="flex mb-auto gap-1">
- <button class="my-auto" on:click={() => copy(profile)}
+ <button class="my-auto" on:click={() => copy(lnaddr)}
><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button
>
- <a
- href={`/qr/${encodeURIComponent(
- `${$page.url.protocol}//${profile}`
- )}`}
- class="my-auto"
- >
+ <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto">
<Icon icon="qr" style="invert max-w-max min-w-[32px]" />
</a>
</div>
</div>
</div>
<div>
- <div class="font-bold">{$t("user.lightningAddress")}</div>
+ <div class="text-secondary">{$t("user.url")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
- {lnaddr}
+ <div class="break-all grow text-xl">
+ {profile}
</div>
<div class="flex mb-auto gap-1">
- <button class="my-auto" on:click={() => copy(lnaddr)}
+ <button class="my-auto" on:click={() => copy(profile)}
><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button
>
- <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto">
+ <a
+ href={`/qr/${encodeURIComponent(
+ `${$page.url.protocol}//${profile}`
+ )}`}
+ class="my-auto"
+ >
<Icon icon="qr" style="invert max-w-max min-w-[32px]" />
</a>
</div>
</div>
</div>
<div>
- <div class="font-bold">{$t("user.nostrPubkey")}</div>
+ <div class="text-secondary">{$t("user.nostrPubkey")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
+ <div class="break-all grow text-xl">
{npub}
</div>
<div class="flex my-auto gap-1">
@@ -188,9 +170,9 @@
</div>
{#if user.id === subject.id}
<div>
- <div class="font-bold">{$t("user.nwc")}</div>
+ <div class="text-secondary">{$t("user.nwc")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
+ <div class="break-all grow text-xl">
{nwc}
</div>
<div class="flex my-auto gap-1">
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte
index 32e6807a..afb4e58c 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -1,283 +1,67 @@
<script>
- import { browser } from "$app/environment";
- import { goto, invalidate } from "$app/navigation";
- import { btc, f, sat, post } from "$lib/utils";
+ import { btc, f, sat } from "$lib/utils";
import Icon from "$comp/Icon.svelte";
+ import Account from "$comp/Account.svelte";
import Balance from "$comp/Balance.svelte";
- import DeleteItem from "$comp/DeleteItem.svelte";
+ import Items from "$comp/Items.svelte";
import { t } from "$lib/translations";
- import { fade } from "svelte/transition";
- import { cubicIn } from "svelte/easing";
- import { flip } from "svelte/animate";
import { installPrompt } from "$lib/store";
- import {
- dndzone,
- SHADOW_ITEM_MARKER_PROPERTY_NAME,
- SOURCES,
- TRIGGERS,
- } from "svelte-dnd-action";
-
export let data;
- let flipDurationMs = 200;
- let dropTargetStyle = {};
-
let { subject, user, items, rates } = data;
$: ({ currency } = subject);
$: rate = rates[currency];
- $: refresh(data);
- let refresh = (d) => ({ items, subject, user, rates } = d);
- let dragDisabled =
- subject?.id !== user?.id || (browser && window.innerWidth < 768);
-
- let deleting;
$: total = items.reduce((a, b) => a + b.price * b.quantity, 0);
- let del = (item) => {
- deleting = item;
- };
-
- function handleConsider(e) {
- const {
- items: newItems,
- info: { source, trigger },
- } = e.detail;
- items = newItems;
- if (source === SOURCES.KEYBOARD && trigger === TRIGGERS.DRAG_STOPPED)
- reset();
- }
- async function handleFinalize(e) {
- const {
- items: newItems,
- info: { source },
- } = e.detail;
- items = newItems;
- if (source === SOURCES.POINTER) reset();
-
- if (subject?.id === user?.id) {
- await post("/api/items/sort", { items });
- invalidate("app:items");
- }
- }
-
- function startDrag(e) {
- e.preventDefault();
- dragDisabled = false;
- }
-
- function handleKeyDown(e) {
- if ((e.key === "Enter" || e.key === " ") && dragDisabled)
- dragDisabled = false;
- }
-
- $: if (total > 0 && browser) {
- let payButton = document.querySelector("#payButton");
- if (payButton) payButton.addEventListener("click", checkout);
- }
-
- let checkout = async () => {
- let invoice = {
- type: "lightning",
- amount: btc(total, rate),
- currency,
- items: items.filter((i) => i.quantity > 0),
- prompt: subject.prompt,
- memoPrompt: subject.memoPrompt,
- rate,
- };
-
- let { id } = await post(`/${subject.username}/invoice`, {
- invoice,
- user: { username: subject.username },
- });
-
- if (invoice.memoPrompt) {
- return goto(`/${subject.username}/invoice/${id}/memo`);
- }
- goto(`/${subject.username}/invoice/${id}`);
- };
-
- let reset = () =>
- (dragDisabled =
- user?.username !== subject?.username || window.innerWidth < 768);
+ let refresh = (d) => ({ items, subject, user, rates } = d);
+ $: refresh(data);
let install = async () => {
if (!$installPrompt) return;
await $installPrompt.prompt();
$installPrompt = null;
};
-</script>
-<svelte:window on:resize={reset} />
+ user.savings = 0;
-{#if deleting}
- <DeleteItem bind:item={deleting} />
+ let accounts = [
+ { balance: user.balance, setup: true, type: "Cash" },
+ { balance: user.savings, setup: false, type: "Savings" },
+ ];
+</script>
+
+{#if !user.balance}
+ <div class="mb-8">
+ <p class="text-secondary text-lg">
+ {$t("user.welcome")}
+ </p>
+ </div>
{/if}
<div class="space-y-5">
{#if user?.id === subject.id}
- <div class="flex justify-center lg:justify-start">
- <Balance {user} {rate} />
- </div>
-
- {#if !user.balance}
- <div class="mb-8">
- <p class="text-secondary text-lg">
- {$t("user.welcome")}
- </p>
- </div>
- {/if}
-
- <div class="flex flex-wrap gap-3 justify-center w-full text-xl">
- <a href={`/send`} class="contents grow">
- <button
- type="button"
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow"
- >
- <div class="mx-auto flex gap-2">
- <Icon icon="send" style="w-8" />
- <div class="my-auto">{$t("user.dashboard.send")}</div>
- </div>
- </button>
- </a>
-
- <a href={`/${user.username}/receive`} class="contents">
- <button
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow"
- >
- <div class="mx-auto flex gap-2">
- <Icon icon="receive" style="w-8" />
- <div class="my-auto">{$t("user.dashboard.receive")}</div>
- </div>
- </button>
- </a>
-
- {#if $installPrompt}
- <button
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white grow basis-full fixed bottom-16 xl:hidden"
- on:click={install}
- >
- <div class="mx-auto flex gap-2">
- <Icon icon="save" style="w-8 mx-auto invert" />
- <div class="my-auto text-xl whitespace-nowrap">
- {$t("user.install")}
- </div>
- </div>
- </button>
- {/if}
- </div>
- {/if}
+ {#each accounts as account}
+ <Account {user} {rate} {account} />
+ {/each}
- {#if items.length}
- <h2 class="text-2xl font-bold">Select items</h2>
- {/if}
- <div
- class="grid sm:grid-cols-2 gap-4 pb-20"
- use:dndzone={{ items, flipDurationMs, dropTargetStyle, dragDisabled }}
- on:consider={handleConsider}
- on:finalize={handleFinalize}
- >
- {#each items as i (i.id)}
+ {#if $installPrompt}
<button
- type="button"
- class="w-full text-center cursor-pointer hover:opacity-80 mb-auto"
- on:click={() => i.quantity++}
+ class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white grow basis-full fixed bottom-16 xl:hidden"
+ on:click={install}
>
- <div class="relative w-full h-64 overflow-hidden rounded-xl">
- {#if i[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
- <div
- in:fade={{ duration: 200, easing: cubicIn }}
- class="custom-shadow-item"
- />
- {:else if i.image}
- <img
- src={`/api/public/${i.image}.webp`}
- alt={i.name}
- class="mx-auto object-cover h-full w-full"
- />
- {:else}
- <div
- class="bg-gradient-to-r from-primary to-gradient mb-4 cursor-pointer hover:opacity-80 w-full h-full"
- alt="Banner"
- />
- {/if}
- {#if user?.username === subject.username}
- <div class="flex gap-2 justify-center absolute top-2 right-2">
- <button type="button" on:click|stopPropagation={() => {}}>
- <a href={`/${user.username}/items/${i.id}`}>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- >
- <Icon icon="edit" style="w-8 mx-auto invert" />
- </button>
- </a>
- </button>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- on:click|stopPropagation={() => del(i)}
- >
- <Icon icon="trash" style="w-8 mx-auto invert" />
- </button>
- <button
- type="button"
- on:click|stopPropagation={() => {}}
- class="sm:hidden"
- on:mousedown={startDrag}
- on:touchstart={startDrag}
- on:keydown={handleKeyDown}
- >
- <a href={`/${user.username}/items/${i.id}`}>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- >
- <Icon icon="move" style="w-8 mx-auto invert" />
- </button>
- </a>
- </button>
- </div>
- {/if}
- </div>
- <div class="bg-white rounded-xl py-2">
- <div class="flex text-left gap-2">
- <div class="overflow-hidden space-y-1">
- <div class="text-2xl break-words">{i.name}</div>
- <div class="flex gap-2">
- <div class="font-semibold text-lg">
- {f(i.price, currency)}
- </div>
- <div class="text-secondary my-auto">
- {sat(btc(i.price, rate))}
- </div>
- </div>
- </div>
- <div class="flex gap-1 ml-auto mb-auto justify-around tabular-nums">
- {#if i.quantity}
- <button
- type="button"
- on:click|stopPropagation={() =>
- i.quantity > 0 && i.quantity--}
- >
- <Icon icon="minus" style="w-10 min-w-10" />
- </button>
- {/if}
- <div class="text-2xl my-auto text-center">
- {#if i.quantity}{i.quantity}{:else}&mdash;{/if}
- </div>
- {#if i.quantity}
- <button
- type="button"
- on:click|stopPropagation={() => i.quantity++}
- >
- <Icon icon="plus" style="w-10 min-w-10" />
- </button>
- {/if}
- </div>
+ <div class="mx-auto flex gap-2">
+ <Icon icon="save" style="w-8 mx-auto invert" />
+ <div class="my-auto text-xl whitespace-nowrap">
+ {$t("user.install")}
</div>
</div>
</button>
- {/each}
- </div>
+ {/if}
+ {/if}
+
+ <Items {items} {subject} {user} {total} {currency} {rate} />
+
<div
class="fixed inset-x-0 mx-auto flex bottom-16"
class:static={!items.length}
diff --git a/src/routes/(app)/[username]/account/setup/+page.svelte b/src/routes/(app)/[username]/account/setup/+page.svelte
new file mode 100644
index 00000000..5c2102c7
--- /dev/null
+++ b/src/routes/(app)/[username]/account/setup/+page.svelte
@@ -0,0 +1,64 @@
+<script>
+ import { copy } from "$lib/utils";
+ import { t } from "$lib/translations";
+ import Icon from "$comp/Icon.svelte";
+ import { mnemonic } from "$lib/store";
+ import { generateMnemonic } from "@scure/bip39";
+ import { wordlist } from "@scure/bip39/wordlists/english";
+
+ export let data;
+ let { user } = data;
+
+ let reveal;
+
+ let toggleSeed = async () => {
+ try {
+ $mnemonic = await generateMnemonic(wordlist);
+ reveal = !reveal;
+ } catch (e) {
+ console.log(e);
+ }
+ };
+</script>
+
+<div class="space-y-5">
+ <h1 class="text-center text-3xl font-semibold">Account setup</h1>
+ <h2 class="text-center text-2xl text-secondary">Step 1</h2>
+
+ <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
+ <p class="text-secondary mb-1">
+ The keys to your savings account are derived from a seed phrase. When
+ you're ready, write it down and keep it safe.
+ </p>
+
+ <p class="text-secondary mb-1">
+ Whoever knows these words can withdraw funds from the account. We can't
+ help you recover them if you lose them.
+ </p>
+
+ {#if reveal}
+ <div class="text-2xl border p-4 text-center rounded-2xl">
+ {$mnemonic}
+ </div>
+
+ <div>
+ <a href={`/${user.username}/account/setup/step2`} class="contents">
+ <button
+ type="button"
+ class="bg-black text-white w-full px-5 py-6 text-xl rounded-2xl font-bold"
+ on:click={toggleSeed}
+ >
+ I've written down my seed phrase
+ </button>
+ </a>
+ </div>
+ {:else}
+ <div class="flex justify-center">
+ <button type="button" class="primary" on:click={toggleSeed}>
+ <Icon icon="warning" style="mr-2 w-6 my-auto" />
+ {$t("user.settings.revealSeed")}
+ </button>
+ </div>
+ {/if}
+ </div>
+</div>
diff --git a/src/routes/(app)/[username]/account/setup/step2/+page.svelte b/src/routes/(app)/[username]/account/setup/step2/+page.svelte
new file mode 100644
index 00000000..3b403295
--- /dev/null
+++ b/src/routes/(app)/[username]/account/setup/step2/+page.svelte
@@ -0,0 +1,46 @@
+<script lang="ts">
+ import { t } from "$lib/translations";
+ import { mnemonic } from "$lib/store";
+ import Icon from "$comp/Icon.svelte";
+ import { encrypt, decrypt } from "nostr-tools/nip49";
+ import { mnemonicToEntropy, entropyToMnemonic } from "@scure/bip39";
+ import { wordlist } from "@scure/bip39/wordlists/english";
+
+ export let data;
+ let { user } = data;
+
+ let password;
+
+ let submit = async () => {
+ console.log($mnemonic);
+ let t = await encrypt(mnemonicToEntropy($mnemonic, wordlist), password);
+
+ let m = await decrypt(t, password);
+ console.log(entropyToMnemonic(m, wordlist));
+ };
+</script>
+
+<div class="space-y-5">
+ <div>
+ <h1 class="text-center text-3xl font-semibold">Account setup</h1>
+ <h2 class="text-center text-2xl text-secondary">Step 2</h2>
+ </div>
+
+ <form
+ on:submit|preventDefault={submit}
+ class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"
+ >
+ <p class="text-secondary mb-1">
+ Add an additional password to your seed phrase. It should be different
+ than your login password. We can't reset it if you lose it.
+ </p>
+
+ <input type="text" placeholder="Password" bind:value={password} />
+
+ <button
+ class="bg-black text-white w-full px-5 py-6 text-xl rounded-2xl font-bold"
+ >
+ Submit
+ </button>
+ </form>
+</div>