diff options
| author | Adam Soltys <[email protected]> | 2024-09-24 20:12:43 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-09-24 20:12:43 -0700 |
| commit | d49d4119811dee9270f0858f3f03e5d8cd0d95ba (patch) | |
| tree | 42101a3ccf47f7b7290637700b90a843476e56f7 /src/components | |
| parent | 20c0c52f8b850bfbab490735b0b5cd5d08851488 (diff) | |
| parent | 9531ccbdf01b07c392b1b5cb6d8ad4680ee29d2c (diff) | |
| download | coinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.tar.gz coinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.zip | |
Merge branch 'self-custody'
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/About.svelte | 5 | ||||
| -rw-r--r-- | src/components/Account.svelte | 64 | ||||
| -rw-r--r-- | src/components/AppHeader.svelte | 33 | ||||
| -rw-r--r-- | src/components/Balance.svelte | 6 | ||||
| -rw-r--r-- | src/components/Footer.svelte | 2 | ||||
| -rw-r--r-- | src/components/Invoice.svelte | 2 | ||||
| -rw-r--r-- | src/components/Items.svelte | 246 | ||||
| -rw-r--r-- | src/components/LandingHeader.svelte | 16 | ||||
| -rw-r--r-- | src/components/LocationsMap.svelte | 12 | ||||
| -rw-r--r-- | src/components/Mnemonic.svelte | 14 | ||||
| -rw-r--r-- | src/components/Numpad.svelte | 2 | ||||
| -rw-r--r-- | src/components/Request.svelte | 39 | ||||
| -rw-r--r-- | src/components/WalletPass.svelte | 65 |
13 files changed, 430 insertions, 76 deletions
diff --git a/src/components/About.svelte b/src/components/About.svelte index 1e60db72..01ff4c7c 100644 --- a/src/components/About.svelte +++ b/src/components/About.svelte @@ -91,14 +91,15 @@ {@html $t("about.locations.description")} </p> <!-- <LocationsMap {locations} /> --> + <a href="/map"> <div class="relative"> <img src="/map.png" class="mx-auto" /> <button - class="bg-black text-white border rounded-full px-6 py-2 font-bold hover:opacity-80 absolute left-1/2 top-1/2 text-2xl" + class="bg-black text-white border border-4 rounded-full px-6 py-2 font-bold hover:bg-secondary absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 text-2xl" > - View map + {$t("about.locations.viewMap")} </button> </div> </a> diff --git a/src/components/Account.svelte b/src/components/Account.svelte new file mode 100644 index 00000000..458f67c6 --- /dev/null +++ b/src/components/Account.svelte @@ -0,0 +1,64 @@ +<script> + import Icon from "$comp/Icon.svelte"; + import Balance from "$comp/Balance.svelte"; + import { t } from "$lib/translations"; + + export let user, rate, account; + let { name, seed, balance, id } = account; + $: type = seed ? "Savings" : "Cash"; + + $: refresh(account); + let refresh = (a) => ({ seed, balance, id } = a); + + let setAccount = () => (document.cookie = `aid=${id}; path=/; max-age=86400`); +</script> + +<a href={`/payments`} class="block" on:click={setAccount}> + <div class="py-4 px-2 space-y-5 bg-primary rounded-2xl border"> + <div class="flex"> + <Balance {balance} {user} {rate} /> + {#if seed} + <a href={`/account/${id}`} class="contents"> + <button class="ml-auto flex gap-1 mb-auto pb-4 pl-4"> + <Icon icon="settings" style="w-8 my-auto" /> + </button> + </a> + {/if} + </div> + + <div class="flex gap-1 justify-center w-full text-xl"> + <a href={`/invoice`} class="contents" on:click={setAccount}> + <button + class="rounded-2xl border py-5 px-4 hover:opacity-80 flex grow bg-white" + > + <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> + + <a href={`/scan`} class="contents" on:click={setAccount}> + <button + class="rounded-2xl border py-5 px-4 hover:opacity-80 flex shrink bg-white" + > + <div class="mx-auto flex gap-2"> + <Icon icon="camera" style="w-8" /> + </div> + </button> + </a> + + <a href={`/send`} class="contents grow" on:click={setAccount}> + <button + type="button" + class="rounded-2xl border py-5 px-6 hover:opacity-80 flex bg-white grow" + > + <div class="mx-auto flex gap-1"> + <Icon icon="send" style="w-8" /> + <div class="my-auto">{$t("user.dashboard.send")}</div> + </div> + </button> + </a> + </div> + </div> +</a> diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte index 3b0303a8..cc521e93 100644 --- a/src/components/AppHeader.svelte +++ b/src/components/AppHeader.svelte @@ -7,11 +7,7 @@ import { loading, t } from "$lib/translations"; import { page } from "$app/stores"; - export let data; - - let { subject, user } = data; - $: update(data); - let update = (data) => ({ subject, user } = data); + export let user, subject; let menuButtons; $: if (user) @@ -19,15 +15,15 @@ { key: "nav.settings", icon: "settings", - href: `/${user.username}/settings`, + href: `/settings`, }, { key: "nav.support", icon: "support", href: `/support` }, - { key: "nav.map", icon: "map", href: `/${user.username}/map` }, - { - key: "nav.messages", - icon: "message", - href: `/${user.username}/messages`, - }, + { key: "nav.map", icon: "map", href: `/map` }, + // { + // key: "nav.messages", + // icon: "message", + // href: `/messages`, + // }, { key: "nav.merch", icon: "shop", href: `https://coinosmerch.com` }, { key: "nav.signOut", icon: "logout", href: `/logout` }, ]; @@ -60,19 +56,19 @@ ><Icon icon="home" style="mx-auto w-6 sm:w-8" /> </button> </a> - <a href={`/${user.username}/receive`}> + <a href={`/invoice`}> <button class="bg-white p-2 rounded-full w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl border border-black/10 {$page - .url.pathname === `/${user.username}/receive` + .url.pathname.includes('invoice') ? 'opacity-100' : 'opacity-70 hover:opacity-80'}" ><Icon icon="receive" style="mx-auto w-6 sm:w-8" /> </button> </a> - <a href={`/${user.username}/payments`}> + <a href={`/payments`}> <button class="bg-white p-2 rounded-full w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl border border-black/10 {$page - .url.pathname === `/${user.username}/payments` + .url.pathname === `/payments` ? 'opacity-100' : 'opacity-70 hover:opacity-80'} relative" ><Icon icon="clock" style="mx-auto w-6 sm:w-8" /> @@ -102,9 +98,8 @@ <div class="relative"> <OutClick on:outclick={() => (showMenu = false)}> <button - class="bg-white p-2 rounded-full w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl border border-black/10 {$page - .url.pathname === `/${user.username}` || - $page.url.pathname === `/${user.username}/settings` || + class="bg-white p-2 rounded-full w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl border border-black/10 { + $page.url.pathname === `/settings` || $page.url.pathname === `/support` ? 'opacity-100' : `opacity-70 hover:opacity-80 ${ diff --git a/src/components/Balance.svelte b/src/components/Balance.svelte index 4d5ac6bd..51c56555 100644 --- a/src/components/Balance.svelte +++ b/src/components/Balance.svelte @@ -6,7 +6,7 @@ import { t } from "$lib/translations"; let show; - export let user, rate; + export let user, rate, balance; </script> {#if show && user.haspin && $pin?.length !== 6} @@ -27,12 +27,12 @@ {#if isNaN(rate)} <div class="text-gray-200">—</div> {:else} - {f((user.balance * rate) / sats, user.currency)} + {f((balance * rate) / sats, user.currency)} {/if} </div> <div class="text-secondary text-2xl"> - ⚡️{s(user.balance)} + ⚡️{s(balance)} </div> {/if} </div> diff --git a/src/components/Footer.svelte b/src/components/Footer.svelte index d70c013c..067e185e 100644 --- a/src/components/Footer.svelte +++ b/src/components/Footer.svelte @@ -4,7 +4,7 @@ import { t } from "$lib/translations"; const followLinks = [ - { url: "https://twitter.com/coinoswallet", titleID: "twitter" }, + { url: "nostr:nprofile1qqst4qyeqenw7zm0fwjsty68h6cnys5jre2xd8ngqpjv5a2j26s78fspz4mhxue69uhhyetvv9ujucm0d9hx7uewd9hsgwx239", titleID: "nostr" }, { url: "https://t.me/coinoswallet", titleID: "telegram" }, { url: "https://linktr.ee/coinos.io", titleID: "linktree" }, ]; diff --git a/src/components/Invoice.svelte b/src/components/Invoice.svelte index ba436649..563a2fee 100644 --- a/src/components/Invoice.svelte +++ b/src/components/Invoice.svelte @@ -27,7 +27,7 @@ </div> </div> <div class="mx-auto my-auto flex gap-2"> - <a href={`/${invoice.user.username}/invoice/${$invoice.id}/tip`}> + <a href={`/invoice/${$invoice.id}/tip`}> <button class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-32" > diff --git a/src/components/Items.svelte b/src/components/Items.svelte new file mode 100644 index 00000000..2916721d --- /dev/null +++ b/src/components/Items.svelte @@ -0,0 +1,246 @@ +<script> + import { t } from "$lib/translations"; + import { btc, f, sat, post } from "$lib/utils"; + import { browser } from "$app/environment"; + import { goto, invalidate } from "$app/navigation"; + import { fade } from "svelte/transition"; + import { cubicIn } from "svelte/easing"; + import { flip } from "svelte/animate"; + import { + dndzone, + SHADOW_ITEM_MARKER_PROPERTY_NAME, + SOURCES, + TRIGGERS, + } from "svelte-dnd-action"; + import DeleteItem from "$comp/DeleteItem.svelte"; + import Icon from "$comp/Icon.svelte"; + + export let subject, user, items, rate, currency, total; + + let flipDurationMs = 200; + let dropTargetStyle = {}; + + let dragDisabled = + subject?.id !== user?.id || (browser && window.innerWidth < 768); + + let deleting; + 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(`/invoice`, { + invoice, + user: { username: subject.username }, + }); + + if (invoice.memoPrompt) { + return goto(`/invoice/${id}/memo`); + } + goto(`/invoice/${id}`); + }; + + let reset = () => + (dragDisabled = + user?.username !== subject?.username || window.innerWidth < 768); +</script> + +<svelte:window on:resize={reset} /> + +{#if deleting} + <DeleteItem bind:item={deleting} /> +{/if} + +{#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)} + <button + type="button" + class="w-full text-center cursor-pointer hover:opacity-80 mb-auto" + on:click={() => i.quantity++} + > + <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={`/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={`/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> + </div> + </button> + {/each} +</div> + +<div + class="fixed inset-x-0 mx-auto flex bottom-16" + class:static={!items.length} +> + {#if total > 0} + <button + class="rounded-2xl py-5 px-6 font-bold hover:bg-neutral-700 flex bg-black text-white mx-auto" + on:click={checkout} + > + <div class="mx-auto flex"> + <div class="my-auto text-2xl"> + {$t("user.dashboard.checkout")} + {f(total, currency)} + <span class="text-base"> + {sat(btc(total, rate), currency)} + </span> + </div> + </div> + </button> + {/if} +</div> + +<style> + .custom-shadow-item { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + visibility: visible; + background: #333; + opacity: 0.5; + margin: 0; + } +</style> diff --git a/src/components/LandingHeader.svelte b/src/components/LandingHeader.svelte index d1094a1d..72913999 100644 --- a/src/components/LandingHeader.svelte +++ b/src/components/LandingHeader.svelte @@ -19,22 +19,22 @@ </script> <header - class="w-full lg:w-5/6 py-5 mx-auto fixed md:sticky z-10 top-0 bg-white/90" + class="w-full lg:w-5/6 py-5 mx-auto fixed sticky z-10 top-0 bg-white/90" bind:this={header} > - <nav class="block xl:flex flex-wrap justify-between items-center"> + <nav class="block lg:flex justify-between items-center"> <div - class="flex justify-start md:justify-center items-center md:space-x-10" + class="flex justify-start lg:justify-center items-center lg:space-x-10" > <a href="/" on:click={() => scroll(header)}> - <Icon icon="logo" style="hidden md:block mb-5 xl:mb-0" /> - <Icon icon="logo" style="block md:hidden ml-5 w-48" /> + <Icon icon="logo" style="hidden lg:block mb-5 xl:mb-0" /> + <Icon icon="logo" style="block lg:hidden ml-5 w-48" /> </a> </div> <!-- desktop nav --> <div - class="hidden space-x-10 md:flex flex-wrap justify-center items-center font-bold" + class="hidden space-x-10 lg:flex justify-center items-center font-bold" > {#if $page.url.pathname === "/"} <button class="hover:opacity-80" on:click={() => scroll(howItWorks)} @@ -76,13 +76,13 @@ <!-- mobile nav --> <button - class="block md:hidden absolute top-[34.5px] right-10 z" + class="block lg:hidden absolute top-[34.5px] right-10 z" on:click={() => (showMobileMenu = !showMobileMenu)} ><Icon icon={!showMobileMenu ? "menu" : "close"} /> </button> <div - class="container w-full px-10 md:hidden absolute top-0 {showMobileMenu + class="container w-full px-10 lg:hidden absolute top-0 {showMobileMenu ? 'right-0' : 'right-[-100%]'} transition-all ease-in-out duration-300 h-[100vh] w-full bg-white" > diff --git a/src/components/LocationsMap.svelte b/src/components/LocationsMap.svelte index 79ea4bf3..bd51b125 100644 --- a/src/components/LocationsMap.svelte +++ b/src/components/LocationsMap.svelte @@ -3,7 +3,7 @@ import { browser } from "$app/environment"; import Icon from "$comp/Icon.svelte"; import Popup from "$comp/Popup.svelte"; - import { shuffleArray } from "$lib/utils"; + import { back, shuffleArray } from "$lib/utils"; import "maplibre-gl/dist/maplibre-gl.css"; @@ -220,8 +220,16 @@ class="mx-auto h-full w-full z-0" bind:this={mapContainer} /> + <div class="absolute flex top-2 left-2 gap-2"> + <button + class="rounded-full border-2 border-black bg-white w-10 h-10" + on:click={back} + > + <Icon icon="arrow-left" style="w-4 m-auto" /> + </button> + </div> <div class="relative"> - <div class="flex absolute flex top-2 right-2 gap-2"> + <div class="absolute flex top-2 right-2 gap-2"> <button class="rounded-full border-2 border-black bg-white w-10 h-10" on:click={toggle} diff --git a/src/components/Mnemonic.svelte b/src/components/Mnemonic.svelte new file mode 100644 index 00000000..5e834aed --- /dev/null +++ b/src/components/Mnemonic.svelte @@ -0,0 +1,14 @@ +<script> + export let mnemonic; +</script> + +<div + class="text-lg border p-4 rounded-2xl text-secondary grid grid-cols-3 gap-2 justify-center" +> + {#each mnemonic.split(" ") as w, i} + <div> + <span class="select-none text-sm text-black">{i + 1}.</span> + <span>{@html w === "-" ? "—" : w}</span> + </div> + {/each} +</div> diff --git a/src/components/Numpad.svelte b/src/components/Numpad.svelte index 88b5b899..78322ff5 100644 --- a/src/components/Numpad.svelte +++ b/src/components/Numpad.svelte @@ -1,7 +1,7 @@ <script> import Left from "$comp/Left.svelte"; import Icon from "$comp/Icon.svelte"; - import { f, s, focus, post, warning, sat, sats } from "$lib/utils"; + import { f, s, focus, warning, sat, sats } from "$lib/utils"; import { t } from "$lib/translations"; export let amount, diff --git a/src/components/Request.svelte b/src/components/Request.svelte deleted file mode 100644 index c085f9e2..00000000 --- a/src/components/Request.svelte +++ /dev/null @@ -1,39 +0,0 @@ -<script> - import { t } from "$lib/translations"; - import { fly } from "svelte/transition"; - - import Avatar from "$comp/Avatar.svelte"; - import Icon from "$comp/Icon.svelte"; - - import { request, requestRedirect } from "$lib/store"; - import { page } from "$app/stores"; - - export let user; - - $requestRedirect = $page.url.pathname; -</script> - -{#if $request} - <div class="fixed bottom-0 border-t w-full p-4 bg-white z-50" in:fly> - <div class="flex flex-wrap md:flex-nowrap max-w-xl mx-auto gap-2"> - <div class="my-auto mx-auto"> - <div class="flex"> - <Avatar user={$request.requester} size={12} /> - <h1 class="my-auto"> - <b>{$request.requester.username}</b> - {$t("payments.readyToPay")} - </h1> - </div> - </div> - <div class="mx-auto my-auto flex gap-2"> - <a href={`/${user.username}/receive/${$request.id}`}> - <button - class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-32" - > - {$t("payments.invoice")} - </button> - </a> - </div> - </div> - </div> -{/if} diff --git a/src/components/WalletPass.svelte b/src/components/WalletPass.svelte new file mode 100644 index 00000000..75c94f34 --- /dev/null +++ b/src/components/WalletPass.svelte @@ -0,0 +1,65 @@ +<script> + import { focus } from "$lib/utils"; + import { t } from "$lib/translations"; + import Icon from "$comp/Icon.svelte"; + export let cancel, password, submit; + let revealPassword; +</script> + +<div + class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-20" +> + <div + class="relative top-1/3 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white space-y-5" + > + <h1 class="text-center text-2xl font-semibold"> + {$t("payments.enterWalletPass")} + </h1> + <form on:submit|preventDefault={submit}> + <div class="relative mb-5"> + {#if revealPassword} + <input + name="password" + type="text" + required + bind:value={password} + autocapitalize="none" + use:focus + /> + {:else} + <input + name="password" + type="password" + required + bind:value={password} + autocapitalize="none" + use:focus + /> + {/if} + <button + type="button" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-5 top-1/2" + > + <Icon icon={revealPassword ? "eye" : "eye-off"} /> + </button> + </div> + <div class="w-full flex justify-center gap-2"> + <button + type="button" + class="border rounded-2xl font-semibold mx-auto py-5 px-6 hover:opacity-80 mx-auto grow" + on:click={cancel} + on:keydown={cancel} + > + <div class="my-auto">Cancel</div> + </button> + <button + type="submit" + class="border rounded-2xl font-semibold mx-auto py-5 px-6 hover:opacity-80 mx-auto grow" + > + <div class="my-auto">Submit</div> + </button> + </div> + </form> + </div> +</div> |
