diff options
| author | Adam Soltys <[email protected]> | 2024-09-04 18:59:30 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-09-17 09:18:32 -0700 |
| commit | c5ef18aec5de023732eb6b86f768f206462fea93 (patch) | |
| tree | 8f8eb333344d430eed1b22b9f13e9c7228d06ac5 /src/routes/(app) | |
| parent | 55297eeaae86fee264f6fc05c95bfc703be1056f (diff) | |
| download | coinos-ui-c5ef18aec5de023732eb6b86f768f206462fea93.tar.gz coinos-ui-c5ef18aec5de023732eb6b86f768f206462fea93.zip | |
wip
Diffstat (limited to 'src/routes/(app)')
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}—{/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> |
