diff options
| author | Adam Soltys <[email protected]> | 2023-12-01 20:04:13 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2023-12-01 20:04:13 -0800 |
| commit | 216a600224fde22ce76b367557a02094ec693886 (patch) | |
| tree | d1189bbaa39db204633523132a7d4767b952717d | |
| parent | 7d9bc1f54dfd04172e8d6d43ebc2efd6f407e833 (diff) | |
| download | coinos-ui-216a600224fde22ce76b367557a02094ec693886.tar.gz coinos-ui-216a600224fde22ce76b367557a02094ec693886.zip | |
prettier
202 files changed, 13469 insertions, 12619 deletions
diff --git a/.prettierrc b/.prettierrc index 4d33dfc8..98ddc618 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,3 @@ { - "useTabs": true, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100, - "plugins": ["./node_modules/prettier-plugin-svelte"] + "plugins": ["./node_modules/prettier-plugin-svelte"] } diff --git a/doc/i18n-technical.md b/doc/i18n-technical.md index 3f6a09d5..6aed2068 100644 --- a/doc/i18n-technical.md +++ b/doc/i18n-technical.md @@ -23,11 +23,11 @@ Each locale in `coinos-ui-v2` is defined by a JSON file in `src/locales`. Its na ```json { - "howItWorks": "How it works", - "faq": "FAQ", - "about": "About", - "startInSeconds": "Start in Seconds", - "signIn": "Sign In" + "howItWorks": "How it works", + "faq": "FAQ", + "about": "About", + "startInSeconds": "Start in Seconds", + "signIn": "Sign In" } ``` diff --git a/docker-compose.yml b/docker-compose.yml index 7e6e4e61..e40cf483 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.6' +version: "3.6" services: v2: container_name: v2 @@ -6,18 +6,18 @@ services: context: . dockerfile: Dockerfile.dev args: - URL: '${PUBLIC_COINOS_URL}' - BTC: '${PUBLIC_BTC}' - SOCKET: '${PUBLIC_SOCKET}' + URL: "${PUBLIC_COINOS_URL}" + BTC: "${PUBLIC_BTC}" + SOCKET: "${PUBLIC_SOCKET}" restart: always ports: - - '3121:3121' + - "3121:3121" environment: NODE_ENV: production - ORIGIN: '${ORIGIN}' + ORIGIN: "${ORIGIN}" PORT: 3121 - STRIPE: '${STRIPE}' - LAUNCH: '${LAUNCH}' + STRIPE: "${STRIPE}" + LAUNCH: "${LAUNCH}" extra_hosts: - host.docker.internal:host-gateway networks: diff --git a/postcss.config.cjs b/postcss.config.cjs index 054c147c..12a703d9 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -1,6 +1,6 @@ module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {} - } + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, }; diff --git a/src/app.css b/src/app.css index 3a722fb1..ba1db128 100644 --- a/src/app.css +++ b/src/app.css @@ -3,39 +3,40 @@ @tailwind utilities; @font-face { - font-family: 'PublicSans'; - src: url('/fonts/Public_Sans/static/PublicSans-Regular.ttf') format('truetype'); + font-family: "PublicSans"; + src: url("/fonts/Public_Sans/static/PublicSans-Regular.ttf") + format("truetype"); } html { - font-family: PublicSans, sans-serif; + font-family: PublicSans, sans-serif; } ::selection { - color: black; - background: #eaf2ff; + color: black; + background: #eaf2ff; } :fullscreen { - background-color: white; + background-color: white; } input, textarea { - @apply block border rounded-2xl px-5 py-4 w-full; + @apply block border rounded-2xl px-5 py-4 w-full; } button.primary { - @apply rounded-full border py-2 font-bold my-4 flex px-4 bg-white mx-auto hover:opacity-80; + @apply rounded-full border py-2 font-bold my-4 flex px-4 bg-white mx-auto hover:opacity-80; } .select-styles { - @apply border rounded-2xl py-4 px-5; - cursor: pointer; - -webkit-appearance: none; - -moz-appearance: none; - background: transparent; - background-repeat: no-repeat; - background-position-x: 95%; - background-position-y: 1em; + @apply border rounded-2xl py-4 px-5; + cursor: pointer; + -webkit-appearance: none; + -moz-appearance: none; + background: transparent; + background-repeat: no-repeat; + background-position-x: 95%; + background-position-y: 1em; } diff --git a/src/app.html b/src/app.html index c0b7b4c1..28f07d06 100644 --- a/src/app.html +++ b/src/app.html @@ -1,15 +1,18 @@ <!DOCTYPE html> <html lang="en"> - <head> - <meta charset="utf-8" /> - <link rel="icon" href="%sveltekit.assets%/favicon.svg" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> - <meta name="description" content="An easy bitcoin web wallet" /> - <meta name="keywords" content="open source bitcoin lightning web wallet" /> - <link rel="manifest" href="/manifest.webmanifest" /> - %sveltekit.head% - </head> - <body> - <div>%sveltekit.body%</div> - </body> + <head> + <meta charset="utf-8" /> + <link rel="icon" href="%sveltekit.assets%/favicon.svg" /> + <meta + name="viewport" + content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" + /> + <meta name="description" content="An easy bitcoin web wallet" /> + <meta name="keywords" content="open source bitcoin lightning web wallet" /> + <link rel="manifest" href="/manifest.webmanifest" /> + %sveltekit.head% + </head> + <body> + <div>%sveltekit.body%</div> + </body> </html> diff --git a/src/components/404.svelte b/src/components/404.svelte index 08323d40..d831a40a 100644 --- a/src/components/404.svelte +++ b/src/components/404.svelte @@ -1,17 +1,17 @@ <script> - import { Icon } from '$comp'; - import { t } from '$lib/translations'; + import { Icon } from "$comp"; + import { t } from "$lib/translations"; </script> <div class="flex justify-center items-center mt-20"> - <div class="space-y-5 text-center"> - <a href="/"> - <Icon icon="logo" style="mx-auto" /> - </a> + <div class="space-y-5 text-center"> + <a href="/"> + <Icon icon="logo" style="mx-auto" /> + </a> - <Icon icon="question" style="mx-auto w-56" /> + <Icon icon="question" style="mx-auto w-56" /> - <h1 class="font-bold text-5xl">{$t('404.404')}</h1> - <p>{$t('404.pageNotFound')}</p> - </div> + <h1 class="font-bold text-5xl">{$t("404.404")}</h1> + <p>{$t("404.pageNotFound")}</p> + </div> </div> diff --git a/src/components/About.svelte b/src/components/About.svelte index 1dbd8038..6e0cdcb5 100644 --- a/src/components/About.svelte +++ b/src/components/About.svelte @@ -1,7 +1,7 @@ <script> import { onMount } from "svelte"; - let locations; + let locations; onMount(async () => { try { ({ locations } = await fetch("/locations").then((r) => r.json())); @@ -10,70 +10,85 @@ } }); - import { Image, Icon, LocationsMap } from '$comp'; - import { t } from '$lib/translations'; + import { Image, Icon, LocationsMap } from "$comp"; + import { t } from "$lib/translations"; - let team = [ - { - name: 'adam', - socials: [ - { type: 'twitter', url: 'https://twitter.com/adamsoltys', color: 'bg-[#1D9BF0]' }, - { type: 'github', url: 'https://github.com/asoltys', color: 'bg-gray-800' } - ] - }, - { name: 'cole', socials: [] } - ]; + let team = [ + { + name: "adam", + socials: [ + { + type: "twitter", + url: "https://twitter.com/adamsoltys", + color: "bg-[#1D9BF0]", + }, + { + type: "github", + url: "https://github.com/asoltys", + color: "bg-gray-800", + }, + ], + }, + { name: "cole", socials: [] }, + ]; </script> <div class="flex justify-center"> - <div class="container space-y-10"> - <h3 class="text-5xl font-medium text-center"> - {$t('about.team.header')} - </h3> - <div class="space-y-10 2xl:space-y-0 2xl:grid gap-y-10 grid-cols-2"> - {#each team as member} - <div class="flex w-full"> - <div class="md:flex 2xl:items-start md:max-w-2xl mx-auto"> - <div class="w-[220px] mx-auto"> - <Image image={member.name} style="rounded-full mx-auto w-40 h-40 mb-5 object-cover" /> + <div class="container space-y-10"> + <h3 class="text-5xl font-medium text-center"> + {$t("about.team.header")} + </h3> + <div class="space-y-10 2xl:space-y-0 2xl:grid gap-y-10 grid-cols-2"> + {#each team as member} + <div class="flex w-full"> + <div class="md:flex 2xl:items-start md:max-w-2xl mx-auto"> + <div class="w-[220px] mx-auto"> + <Image + image={member.name} + style="rounded-full mx-auto w-40 h-40 mb-5 object-cover" + /> - <div class="space-y-2"> - <span class="text-2xl text-center font-semibold block" - >{$t(`about.team.${member.name}.name`)}</span - > - <span class="text-xl text-center text-secondary block" - >{$t(`about.team.${member.name}.position`)}</span - > - </div> + <div class="space-y-2"> + <span class="text-2xl text-center font-semibold block" + >{$t(`about.team.${member.name}.name`)}</span + > + <span class="text-xl text-center text-secondary block" + >{$t(`about.team.${member.name}.position`)}</span + > + </div> - <div class="mt-2 flex justify-center items-center space-x-5"> - {#each member.socials as social} - <a - href={social.url} - target="_blank" - rel="noreferrer" - class={`rounded-full p-2 ${social.color} hover:opacity-80`} - ><Icon icon={social.type} style="w-6" /></a - > - {/each} - </div> - </div> - <div class="text-secondary text-xl w-full max-w-[450px] mt-5"> - {$t(`about.team.${member.name}.bio`)} - </div> - </div> - </div> - {/each} - </div> - </div> + <div class="mt-2 flex justify-center items-center space-x-5"> + {#each member.socials as social} + <a + href={social.url} + target="_blank" + rel="noreferrer" + class={`rounded-full p-2 ${social.color} hover:opacity-80`} + ><Icon icon={social.type} style="w-6" /></a + > + {/each} + </div> + </div> + <div class="text-secondary text-xl w-full max-w-[450px] mt-5"> + {$t(`about.team.${member.name}.bio`)} + </div> + </div> + </div> + {/each} + </div> + </div> </div> {#if locations?.length} - <div> - <h3 class="text-5xl font-medium mb-5 text-center">{$t('about.locations.header')}</h3> - <p class="text-secondary text-xl w-full md:w-10/12 lg:w-2/3 2xl:w-[800px] mx-auto mb-10"> - {@html $t('about.locations.description')} - </p> - <LocationsMap {locations} /> - </div> + <div> + <h3 class="text-5xl font-medium mb-5 text-center"> + {$t("about.locations.header")} + </h3> + <p + class="text-secondary text-xl w-full md:w-10/12 lg:w-2/3 2xl:w-[800px] mx-auto mb-10" + > + {@html $t("about.locations.description")} + </p> + <LocationsMap {locations} /> + </div> {/if} diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte index 53b31de4..2f088eec 100644 --- a/src/components/AppHeader.svelte +++ b/src/components/AppHeader.svelte @@ -1,149 +1,159 @@ <script> - import OutClick from 'svelte-outclick'; - import { banner, newPayment, colorTheme } from '$lib/store'; - import { goto } from '$app/navigation'; - import { Avatar, Icon } from '$comp'; - import { loading, t } from '$lib/translations'; - import { page } from '$app/stores'; + import OutClick from "svelte-outclick"; + import { banner, newPayment, colorTheme } from "$lib/store"; + import { goto } from "$app/navigation"; + import { Avatar, Icon } from "$comp"; + import { loading, t } from "$lib/translations"; + import { page } from "$app/stores"; - export let data; + export let data; - let { subject, user } = data; - $: update(data); - let update = (data) => ({ subject, user } = data); + let { subject, user } = data; + $: update(data); + let update = (data) => ({ subject, user } = data); - let menuButtons; - $: if (user) - menuButtons = [ - { key: 'nav.settings', icon: 'settings', href: `/${user.username}/settings` }, - { key: 'nav.support', icon: 'support', href: `/support` }, - { key: 'nav.map', icon: 'map', href: `/${user.username}/map` }, - { key: 'nav.merch', icon: 'shop', href: `https://coinosmerch.com` }, - { key: 'nav.signOut', icon: 'logout', href: `/logout` } - ]; + let menuButtons; + $: if (user) + menuButtons = [ + { + key: "nav.settings", + icon: "settings", + href: `/${user.username}/settings`, + }, + { key: "nav.support", icon: "support", href: `/support` }, + { key: "nav.map", icon: "map", href: `/${user.username}/map` }, + { key: "nav.merch", icon: "shop", href: `https://coinosmerch.com` }, + { key: "nav.signOut", icon: "logout", href: `/logout` }, + ]; - let showMenu = false; + let showMenu = false; - $: bg = - $banner?.id && $banner.id === subject.id - ? `url(${$banner.src})` - : subject?.banner - ? `url(/api/public/${subject.banner}.webp)` - : undefined; + $: bg = + $banner?.id && $banner.id === subject.id + ? `url(${$banner.src})` + : subject?.banner + ? `url(/api/public/${subject.banner}.webp)` + : undefined; - $: $page && (showMenu = false); + $: $page && (showMenu = false); </script> {#if !$loading} - <header - class="bg-gradient-to-r {$colorTheme} h-[175px] w-full relative bg-cover mb-20" - style:background-image={bg} - > - <nav class="flex justify-end items-center space-x-4 p-5"> - {#if user} - <a href={`/${user.username}`}> - <button - class="bg-white p-2 rounded-full w-12 h-12 md:w-16 md:h-16 drop-shadow-xl border border-black/10 {$page - .url.pathname === `/${user.username}` - ? 'opacity-100' - : 'opacity-70 hover:opacity-80'}" - ><Icon icon="home" style="mx-auto w-6 md:w-8" /> - </button> - </a> - <a href={`/${user.username}/receive`}> - <button - class="bg-white p-2 rounded-full w-12 h-12 md:w-16 md:h-16 drop-shadow-xl border border-black/10 {$page - .url.pathname === `/${user.username}/receive` - ? 'opacity-100' - : 'opacity-70 hover:opacity-80'}" - ><Icon icon="numpad" style="mx-auto w-6 md:w-8" /> - </button> - </a> - <a href={`/${user.username}/payments`}> - <button - class="bg-white p-2 rounded-full w-12 h-12 md:w-16 md:h-16 drop-shadow-xl border border-black/10 {$page - .url.pathname === `/${user.username}/payments` - ? 'opacity-100' - : 'opacity-70 hover:opacity-80'} relative" - ><Icon icon="clock" style="mx-auto w-6 md:w-8" /> - {#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={`/send`}> - <button - class="bg-white p-2 rounded-full w-12 h-12 md:w-16 md:h-16 drop-shadow-xl border border-black/10 {$page - .url.pathname === `/send` - ? 'opacity-100' - : 'opacity-70 hover:opacity-80'}" - ><Icon icon="send" style="mx-auto w-6 md:w-8" /> - </button> - </a> - <div class="relative"> - <OutClick on:outclick={() => (showMenu = false)}> - <button - class="bg-white p-2 rounded-full w-12 h-12 md:w-16 md:h-16 drop-shadow-xl border border-black/10 {$page - .url.pathname === `/${user.username}` || - $page.url.pathname === `/${user.username}/settings` || - $page.url.pathname === `/support` - ? 'opacity-100' - : `opacity-70 hover:opacity-80 ${showMenu ? 'opacity-80' : ''}`}" - on:click={() => (showMenu = !showMenu)} - ><Icon icon="menu" style="mx-auto w-6 md:w-8" /> - </button> + <header + class="bg-gradient-to-r {$colorTheme} h-[175px] w-full relative bg-cover mb-20" + style:background-image={bg} + > + <nav class="flex justify-end items-center space-x-4 p-5"> + {#if user} + <a href={`/${user.username}`}> + <button + class="bg-white p-2 rounded-full w-12 h-12 md:w-16 md:h-16 drop-shadow-xl border border-black/10 {$page + .url.pathname === `/${user.username}` + ? 'opacity-100' + : 'opacity-70 hover:opacity-80'}" + ><Icon icon="home" style="mx-auto w-6 md:w-8" /> + </button> + </a> + <a href={`/${user.username}/receive`}> + <button + class="bg-white p-2 rounded-full w-12 h-12 md:w-16 md:h-16 drop-shadow-xl border border-black/10 {$page + .url.pathname === `/${user.username}/receive` + ? 'opacity-100' + : 'opacity-70 hover:opacity-80'}" + ><Icon icon="numpad" style="mx-auto w-6 md:w-8" /> + </button> + </a> + <a href={`/${user.username}/payments`}> + <button + class="bg-white p-2 rounded-full w-12 h-12 md:w-16 md:h-16 drop-shadow-xl border border-black/10 {$page + .url.pathname === `/${user.username}/payments` + ? 'opacity-100' + : 'opacity-70 hover:opacity-80'} relative" + ><Icon icon="clock" style="mx-auto w-6 md:w-8" /> + {#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={`/send`}> + <button + class="bg-white p-2 rounded-full w-12 h-12 md:w-16 md:h-16 drop-shadow-xl border border-black/10 {$page + .url.pathname === `/send` + ? 'opacity-100' + : 'opacity-70 hover:opacity-80'}" + ><Icon icon="send" style="mx-auto w-6 md:w-8" /> + </button> + </a> + <div class="relative"> + <OutClick on:outclick={() => (showMenu = false)}> + <button + class="bg-white p-2 rounded-full w-12 h-12 md:w-16 md:h-16 drop-shadow-xl border border-black/10 {$page + .url.pathname === `/${user.username}` || + $page.url.pathname === `/${user.username}/settings` || + $page.url.pathname === `/support` + ? 'opacity-100' + : `opacity-70 hover:opacity-80 ${ + showMenu ? 'opacity-80' : '' + }`}" + on:click={() => (showMenu = !showMenu)} + ><Icon icon="menu" style="mx-auto w-6 md:w-8" /> + </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-48"> - {#each menuButtons as { href, icon, key }} - <li> - <a {href} data-sveltekit-prefetch="off"> - <button - class="flex justify-center items-center font-semibold hover:opacity-80" - ><Icon {icon} style="mr-2 w-6 md:w-8" /> {$t(key)} - </button> - </a> - </li> - {/each} - </ul> - <hr class="my-4" /> - <a href="/"><img src="/images/logo.svg" alt="Coinos" class="w-32" /></a> - </div> - </OutClick> - </div> - {:else} - <a href={`/login?redirect=${$page.url.pathname}`}> - <button class="bg-white px-5 py-2 rounded-xl font-semibold text-sm" - >{$t('nav.signIn')}</button - > - </a> - {/if} - </nav> - <div - class="absolute md:w-[64px] md:mx-auto lg:left-[164px] left-[calc(50vw-64px)] -bottom-[64px] z-30" - > - <Avatar user={subject} /> - </div> - </header> + <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-48"> + {#each menuButtons as { href, icon, key }} + <li> + <a {href} data-sveltekit-prefetch="off"> + <button + class="flex justify-center items-center font-semibold hover:opacity-80" + ><Icon {icon} style="mr-2 w-6 md:w-8" /> {$t(key)} + </button> + </a> + </li> + {/each} + </ul> + <hr class="my-4" /> + <a href="/" + ><img src="/images/logo.svg" alt="Coinos" class="w-32" /></a + > + </div> + </OutClick> + </div> + {:else} + <a href={`/login?redirect=${$page.url.pathname}`}> + <button class="bg-white px-5 py-2 rounded-xl font-semibold text-sm" + >{$t("nav.signIn")}</button + > + </a> + {/if} + </nav> + <div + class="absolute md:w-[64px] md:mx-auto lg:left-[164px] left-[calc(50vw-64px)] -bottom-[64px] z-30" + > + <Avatar user={subject} /> + </div> + </header> {/if} <style> - .z { - z-index: 100; - } + .z { + z-index: 100; + } - header { - view-transition-name: header; - } + header { + view-transition-name: header; + } </style> diff --git a/src/components/Avatar.svelte b/src/components/Avatar.svelte index b258a0e8..3955a42f 100644 --- a/src/components/Avatar.svelte +++ b/src/components/Avatar.svelte @@ -1,29 +1,31 @@ <script> - import { avatar, banner, colorTheme } from '$lib/store'; - import { Icon } from '$comp'; - import { punk } from '$lib/utils'; + import { avatar, banner, colorTheme } from "$lib/store"; + import { Icon } from "$comp"; + import { punk } from "$lib/utils"; - export let user; - export let size = 32; - export let disabled = false; + export let user; + export let size = 32; + export let disabled = false; - $: s = size.toString(); - $: link = user.anon ? `/${user.pubkey}` : `/${user.username}`; + $: s = size.toString(); + $: link = user.anon ? `/${user.pubkey}` : `/${user.username}`; - $: src = - ($avatar?.id && $avatar.id === user.id && $avatar.src) || - '/api/public/' + - (user.profile ? user.profile + '.webp' : 'punks/' + punk(user.pubkey || user.id || 'aa')); + $: src = + ($avatar?.id && $avatar.id === user.id && $avatar.src) || + "/api/public/" + + (user.profile + ? user.profile + ".webp" + : "punks/" + punk(user.pubkey || user.id || "aa")); </script> <a href={link} class:pointer-events-none={disabled}> - <div - class="w-{s} h-{s} rounded-full border-4 border-white overflow-hidden bg-gradient-to-r {$colorTheme} flex justify-center items-center" - > - <img - {src} - class="w-full h-full object-cover object-center overflow-hidden" - alt={user.username} - /> - </div> + <div + class="w-{s} h-{s} rounded-full border-4 border-white overflow-hidden bg-gradient-to-r {$colorTheme} flex justify-center items-center" + > + <img + {src} + class="w-full h-full object-cover object-center overflow-hidden" + alt={user.username} + /> + </div> </a> diff --git a/src/components/Balance.svelte b/src/components/Balance.svelte index 0e4f2ec2..92714f98 100644 --- a/src/components/Balance.svelte +++ b/src/components/Balance.svelte @@ -1,34 +1,34 @@ <script> - import { Pin } from '$comp'; - import { f, s, sat, sats } from '$lib/utils'; - import { animatedRate, pin } from '$lib/store'; - import { t } from '$lib/translations'; + import { Pin } from "$comp"; + import { f, s, sat, sats } from "$lib/utils"; + import { animatedRate, pin } from "$lib/store"; + import { t } from "$lib/translations"; - let show; - export let user; + let show; + export let user; </script> {#if show && user.haspin && $pin?.length !== 6} - <Pin cancel={() => (show = false)} /> + <Pin cancel={() => (show = false)} /> {/if} <div class="space-y-2"> - {#if user.haspin && !$pin} - <button - class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px] bg-black text-white" - on:click={() => (show = true)}>{$t('user.showBalance')}</button - > - {:else} - <div class="text-5xl font-bold tabular-nums"> - {#if isNaN($animatedRate)} - <div class="text-gray-200">—</div> - {:else} - {f((user.balance * $animatedRate) / sats, user.currency)} - {/if} - </div> + {#if user.haspin && !$pin} + <button + class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px] bg-black text-white" + on:click={() => (show = true)}>{$t("user.showBalance")}</button + > + {:else} + <div class="text-5xl font-bold tabular-nums"> + {#if isNaN($animatedRate)} + <div class="text-gray-200">—</div> + {:else} + {f((user.balance * $animatedRate) / sats, user.currency)} + {/if} + </div> - <div class="text-secondary text-2xl"> - ⚡️{s(user.balance)} - </div> - {/if} + <div class="text-secondary text-2xl"> + ⚡️{s(user.balance)} + </div> + {/if} </div> diff --git a/src/components/Calendar.svelte b/src/components/Calendar.svelte index 7504f48e..b07c136c 100644 --- a/src/components/Calendar.svelte +++ b/src/components/Calendar.svelte @@ -1,14 +1,14 @@ <svg - xmlns="http://www.w3.org/2000/svg" - class="absolute bottom-0 left-0 -mb-0.5 transform translate-x-1/2 -translate-y-1/2 text-black peer-placeholder-shown:text-gray-300 h-6 w-6" - fill="none" - viewBox="0 0 24 24" - stroke="currentColor" + xmlns="http://www.w3.org/2000/svg" + class="absolute bottom-0 left-0 -mb-0.5 transform translate-x-1/2 -translate-y-1/2 text-black peer-placeholder-shown:text-gray-300 h-6 w-6" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" - /> + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" + /> </svg> diff --git a/src/components/Card.svelte b/src/components/Card.svelte index 44ab635f..864465c0 100644 --- a/src/components/Card.svelte +++ b/src/components/Card.svelte @@ -1,14 +1,14 @@ <svg - xmlns="http://www.w3.org/2000/svg" - class="absolute bottom-0 left-0 -mb-0.5 transform translate-x-1/2 -translate-y-1/2 text-black peer-placeholder-shown:text-gray-300 h-6 w-6" - fill="none" - viewBox="0 0 24 24" - stroke="currentColor" + xmlns="http://www.w3.org/2000/svg" + class="absolute bottom-0 left-0 -mb-0.5 transform translate-x-1/2 -translate-y-1/2 text-black peer-placeholder-shown:text-gray-300 h-6 w-6" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" - /> + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" + /> </svg> diff --git a/src/components/Check.svelte b/src/components/Check.svelte index 4c9f504b..222c4dea 100644 --- a/src/components/Check.svelte +++ b/src/components/Check.svelte @@ -1,11 +1,11 @@ <svg - class="flex-shrink-0 w-5 h-5 text-gray-500 mr-1" - fill="currentColor" - viewBox="0 0 20 20" - xmlns="http://www.w3.org/2000/svg" - ><path - fill-rule="evenodd" - d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" - clip-rule="evenodd" - /></svg + class="flex-shrink-0 w-5 h-5 text-gray-500 mr-1" + fill="currentColor" + viewBox="0 0 20 20" + xmlns="http://www.w3.org/2000/svg" + ><path + fill-rule="evenodd" + d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" + clip-rule="evenodd" + /></svg > diff --git a/src/components/Code.svelte b/src/components/Code.svelte index ffca98a4..6c1048cf 100644 --- a/src/components/Code.svelte +++ b/src/components/Code.svelte @@ -1,27 +1,31 @@ <script> - import { copyNoNewlines as copy } from '$lib/utils'; - import { Icon } from '$comp'; - import samples from '$lib/samples'; - export let sample; + import { copyNoNewlines as copy } from "$lib/utils"; + import { Icon } from "$comp"; + import samples from "$lib/samples"; + export let sample; </script> -<div class="bg-black text-white rounded-lg p-4 flex gap-4 whitespace-pre-wrap font-mono"> - <div class="overflow-x-scroll max-w-full scrollbar-hide"> - <div class="break-all min-w-[800px]">{samples[sample]}</div> - </div> - <button class="ml-auto my-auto invert opacity-90" on:click={() => copy(samples[sample])} - ><Icon icon="copy" style="w-10 max-w-none" /></button - > +<div + class="bg-black text-white rounded-lg p-4 flex gap-4 whitespace-pre-wrap font-mono" +> + <div class="overflow-x-scroll max-w-full scrollbar-hide"> + <div class="break-all min-w-[800px]">{samples[sample]}</div> + </div> + <button + class="ml-auto my-auto invert opacity-90" + on:click={() => copy(samples[sample])} + ><Icon icon="copy" style="w-10 max-w-none" /></button + > </div> <style> - .scrollbar-hide::-webkit-scrollbar { - display: none; - } + .scrollbar-hide::-webkit-scrollbar { + display: none; + } - /* For IE, Edge and Firefox */ - .scrollbar-hide { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ - } + /* For IE, Edge and Firefox */ + .scrollbar-hide { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } </style> diff --git a/src/components/Event.svelte b/src/components/Event.svelte index 68f44589..2312f44e 100644 --- a/src/components/Event.svelte +++ b/src/components/Event.svelte @@ -1,84 +1,87 @@ <script> - import { Avatar, Icon } from '$comp'; - export let event; + import { Avatar, Icon } from "$comp"; + export let event; - let { id, user, seen, content } = event; + let { id, user, seen, content } = event; - let distance = (date) => { - let m = 60; - let h = 60 * m; - let d = 24 * h; + let distance = (date) => { + let m = 60; + let h = 60 * m; + let d = 24 * h; - let diff = Math.round(Date.now() / 1000) - date; - return diff > d - ? Math.round(diff / d) + 'd' - : diff > h - ? Math.round(diff / h) + 'h' - : diff > m - ? Math.round(diff / m) + 'm' - : diff > 2 - ? diff + 's' - : 'now'; - }; + let diff = Math.round(Date.now() / 1000) - date; + return diff > d + ? Math.round(diff / d) + "d" + : diff > h + ? Math.round(diff / h) + "h" + : diff > m + ? Math.round(diff / m) + "m" + : diff > 2 + ? diff + "s" + : "now"; + }; - let w; + let w; </script> <svelte:window bind:innerWidth={w} /> -<div class="flex text-sm lg:text-base text-secondary hover:bg-gray-100 px-4 py-3" :key={id}> - <a href={`/${user.pubkey}`}> - <div class="mb-auto mr-2"> - <div class="md:hidden"> - <Avatar size={12} {user} disabled={true} /> - </div> - <div class="hidden md:block"> - <Avatar size={20} {user} disabled={true} /> - </div> - </div> - </a> +<div + class="flex text-sm lg:text-base text-secondary hover:bg-gray-100 px-4 py-3" + :key={id} +> + <a href={`/${user.pubkey}`}> + <div class="mb-auto mr-2"> + <div class="md:hidden"> + <Avatar size={12} {user} disabled={true} /> + </div> + <div class="hidden md:block"> + <Avatar size={20} {user} disabled={true} /> + </div> + </div> + </a> - <div class="grow" style={`max-width: ${w - 100}px`}> - <div class="w-full flex pb-1 text-black pt-1"> - <div> - {user.display_name || ''} - <span class="text-secondary">@{user.username}</span> - </div> - <div class="text-secondary"> {distance(seen)}</div> - </div> - <div class="break-words text-black"> - {content} - </div> + <div class="grow" style={`max-width: ${w - 100}px`}> + <div class="w-full flex pb-1 text-black pt-1"> + <div> + {user.display_name || ""} + <span class="text-secondary">@{user.username}</span> + </div> + <div class="text-secondary"> {distance(seen)}</div> + </div> + <div class="break-words text-black"> + {content} + </div> - <div class="flex justify-between text-sm text-secondary pt-3"> - <div class="flex group"> - <Icon - icon="party" - style="mr-1 w-10 my-auto opacity-50 group-hover:opacity-100 group-hover:bg-white rounded-full p-2" - /> - <div class="my-auto group-hover:text-black">444</div> - </div> - <div class="flex group"> - <Icon - icon="support" - style="mr-1 w-10 my-auto opacity-50 group-hover:opacity-100 group-hover:bg-white rounded-full p-2" - /> - <div class="my-auto group-hover:text-black">1072</div> - </div> - <div class="flex group"> - <Icon - icon="smile" - style="mr-1 w-10 my-auto opacity-50 group-hover:opacity-100 group-hover:bg-white rounded-full p-2" - /> - <div class="my-auto group-hover:text-black">26</div> - </div> - <div class="flex group"> - <Icon - icon="bolt" - style="mr-1 h-10 my-auto opacity-50 group-hover:opacity-100 group-hover:bg-white rounded-full p-2" - /> - <div class="my-auto group-hover:text-black">4</div> - </div> - </div> - </div> + <div class="flex justify-between text-sm text-secondary pt-3"> + <div class="flex group"> + <Icon + icon="party" + style="mr-1 w-10 my-auto opacity-50 group-hover:opacity-100 group-hover:bg-white rounded-full p-2" + /> + <div class="my-auto group-hover:text-black">444</div> + </div> + <div class="flex group"> + <Icon + icon="support" + style="mr-1 w-10 my-auto opacity-50 group-hover:opacity-100 group-hover:bg-white rounded-full p-2" + /> + <div class="my-auto group-hover:text-black">1072</div> + </div> + <div class="flex group"> + <Icon + icon="smile" + style="mr-1 w-10 my-auto opacity-50 group-hover:opacity-100 group-hover:bg-white rounded-full p-2" + /> + <div class="my-auto group-hover:text-black">26</div> + </div> + <div class="flex group"> + <Icon + icon="bolt" + style="mr-1 h-10 my-auto opacity-50 group-hover:opacity-100 group-hover:bg-white rounded-full p-2" + /> + <div class="my-auto group-hover:text-black">4</div> + </div> + </div> + </div> </div> diff --git a/src/components/FaqCard.svelte b/src/components/FaqCard.svelte index 918e03d6..ae1ad077 100644 --- a/src/components/FaqCard.svelte +++ b/src/components/FaqCard.svelte @@ -1,15 +1,15 @@ <script> - import { t } from '$lib/translations'; + import { t } from "$lib/translations"; - // this ID will be used to get the strings from the locale - // question is at faq.[questionID].question - // answer is at faq.[questionID].answer - export let questionID; + // this ID will be used to get the strings from the locale + // question is at faq.[questionID].question + // answer is at faq.[questionID].answer + export let questionID; </script> <div class="mx-auto w-full md:w-2/3 lg:w-[700px] space-y-5"> - <p class="text-2xl font-medium">{$t('faq.' + questionID + '.question')}</p> - <p class="text-secondary text-xl font-medium"> - {$t('faq.' + questionID + '.answer')} - </p> + <p class="text-2xl font-medium">{$t("faq." + questionID + ".question")}</p> + <p class="text-secondary text-xl font-medium"> + {$t("faq." + questionID + ".answer")} + </p> </div> diff --git a/src/components/Feed.svelte b/src/components/Feed.svelte index 7de087a7..0c9c96d1 100644 --- a/src/components/Feed.svelte +++ b/src/components/Feed.svelte @@ -1,72 +1,84 @@ <script> - import { t } from '$lib/translations'; - import { onMount } from 'svelte'; - import { Event } from '$comp'; - import { browser } from '$app/environment'; - import VirtualScroll from 'svelte-virtual-scroll-list'; - import { fail } from '$lib/utils'; - import { sign, send } from '$lib/nostr'; + import { t } from "$lib/translations"; + import { onMount } from "svelte"; + import { Event } from "$comp"; + import { browser } from "$app/environment"; + import VirtualScroll from "svelte-virtual-scroll-list"; + import { fail } from "$lib/utils"; + import { sign, send } from "$lib/nostr"; - export let events; - export let user = undefined; + export let events; + export let user = undefined; - let message; + let message; - let submit = async () => { - try { - let mnemonic, key, seed, entropy, child, privkey; + let submit = async () => { + try { + let mnemonic, key, seed, entropy, child, privkey; - let event = { - pubkey: user.pubkey, - created_at: Math.floor(Date.now() / 1000), - kind: 1, - content: message, - tags: [] - }; + let event = { + pubkey: user.pubkey, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + content: message, + tags: [], + }; - await sign({ event, user }); - await send(event); + await sign({ event, user }); + await send(event); - event.user = user; - event.seen = event.created_at; + event.user = user; + event.seen = event.created_at; - events[event.id] = event; - events = events; - } catch (e) { - console.log(e, e.stack); - fail('Problem posting event'); - } - }; + events[event.id] = event; + events = events; + } catch (e) { + console.log(e, e.stack); + fail("Problem posting event"); + } + }; - $: sorted = Object.values(events) - .filter((ev) => ev?.pubkey && ev?.kind === 1) - .sort((a, b) => b.seen - a.seen); + $: sorted = Object.values(events) + .filter((ev) => ev?.pubkey && ev?.kind === 1) + .sort((a, b) => b.seen - a.seen); </script> {#if user} - <form on:submit|preventDefault={submit} class="flex justify-center gap-4 max-w-2xl mx-auto"> - <div class="grow"> - <input - name="message" - bind:value={message} - placeholder="What's happening?" - class="my-5 mx-auto" - /> - </div> - <button class="rounded-full border py-4 px-5 font-bold hover:opacity-80 w-40 my-5">Post</button> - </form> + <form + on:submit|preventDefault={submit} + class="flex justify-center gap-4 max-w-2xl mx-auto" + > + <div class="grow"> + <input + name="message" + bind:value={message} + placeholder="What's happening?" + class="my-5 mx-auto" + /> + </div> + <button + class="rounded-full border py-4 px-5 font-bold hover:opacity-80 w-40 my-5" + >Post</button + > + </form> {/if} {#if browser} - {#if sorted.length} - <VirtualScroll data={sorted} key="id" let:data pageMode={true} estimateSize={200}> - <a href={`/${data.user.pubkey}/event/${data.id}`}> - <Event event={data} /> - </a> - </VirtualScroll> - {:else} - <div class="flex w-full"> - <div class="mx-auto text-secondary">{$t('user.noNotes')}</div> - </div> - {/if} + {#if sorted.length} + <VirtualScroll + data={sorted} + key="id" + let:data + pageMode={true} + estimateSize={200} + > + <a href={`/${data.user.pubkey}/event/${data.id}`}> + <Event event={data} /> + </a> + </VirtualScroll> + {:else} + <div class="flex w-full"> + <div class="mx-auto text-secondary">{$t("user.noNotes")}</div> + </div> + {/if} {/if} diff --git a/src/components/Footer.svelte b/src/components/Footer.svelte index eda26526..c1ac79cf 100644 --- a/src/components/Footer.svelte +++ b/src/components/Footer.svelte @@ -1,65 +1,74 @@ <script> - import { Icon, LocaleSelector } from '$comp'; - import { t } from '$lib/translations'; + import { Icon, LocaleSelector } from "$comp"; + import { t } from "$lib/translations"; - const followLinks = [ - { url: 'https://twitter.com/coinoswallet', titleID: 'twitter' }, - { url: 'https://t.me/coinoswallet', titleID: 'telegram' }, - { url: 'https://linktr.ee/coinos.io', titleID: 'linktree' } - ]; + const followLinks = [ + { url: "https://twitter.com/coinoswallet", titleID: "twitter" }, + { url: "https://t.me/coinoswallet", titleID: "telegram" }, + { url: "https://linktr.ee/coinos.io", titleID: "linktree" }, + ]; - const companyLinks = [ - { url: '/docs', titleID: 'documentation' }, - { url: '/support', titleID: 'support' } - ]; + const companyLinks = [ + { url: "/docs", titleID: "documentation" }, + { url: "/support", titleID: "support" }, + ]; </script> -<footer class="space-y-10 md:space-y-0 text-xl block md:flex justify-around mb-20"> - <div class="space-y-5"> - <div class="text-center w-full"> - <a href="/"><Icon icon="logo" style="w-48 mx-auto md:mx-0" /></a> - </div> - <div class="flex gap-2 justify-center sm:hidden"> - <div> - <a href="/register"> - <button - class="bg-black text-white border rounded-full px-4 py-2 font-bold hover:opacity-80 text-lg" - > - {$t('nav.startInSeconds')} - </button> - </a> - </div> - </div> - <div class="w-full flex"> - <div class="mx-auto"> - <LocaleSelector - style="p-2 text-center hover:opacity-80 cursor-pointer border rounded-full" - /> - </div> - </div> - </div> +<footer + class="space-y-10 md:space-y-0 text-xl block md:flex justify-around mb-20" +> + <div class="space-y-5"> + <div class="text-center w-full"> + <a href="/"><Icon icon="logo" style="w-48 mx-auto md:mx-0" /></a> + </div> + <div class="flex gap-2 justify-center sm:hidden"> + <div> + <a href="/register"> + <button + class="bg-black text-white border rounded-full px-4 py-2 font-bold hover:opacity-80 text-lg" + > + {$t("nav.startInSeconds")} + </button> + </a> + </div> + </div> + <div class="w-full flex"> + <div class="mx-auto"> + <LocaleSelector + style="p-2 text-center hover:opacity-80 cursor-pointer border rounded-full" + /> + </div> + </div> + </div> - <div class="text-center md:text-left"> - <p class="font-semibold">{$t('footer.followUs')}</p> - <ul class="mt-5 text-secondary space-y-3 font-medium"> - {#each followLinks as link} - <li> - <a href={link.url} target="_blank" rel="noreferrer" class="hover:opacity-80" - >{$t('footer.' + link.titleID)}</a - > - </li> - {/each} - </ul> - </div> + <div class="text-center md:text-left"> + <p class="font-semibold">{$t("footer.followUs")}</p> + <ul class="mt-5 text-secondary space-y-3 font-medium"> + {#each followLinks as link} + <li> + <a + href={link.url} + target="_blank" + rel="noreferrer" + class="hover:opacity-80">{$t("footer." + link.titleID)}</a + > + </li> + {/each} + </ul> + </div> - <div class="text-center md:text-left"> - <p class="font-semibold">{$t('footer.company')}</p> - <ul class="mt-5 text-secondary space-y-3"> - {#each companyLinks as link} - <li><a href={link.url} class="hover:opacity-80">{$t('footer.' + link.titleID)}</a></li> - {/each} - </ul> - </div> + <div class="text-center md:text-left"> + <p class="font-semibold">{$t("footer.company")}</p> + <ul class="mt-5 text-secondary space-y-3"> + {#each companyLinks as link} + <li> + <a href={link.url} class="hover:opacity-80" + >{$t("footer." + link.titleID)}</a + > + </li> + {/each} + </ul> + </div> - <Icon icon="shield" style="mx-auto md:mx-0" /> + <Icon icon="shield" style="mx-auto md:mx-0" /> </footer> diff --git a/src/components/Heart.svelte b/src/components/Heart.svelte index 55b13fa9..9bae6500 100644 --- a/src/components/Heart.svelte +++ b/src/components/Heart.svelte @@ -1,16 +1,16 @@ <svg - width="24" - height="24" - class="mr-1" - viewBox="0 0 24 24" - fill="none" - xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + class="mr-1" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" > - <path - d="M4.31802 6.31802C2.56066 8.07538 2.56066 10.9246 4.31802 12.682L12.0001 20.364L19.682 12.682C21.4393 10.9246 21.4393 8.07538 19.682 6.31802C17.9246 4.56066 15.0754 4.56066 13.318 6.31802L12.0001 7.63609L10.682 6.31802C8.92462 4.56066 6.07538 4.56066 4.31802 6.31802Z" - stroke="currentColor" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" - /> + <path + d="M4.31802 6.31802C2.56066 8.07538 2.56066 10.9246 4.31802 12.682L12.0001 20.364L19.682 12.682C21.4393 10.9246 21.4393 8.07538 19.682 6.31802C17.9246 4.56066 15.0754 4.56066 13.318 6.31802L12.0001 7.63609L10.682 6.31802C8.92462 4.56066 6.07538 4.56066 4.31802 6.31802Z" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + /> </svg> diff --git a/src/components/HowItWorksCard.svelte b/src/components/HowItWorksCard.svelte index 784c738d..e5e62d05 100644 --- a/src/components/HowItWorksCard.svelte +++ b/src/components/HowItWorksCard.svelte @@ -1,14 +1,14 @@ <script> - import Icon from './Icon.svelte'; - import { t } from '$lib/translations'; - export let image; - export let stepID; + import Icon from "./Icon.svelte"; + import { t } from "$lib/translations"; + export let image; + export let stepID; </script> <div class="space-y-5"> - <Icon icon={image} style="mx-auto w-32 h-32" /> - <p class="text-2xl font-medium">{$t('howItWorks.' + stepID + '.header')}</p> - <p class="text-secondary text-xl font-medium w-full md:w-2/3 mx-auto"> - {$t('howItWorks.' + stepID + '.description')} - </p> + <Icon icon={image} style="mx-auto w-32 h-32" /> + <p class="text-2xl font-medium">{$t("howItWorks." + stepID + ".header")}</p> + <p class="text-secondary text-xl font-medium w-full md:w-2/3 mx-auto"> + {$t("howItWorks." + stepID + ".description")} + </p> </div> diff --git a/src/components/Icon.svelte b/src/components/Icon.svelte index 4b86cc43..0b47f726 100644 --- a/src/components/Icon.svelte +++ b/src/components/Icon.svelte @@ -1,6 +1,6 @@ <script> - export let icon; - export let style = undefined; + export let icon; + export let style = undefined; </script> <img src="/icons/{icon}.svg" alt={icon} class={style} /> diff --git a/src/components/Image.svelte b/src/components/Image.svelte index dcd9d3ff..4f8b39bb 100644 --- a/src/components/Image.svelte +++ b/src/components/Image.svelte @@ -1,6 +1,6 @@ <script> - export let image; - export let style; + export let image; + export let style; </script> <img src="/images/{image}.webp" alt={image} class={style} /> diff --git a/src/components/Invoice.svelte b/src/components/Invoice.svelte index b0cd58a2..363d260a 100644 --- a/src/components/Invoice.svelte +++ b/src/components/Invoice.svelte @@ -1,32 +1,39 @@ <script> - import { t } from '$lib/translations'; - import { f, sat, sats } from '$lib/utils'; - import { fly } from 'svelte/transition'; - import { Avatar, Icon } from '$comp'; - import { invoice } from '$lib/store'; + import { t } from "$lib/translations"; + import { f, sat, sats } from "$lib/utils"; + import { fly } from "svelte/transition"; + import { Avatar, Icon } from "$comp"; + import { invoice } from "$lib/store"; </script> {#if $invoice} - <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={$invoice.user.username} size={12} /> - <h1 class="my-auto"> - <b>{$invoice.user.username}</b> - {$t('payments.isRequesting')} - <b>{f(($invoice.amount * $invoice.rate) / sats, $invoice.currency)}</b> - {sat($invoice.amount)} - </h1> - </div> - </div> - <div class="mx-auto my-auto flex gap-2"> - <a href={`/${invoice.user.username}/invoice/${$invoice.id}/tip`}> - <button class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-32"> - {$t('payments.pay')} - </button> - </a> - </div> - </div> - </div> + <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={$invoice.user.username} size={12} /> + <h1 class="my-auto"> + <b>{$invoice.user.username}</b> + {$t("payments.isRequesting")} + <b + >{f( + ($invoice.amount * $invoice.rate) / sats, + $invoice.currency + )}</b + > + {sat($invoice.amount)} + </h1> + </div> + </div> + <div class="mx-auto my-auto flex gap-2"> + <a href={`/${invoice.user.username}/invoice/${$invoice.id}/tip`}> + <button + class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-32" + > + {$t("payments.pay")} + </button> + </a> + </div> + </div> + </div> {/if} diff --git a/src/components/LandingHeader.svelte b/src/components/LandingHeader.svelte index ed7907c1..556d44a3 100644 --- a/src/components/LandingHeader.svelte +++ b/src/components/LandingHeader.svelte @@ -1,125 +1,133 @@ <script> - import { goto } from '$app/navigation'; - import { scroll } from '$lib/utils'; - import { Icon } from '$comp'; - import { t } from '$lib/translations'; - import { page } from '$app/stores'; + import { goto } from "$app/navigation"; + import { scroll } from "$lib/utils"; + import { Icon } from "$comp"; + import { t } from "$lib/translations"; + import { page } from "$app/stores"; - export let howItWorks; - export let faq; - export let about; - export let user; + export let howItWorks; + export let faq; + export let about; + export let user; - let showMobileMenu = false; - let header; - const mobileMenuButtonClick = (section) => { - showMobileMenu = false; - scroll(section); - }; + let showMobileMenu = false; + let header; + const mobileMenuButtonClick = (section) => { + showMobileMenu = false; + scroll(section); + }; </script> <header - class="w-full lg:w-5/6 py-5 mx-auto fixed md:sticky z-10 top-0 bg-white/90" - bind:this={header} + class="w-full lg:w-5/6 py-5 mx-auto fixed md:sticky z-10 top-0 bg-white/90" + bind:this={header} > - <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="/" 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" /> - </a> - </div> + <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="/" 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" /> + </a> + </div> - <!-- desktop nav --> - <div class="hidden space-x-10 md:flex flex-wrap justify-center items-center font-bold"> - {#if $page.url.pathname === '/'} - <button class="hover:opacity-80" on:click={() => scroll(howItWorks)} - >{$t('howItWorks.header')}</button - > - <button class="hover:opacity-80" on:click={() => scroll(faq)}>{$t('faq.header')}</button> - <button class="hover:opacity-80" on:click={() => scroll(about)}>{$t('about.header')}</button - > - {/if} - {#if user} - <button - class="border rounded-full px-6 py-2 font-bold" - on:click={() => goto(`/${user.username}`)} - >Home - </button> - <button - class="bg-black text-white border rounded-full px-6 py-2 font-bold" - on:click={() => goto('/logout')} - > - {$t('nav.signOut')} - </button> - {:else} - <button - class="border rounded-full px-6 py-2 font-bold hover:opacity-80" - on:click={() => goto('/register')} - >{$t('nav.startInSeconds')} - </button> - <button - class="bg-black text-white border rounded-full px-6 py-2 font-bold hover:opacity-80" - on:click={() => goto('/login')} - > - {$t('nav.signIn')} - </button> - {/if} - </div> + <!-- desktop nav --> + <div + class="hidden space-x-10 md:flex flex-wrap justify-center items-center font-bold" + > + {#if $page.url.pathname === "/"} + <button class="hover:opacity-80" on:click={() => scroll(howItWorks)} + >{$t("howItWorks.header")}</button + > + <button class="hover:opacity-80" on:click={() => scroll(faq)} + >{$t("faq.header")}</button + > + <button class="hover:opacity-80" on:click={() => scroll(about)} + >{$t("about.header")}</button + > + {/if} + {#if user} + <button + class="border rounded-full px-6 py-2 font-bold" + on:click={() => goto(`/${user.username}`)} + >Home + </button> + <button + class="bg-black text-white border rounded-full px-6 py-2 font-bold" + on:click={() => goto("/logout")} + > + {$t("nav.signOut")} + </button> + {:else} + <button + class="border rounded-full px-6 py-2 font-bold hover:opacity-80" + on:click={() => goto("/register")} + >{$t("nav.startInSeconds")} + </button> + <button + class="bg-black text-white border rounded-full px-6 py-2 font-bold hover:opacity-80" + on:click={() => goto("/login")} + > + {$t("nav.signIn")} + </button> + {/if} + </div> - <!-- mobile nav --> - <button - class="block md:hidden absolute top-[34.5px] right-10 z" - on:click={() => (showMobileMenu = !showMobileMenu)} - ><Icon icon={!showMobileMenu ? 'menu' : 'close'} /> - </button> + <!-- mobile nav --> + <button + class="block md: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 - ? 'right-0' - : 'right-[-100%]'} transition-all ease-in-out duration-300 h-[100vh] w-full bg-white" - > - <div class="space-y-8 mt-24 font-bold text-xl"> - <button on:click={() => mobileMenuButtonClick(howItWorks)} class="block" - >{$t('howItWorks.header')}</button - > - <button on:click={() => mobileMenuButtonClick(faq)} class="block">{$t('faq.header')}</button - > - <button on:click={() => mobileMenuButtonClick(about)} class="block" - >{$t('about.header')}</button - > - {#if !user} - <button - class="border rounded-full px-6 py-2 font-bold block w-full" - on:click={() => goto('/register')} - >{$t('nav.startInSeconds')} - </button> - <button - class="bg-black text-white border rounded-full px-6 py-2 font-bold block w-full" - on:click={() => goto('/login')} - > - {$t('nav.signIn')} - </button> - {:else} - <button - class="border rounded-full px-6 py-2 font-bold block" - on:click={() => goto(`/${user.username}`)} - >{$t('nav.account')} - </button> - <button - class="bg-black text-white border rounded-full px-6 py-2 font-bold block" - on:click={() => goto('/logout')} - > - {$t('nav.signOut')} - </button> - {/if} - </div> - </div> - </nav> + <div + class="container w-full px-10 md:hidden absolute top-0 {showMobileMenu + ? 'right-0' + : 'right-[-100%]'} transition-all ease-in-out duration-300 h-[100vh] w-full bg-white" + > + <div class="space-y-8 mt-24 font-bold text-xl"> + <button on:click={() => mobileMenuButtonClick(howItWorks)} class="block" + >{$t("howItWorks.header")}</button + > + <button on:click={() => mobileMenuButtonClick(faq)} class="block" + >{$t("faq.header")}</button + > + <button on:click={() => mobileMenuButtonClick(about)} class="block" + >{$t("about.header")}</button + > + {#if !user} + <button + class="border rounded-full px-6 py-2 font-bold block w-full" + on:click={() => goto("/register")} + >{$t("nav.startInSeconds")} + </button> + <button + class="bg-black text-white border rounded-full px-6 py-2 font-bold block w-full" + on:click={() => goto("/login")} + > + {$t("nav.signIn")} + </button> + {:else} + <button + class="border rounded-full px-6 py-2 font-bold block" + on:click={() => goto(`/${user.username}`)} + >{$t("nav.account")} + </button> + <button + class="bg-black text-white border rounded-full px-6 py-2 font-bold block" + on:click={() => goto("/logout")} + > + {$t("nav.signOut")} + </button> + {/if} + </div> + </div> + </nav> </header> <style> - .z { - z-index: 100; - } + .z { + z-index: 100; + } </style> diff --git a/src/components/LandingHero.svelte b/src/components/LandingHero.svelte index 8e1d6476..e03bfdc2 100644 --- a/src/components/LandingHero.svelte +++ b/src/components/LandingHero.svelte @@ -1,37 +1,40 @@ <script> - import { goto } from '$app/navigation'; - import { Icon, Image } from '$comp'; - import { t } from '$lib/translations'; + import { goto } from "$app/navigation"; + import { Icon, Image } from "$comp"; + import { t } from "$lib/translations"; </script> <div class="mt-[96.88px] md:mt-0"> - <div class="space-y-10"> - <h1 - class="text-5xl md:text-6xl lg:text-7xl font-medium text-center w-full md:w-3/4 lg:w-1/2 max-w-7xl mx-auto leading-tight" - > - {$t('landing.header_before_icon')} - <span class="relative"> - {$t('landing.header_with_icon')} - <Icon icon="rays" style="absolute -top-16 left-5 md:left-8 lg:left-14 2xl:left-24" /> - </span> - {$t('landing.header_after_icon')} - </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" - > - {$t('landing.subheader')} - </h2> + <div class="space-y-10"> + <h1 + class="text-5xl md:text-6xl lg:text-7xl font-medium text-center w-full md:w-3/4 lg:w-1/2 max-w-7xl mx-auto leading-tight" + > + {$t("landing.header_before_icon")} + <span class="relative"> + {$t("landing.header_with_icon")} + <Icon + icon="rays" + style="absolute -top-16 left-5 md:left-8 lg:left-14 2xl:left-24" + /> + </span> + {$t("landing.header_after_icon")} + </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" + > + {$t("landing.subheader")} + </h2> - <div class="flex justify-center"> - <a href="/register"> - <button - class="bg-black text-white border rounded-full px-6 py-2 font-bold hover:opacity-80" - > - {$t('nav.startInSeconds')} - </button> - </a> - </div> - </div> + <div class="flex justify-center"> + <a href="/register"> + <button + class="bg-black text-white border rounded-full px-6 py-2 font-bold hover:opacity-80" + > + {$t("nav.startInSeconds")} + </button> + </a> + </div> + </div> - <Image image="hero-image" style="w-full md:w-2/3 max-w-5xl mx-auto mt-20" /> + <Image image="hero-image" style="w-full md:w-2/3 max-w-5xl mx-auto mt-20" /> </div> diff --git a/src/components/LandingInfoCard.svelte b/src/components/LandingInfoCard.svelte index 3d719e0b..fadcb6c0 100644 --- a/src/components/LandingInfoCard.svelte +++ b/src/components/LandingInfoCard.svelte @@ -1,26 +1,32 @@ <script> - import Image from './Image.svelte'; + import Image from "./Image.svelte"; - export let image; - export let title; - export let description = undefined; - export let order = undefined; + export let image; + export let title; + export let description = undefined; + export let order = undefined; </script> <div class="block lg:flex justify-center lg:space-x-20 items-center"> - <Image - {image} - style="w-2/3 md:w-5/12 lg:w-2/5 xl:w-1/3 2xl:w-1/4 max-w-xl mx-auto lg:mx-0 mb-10 lg:mb-0 {order === - 'reverse' && 'order-last'}" - /> - <div class="w-full lg:w-1/2 max-w-3xl space-y-10 mx-auto lg:mx-0"> - <h3 class="text-5xl font-medium text-left w-full mx-auto lg:mx-0 md:w-[500px]">{title}</h3> - <p class="text-secondary text-xl font-medium w-full md:w-[500px] mx-auto lg:mx-0"> - {#if description} - {description} - {:else} - <slot /> - {/if} - </p> - </div> + <Image + {image} + style="w-2/3 md:w-5/12 lg:w-2/5 xl:w-1/3 2xl:w-1/4 max-w-xl mx-auto lg:mx-0 mb-10 lg:mb-0 {order === + 'reverse' && 'order-last'}" + /> + <div class="w-full lg:w-1/2 max-w-3xl space-y-10 mx-auto lg:mx-0"> + <h3 + class="text-5xl font-medium text-left w-full mx-auto lg:mx-0 md:w-[500px]" + > + {title} + </h3> + <p + class="text-secondary text-xl font-medium w-full md:w-[500px] mx-auto lg:mx-0" + > + {#if description} + {description} + {:else} + <slot /> + {/if} + </p> + </div> </div> diff --git a/src/components/Left.svelte b/src/components/Left.svelte index 24ab927f..1e236969 100644 --- a/src/components/Left.svelte +++ b/src/components/Left.svelte @@ -1,16 +1,16 @@ <svg - class="w-5" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - xmlns="http://www.w3.org/2000/svg" + class="w-5" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" > - <path - d="M15 19L8 12L15 5" - stroke="currentColor" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" - /> + <path + d="M15 19L8 12L15 5" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + /> </svg> diff --git a/src/components/Lnurlp.svelte b/src/components/Lnurlp.svelte index 9918a3f4..427e4c07 100644 --- a/src/components/Lnurlp.svelte +++ b/src/components/Lnurlp.svelte @@ -1,60 +1,60 @@ <script> - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { Numpad, Spinner } from '$comp'; - import { pin } from '$lib/store'; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { Numpad, Spinner } from "$comp"; + import { pin } from "$lib/store"; - export let data, form; - let { currency } = data.user; - let { minSendable, maxSendable, callback, metadata, rates } = data; + export let data, form; + let { currency } = data.user; + let { minSendable, maxSendable, callback, metadata, rates } = data; - $: rate = rates[currency]; + $: rate = rates[currency]; - let amount = Math.round(minSendable / 1000), - loading; - let submit = () => (loading = true); + let amount = Math.round(minSendable / 1000), + loading; + let submit = () => (loading = true); </script> <div class="container px-4 mt-20 max-w-xl mx-auto"> - <div class="text-center mb-8"> - {#each JSON.parse(metadata) as m} - <div> - {#if m[0] === 'text/plain'} - <h1 class="text-3xl md:text-4xl font-semibold mb-2">{m[1]}</h1> - {/if} - {#if m[0].includes('image')} - <img src={`data:image/png;base64,${m[1]}`} alt="Recipient" /> - {/if} - </div> - {/each} - </div> + <div class="text-center mb-8"> + {#each JSON.parse(metadata) as m} + <div> + {#if m[0] === "text/plain"} + <h1 class="text-3xl md:text-4xl font-semibold mb-2">{m[1]}</h1> + {/if} + {#if m[0].includes("image")} + <img src={`data:image/png;base64,${m[1]}`} alt="Recipient" /> + {/if} + </div> + {/each} + </div> - {#if form?.error} - <div class="text-red-600 text-center mb-5"> - {form.error} - </div> - {/if} + {#if form?.error} + <div class="text-red-600 text-center mb-5"> + {form.error} + </div> + {/if} - <Numpad bind:amount bind:rate {currency} /> + <Numpad bind:amount bind:rate {currency} /> - <form action="?/pay" method="POST" use:enhance on:submit={submit}> - <input name="amount" value={amount} type="hidden" /> - <input name="minSendable" value={minSendable} type="hidden" /> - <input name="maxSendable" value={maxSendable} type="hidden" /> - <input name="callback" value={callback} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> + <form action="?/pay" method="POST" use:enhance on:submit={submit}> + <input name="amount" value={amount} type="hidden" /> + <input name="minSendable" value={minSendable} type="hidden" /> + <input name="maxSendable" value={maxSendable} type="hidden" /> + <input name="callback" value={callback} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> - <div class="flex w-full"> - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.send')} - {/if} - </button> - </div> - </form> + <div class="flex w-full"> + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + </div> + </form> </div> diff --git a/src/components/Lnurlw.svelte b/src/components/Lnurlw.svelte index f780d755..09bbca7f 100644 --- a/src/components/Lnurlw.svelte +++ b/src/components/Lnurlw.svelte @@ -1,56 +1,65 @@ <script> - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { Numpad, Spinner } from '$comp'; - import { pin } from '$lib/store'; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { Numpad, Spinner } from "$comp"; + import { pin } from "$lib/store"; - export let data, form; + export let data, form; - let { currency, username } = data.user; - let { defaultDescription, minWithdrawable, maxWithdrawable, k1, callback, rates } = data; - $: rate = rates[currency]; + let { currency, username } = data.user; + let { + defaultDescription, + minWithdrawable, + maxWithdrawable, + k1, + callback, + rates, + } = data; + $: rate = rates[currency]; - let amount = Math.round(maxWithdrawable / 1000), - loading; + let amount = Math.round(maxWithdrawable / 1000), + loading; - let submit = () => (loading = true); + let submit = () => (loading = true); </script> <div class="container px-4 mt-20 max-w-xl mx-auto"> - <div class="text-center mb-8"> - <div> - <h1 class="text-3xl md:text-4xl font-semibold mb-2">{defaultDescription}</h1> - </div> - </div> + <div class="text-center mb-8"> + <div> + <h1 class="text-3xl md:text-4xl font-semibold mb-2"> + {defaultDescription} + </h1> + </div> + </div> - {#if form?.error} - <div class="text-red-600 text-center mb-5"> - {form.error} - </div> - {/if} + {#if form?.error} + <div class="text-red-600 text-center mb-5"> + {form.error} + </div> + {/if} - <Numpad bind:amount {currency} bind:rate /> + <Numpad bind:amount {currency} bind:rate /> - <form action="?/withdraw" method="POST" use:enhance on:submit={submit}> - <input name="amount" value={amount} type="hidden" /> - <input name="username" value={username} type="hidden" /> - <input name="currency" value={currency} type="hidden" /> - <input name="minWithdrawable" value={minWithdrawable} type="hidden" /> - <input name="maxWithdrawable" value={maxWithdrawable} type="hidden" /> - <input name="callback" value={callback} type="hidden" /> - <input name="k1" value={k1} type="hidden" /> + <form action="?/withdraw" method="POST" use:enhance on:submit={submit}> + <input name="amount" value={amount} type="hidden" /> + <input name="username" value={username} type="hidden" /> + <input name="currency" value={currency} type="hidden" /> + <input name="minWithdrawable" value={minWithdrawable} type="hidden" /> + <input name="maxWithdrawable" value={maxWithdrawable} type="hidden" /> + <input name="callback" value={callback} type="hidden" /> + <input name="k1" value={k1} type="hidden" /> - <div class="flex w-full"> - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.redeem')} - {/if} - </button> - </div> - </form> + <div class="flex w-full"> + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.redeem")} + {/if} + </button> + </div> + </form> </div> diff --git a/src/components/LoadingSplash.svelte b/src/components/LoadingSplash.svelte index 2ab4c739..65ee5cd8 100644 --- a/src/components/LoadingSplash.svelte +++ b/src/components/LoadingSplash.svelte @@ -1,7 +1,7 @@ <script> - import { Icon } from '$comp'; + import { Icon } from "$comp"; </script> <div class="flex justify-center items-center"> - <Icon icon="logo" style="mt-[calc(50vh-72px)]" /> + <Icon icon="logo" style="mt-[calc(50vh-72px)]" /> </div> diff --git a/src/components/LocaleSelector.svelte b/src/components/LocaleSelector.svelte index 28e01d07..8ec5def0 100644 --- a/src/components/LocaleSelector.svelte +++ b/src/components/LocaleSelector.svelte @@ -1,18 +1,18 @@ <script> - import { t, locale, locales } from '$lib/translations'; - export let style; + import { t, locale, locales } from "$lib/translations"; + export let style; </script> <select name="language" bind:value={$locale} class={style}> - {#each $locales as locale} - <option value={locale}>{$t(`lang.${locale}`)}</option> - {/each} + {#each $locales as locale} + <option value={locale}>{$t(`lang.${locale}`)}</option> + {/each} </select> <style> - select { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - } + select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + } </style> diff --git a/src/components/LocationsMap.svelte b/src/components/LocationsMap.svelte index 0068b95f..cd26d4bd 100644 --- a/src/components/LocationsMap.svelte +++ b/src/components/LocationsMap.svelte @@ -1,276 +1,286 @@ <script> - import { tick, onMount, onDestroy } from 'svelte'; - import { browser } from '$app/environment'; - import { Icon, Popup } from '$comp'; - import { shuffleArray } from '$lib/utils'; + import { tick, onMount, onDestroy } from "svelte"; + import { browser } from "$app/environment"; + import { Icon, Popup } from "$comp"; + import { shuffleArray } from "$lib/utils"; - import 'maplibre-gl/dist/maplibre-gl.css'; + import "maplibre-gl/dist/maplibre-gl.css"; - export let locations; - let map; - let mapContainer, mapWrapper; - let markers = []; + export let locations; + let map; + let mapContainer, mapWrapper; + let markers = []; - let currentIndex = -1; - let timeout; + let currentIndex = -1; + let timeout; - let full = () => { - if (!document.fullscreenElement) { - if (mapWrapper.requestFullscreen) { - mapWrapper.requestFullscreen(); - } else if (mapWrapper.mozRequestFullScreen) { - /* Firefox */ - mapWrapper.mozRequestFullScreen(); - } else if (mapWrapper.webkitRequestFullscreen) { - /* Chrome, Safari & Opera */ - mapWrapper.webkitRequestFullscreen(); - } else if (mapWrapper.msRequestFullscreen) { - /* IE/Edge */ - mapWrapper.msRequestFullscreen(); - } + let full = () => { + if (!document.fullscreenElement) { + if (mapWrapper.requestFullscreen) { + mapWrapper.requestFullscreen(); + } else if (mapWrapper.mozRequestFullScreen) { + /* Firefox */ + mapWrapper.mozRequestFullScreen(); + } else if (mapWrapper.webkitRequestFullscreen) { + /* Chrome, Safari & Opera */ + mapWrapper.webkitRequestFullscreen(); + } else if (mapWrapper.msRequestFullscreen) { + /* IE/Edge */ + mapWrapper.msRequestFullscreen(); + } - map.resize(); - } else { - if (document.exitFullscreen) { - document.exitFullscreen(); - } else if (document.mozCancelFullScreen) { - /* Firefox */ - document.mozCancelFullScreen(); - } else if (document.webkitExitFullscreen) { - /* Chrome, Safari and Opera */ - document.webkitExitFullscreen(); - } else if (document.msExitFullscreen) { - /* IE/Edge */ - document.msExitFullscreen(); - } - } - }; + map.resize(); + } else { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.mozCancelFullScreen) { + /* Firefox */ + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { + /* Chrome, Safari and Opera */ + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { + /* IE/Edge */ + document.msExitFullscreen(); + } + } + }; - function select(m) { - stopFlying(); - currentIndex = inview.indexOf(m); - flyToMarker(m); - } + function select(m) { + stopFlying(); + currentIndex = inview.indexOf(m); + flyToMarker(m); + } - async function flyToMarker(marker) { - map.flyTo({ - zoom: 12, - center: marker.getLngLat(), - essential: true, - easing: function (t) { - return 1 - Math.pow(1 - t, 3); - }, - duration: 3000 - }); + async function flyToMarker(marker) { + map.flyTo({ + zoom: 12, + center: marker.getLngLat(), + essential: true, + easing: function (t) { + return 1 - Math.pow(1 - t, 3); + }, + duration: 3000, + }); - await new Promise((r) => setTimeout(r, 3000)); - marker.getElement().click(); - await new Promise((r) => setTimeout(r, 1500)); + await new Promise((r) => setTimeout(r, 3000)); + marker.getElement().click(); + await new Promise((r) => setTimeout(r, 1500)); - if (!timeout) return; + if (!timeout) return; - map.flyTo({ - zoom: 10.5, - center: marker.getLngLat(), - essential: true, - easing: function (t) { - return 1 - Math.pow(1 - t, 3); - }, - duration: 1500 - }); - } + map.flyTo({ + zoom: 10.5, + center: marker.getLngLat(), + essential: true, + easing: function (t) { + return 1 - Math.pow(1 - t, 3); + }, + duration: 1500, + }); + } - let toggle = () => { - if (timeout) stopFlying(); - else startFlying(); - }; + let toggle = () => { + if (timeout) stopFlying(); + else startFlying(); + }; - let currentPopup; - function startFlying() { - currentIndex++; + let currentPopup; + function startFlying() { + currentIndex++; - for (let p in popups) popups[p].remove(); - if (currentIndex >= inview.length) currentIndex = 0; - flyToMarker(inview[currentIndex]); + for (let p in popups) popups[p].remove(); + if (currentIndex >= inview.length) currentIndex = 0; + flyToMarker(inview[currentIndex]); - timeout = setTimeout(startFlying, 6000); - } + timeout = setTimeout(startFlying, 6000); + } - function stopFlying() { - if (!timeout) return; - timeout = clearTimeout(timeout); + function stopFlying() { + if (!timeout) return; + timeout = clearTimeout(timeout); - if (currentPopup) { - currentPopup.remove(); - } + if (currentPopup) { + currentPopup.remove(); + } - map.jumpTo({ center: map.getCenter(), zoom: map.getZoom() }); - map.off('moveend'); // Remove moveend listener if set - } + map.jumpTo({ center: map.getCenter(), zoom: map.getZoom() }); + map.off("moveend"); // Remove moveend listener if set + } - let inview = []; + let inview = []; - function updateLabelVisibility() { - inview = []; - if (!map) return; - let zoom = map.getZoom(); - let bounds = map.getBounds(); + function updateLabelVisibility() { + inview = []; + if (!map) return; + let zoom = map.getZoom(); + let bounds = map.getBounds(); - for (let marker of markers) { - let isInView = bounds.contains(marker.getLngLat()); - let { tags } = marker; + for (let marker of markers) { + let isInView = bounds.contains(marker.getLngLat()); + let { tags } = marker; - if (isInView) { - inview.push(marker); - } - } + if (isInView) { + inview.push(marker); + } + } - inview = inview.sort((a, b) => a.tags.name.localeCompare(b.tags.name)); - shuffleArray(inview); - } + inview = inview.sort((a, b) => a.tags.name.localeCompare(b.tags.name)); + shuffleArray(inview); + } - let popups = {}; - let counter = 0; + let popups = {}; + let counter = 0; - onMount(() => { - if (browser) { - import('maplibre-gl').then(({ default: maplibre }) => { - map = new maplibre.Map({ - container: mapContainer, - style: 'https://tiles.stadiamaps.com/styles/stamen_toner.json', - center: [-123.05, 49.26], - zoom: 10.5 - }); + onMount(() => { + if (browser) { + import("maplibre-gl").then(({ default: maplibre }) => { + map = new maplibre.Map({ + container: mapContainer, + style: "https://tiles.stadiamaps.com/styles/stamen_toner.json", + center: [-123.05, 49.26], + zoom: 10.5, + }); - map.on('load', () => { - locations.forEach((location) => { - if (location['deleted_at']) return; + map.on("load", () => { + locations.forEach((location) => { + if (location["deleted_at"]) return; - let { lat, lon, tags } = location['osm_json']; - if (!(lat && lon)) return; + let { lat, lon, tags } = location["osm_json"]; + if (!(lat && lon)) return; - let element = document.createElement('div'); - element.className = 'marker'; + let element = document.createElement("div"); + element.className = "marker"; - let popupContainer = document.createElement('div'); + let popupContainer = document.createElement("div"); - let popup = new Popup({ - target: popupContainer, - props: { tags } - }); + let popup = new Popup({ + target: popupContainer, + props: { tags }, + }); - let marker = new maplibre.Marker({ color: '#F7931A' }) - .setLngLat([lon, lat]) - .setPopup(new maplibre.Popup().setDOMContent(popupContainer)) - .addTo(map); + let marker = new maplibre.Marker({ color: "#F7931A" }) + .setLngLat([lon, lat]) + .setPopup(new maplibre.Popup().setDOMContent(popupContainer)) + .addTo(map); - marker.id = counter++; + marker.id = counter++; - marker.getElement().addEventListener('click', () => { - let p = marker.getPopup(); - popups[marker.id] = p; - p.setDOMContent(popupContainer); - p.on('close', () => { - delete popups[marker.id]; - }); - }); + marker.getElement().addEventListener("click", () => { + let p = marker.getPopup(); + popups[marker.id] = p; + p.setDOMContent(popupContainer); + p.on("close", () => { + delete popups[marker.id]; + }); + }); - marker.tags = tags; + marker.tags = tags; - markers.push(marker); - }); + markers.push(marker); + }); - updateLabelVisibility(); + updateLabelVisibility(); - let observer = new IntersectionObserver( - (entries, observer) => { - entries.forEach((entry) => { - if (entry.isIntersecting) startFlying(); - else stopFlying(); - }); - }, - { threshold: 0.5 } - ); + let observer = new IntersectionObserver( + (entries, observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) startFlying(); + else stopFlying(); + }); + }, + { threshold: 0.5 } + ); - observer.observe(mapContainer); - }); + observer.observe(mapContainer); + }); - map.on('mousedown', stopFlying); - map.on('touchstart', stopFlying); - map.on('wheel', stopFlying); - map.on('dragstart', stopFlying); + map.on("mousedown", stopFlying); + map.on("touchstart", stopFlying); + map.on("wheel", stopFlying); + map.on("dragstart", stopFlying); - let resize = () => map && map.resize(); + let resize = () => map && map.resize(); - document.addEventListener('fullscreenchange', resize, false); - document.addEventListener('webkitfullscreenchange', resize, false); - document.addEventListener('mozfullscreenchange', resize, false); - document.addEventListener('MSFullscreenChange', resize, false); - }); - } - }); + document.addEventListener("fullscreenchange", resize, false); + document.addEventListener("webkitfullscreenchange", resize, false); + document.addEventListener("mozfullscreenchange", resize, false); + document.addEventListener("MSFullscreenChange", resize, false); + }); + } + }); - onDestroy(() => map && map.remove()); + onDestroy(() => map && map.remove()); </script> <div class="container mx-auto p-2 space-y-5"> - <div class="flex w-full h-[60vh]" id="map" bind:this={mapWrapper}> - <div id="map-container" class="mx-auto h-full w-full z-0" bind:this={mapContainer} /> - <div class="relative"> - <div class="flex 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}> - {#if timeout} - <Icon icon="pause" style="w-4 m-auto" /> - {:else} - <Icon icon="play" style="w-4 m-auto" /> - {/if} - </button> - <button class="rounded-full border-2 border-black bg-white w-10 h-10" on:click={full}> - <Icon icon="full" style="w-4 m-auto" /> - </button> - </div> - </div> - </div> + <div class="flex w-full h-[60vh]" id="map" bind:this={mapWrapper}> + <div + id="map-container" + class="mx-auto h-full w-full z-0" + bind:this={mapContainer} + /> + <div class="relative"> + <div class="flex 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} + > + {#if timeout} + <Icon icon="pause" style="w-4 m-auto" /> + {:else} + <Icon icon="play" style="w-4 m-auto" /> + {/if} + </button> + <button + class="rounded-full border-2 border-black bg-white w-10 h-10" + on:click={full} + > + <Icon icon="full" style="w-4 m-auto" /> + </button> + </div> + </div> + </div> - <!-- <div class="hidden lg:grid grid-cols-3 justify-items-center"> --> - <!-- {#each inview as m} --> - <!-- <button --> - <!-- on:click={() => select(m)} --> - <!-- class="text-left" --> - <!-- class:font-bold={m.id === inview[currentIndex]?.id} --> - <!-- id={`marker-${m.id}`}>{m.tags.name}</button --> - <!-- > --> - <!-- {/each} --> - <!-- </div> --> + <!-- <div class="hidden lg:grid grid-cols-3 justify-items-center"> --> + <!-- {#each inview as m} --> + <!-- <button --> + <!-- on:click={() => select(m)} --> + <!-- class="text-left" --> + <!-- class:font-bold={m.id === inview[currentIndex]?.id} --> + <!-- id={`marker-${m.id}`}>{m.tags.name}</button --> + <!-- > --> + <!-- {/each} --> + <!-- </div> --> </div> <style> - #map:fullscreen, - #map-container:fullscreen { - width: 100% !important; - height: 100% !important; - } + #map:fullscreen, + #map-container:fullscreen { + width: 100% !important; + height: 100% !important; + } - :global(.maplibregl-popup-close-button) { - font-size: 24px; - padding: 8px; - } + :global(.maplibregl-popup-close-button) { + font-size: 24px; + padding: 8px; + } - :global(.maplibregl-popup :focus) { - outline: none; - } + :global(.maplibregl-popup :focus) { + outline: none; + } - :global(.maplibregl-ctrl-attrib) { - display: none; - } + :global(.maplibregl-ctrl-attrib) { + display: none; + } - :global(.maplibregl-popup-content) { - padding: 1em 2em; - border-radius: 4px; - font-size: 14px; - color: black; - background-color: white; - overflow: hidden; - width: 280px; - } + :global(.maplibregl-popup-content) { + padding: 1em 2em; + border-radius: 4px; + font-size: 14px; + color: black; + background-color: white; + overflow: hidden; + width: 280px; + } </style> diff --git a/src/components/Lock.svelte b/src/components/Lock.svelte index 11475acf..fce994a2 100644 --- a/src/components/Lock.svelte +++ b/src/components/Lock.svelte @@ -1,14 +1,14 @@ <svg - xmlns="http://www.w3.org/2000/svg" - class="absolute bottom-0 left-0 -mb-0.5 transform translate-x-1/2 -translate-y-1/2 text-black peer-placeholder-shown:text-gray-300 h-6 w-6" - fill="none" - viewBox="0 0 24 24" - stroke="currentColor" + xmlns="http://www.w3.org/2000/svg" + class="absolute bottom-0 left-0 -mb-0.5 transform translate-x-1/2 -translate-y-1/2 text-black peer-placeholder-shown:text-gray-300 h-6 w-6" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" - /> + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" + /> </svg> diff --git a/src/components/Numpad.svelte b/src/components/Numpad.svelte index 5ea1418c..6b779085 100644 --- a/src/components/Numpad.svelte +++ b/src/components/Numpad.svelte @@ -1,219 +1,236 @@ <script> - import { Left, Icon } from '$comp'; - import { f, s, post, warning, sat, sats } from '$lib/utils'; - import { t } from '$lib/translations'; + import { Left, Icon } from "$comp"; + import { f, s, post, warning, sat, sats } from "$lib/utils"; + import { t } from "$lib/translations"; - export let amount, - currency, - fiat = !amount, - rate, - submit = undefined; + export let amount, + currency, + fiat = !amount, + rate, + submit = undefined; - export let amountFiat = 0; + export let amountFiat = 0; - let arrow = '<'; + let arrow = "<"; - $: update(amount, amountFiat); - let update = (a, f) => { - if (fiat) { - amount = Math.round(f / (rate / sats)); - } else { - amountFiat = ((a * rate) / sats).toFixed(2); - } - }; + $: update(amount, amountFiat); + let update = (a, f) => { + if (fiat) { + amount = Math.round(f / (rate / sats)); + } else { + amountFiat = ((a * rate) / sats).toFixed(2); + } + }; - let loading = false; + let loading = false; - let symbol = - { - USD: '$', - CAD: '$', - AUD: '$', - NZD: '$', - MXN: '$', - BRL: 'R$', - HKD: '$', - TWD: '$', - JPY: '¥', - CNY: '¥', - GBP: '£', - EUR: '€', - KRW: '₩' - }[currency] || ''; + let symbol = + { + USD: "$", + CAD: "$", + AUD: "$", + NZD: "$", + MXN: "$", + BRL: "R$", + HKD: "$", + TWD: "$", + JPY: "¥", + CNY: "¥", + GBP: "£", + EUR: "€", + KRW: "₩", + }[currency] || ""; - const numPad = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0', '<']; + const numPad = ["1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "0", "<"]; - const handleInput = (value) => { - if (fiat) { - if (amountFiat === 0 && value !== '.' && value !== '<' && value !== '0') { - amountFiat = value; - } else if ((amountFiat === 0 || amountFiat === '0') && value === '0') { - return; - } else if (amountFiat === 0 && value === '.') { - amountFiat = '0.'; - } else if (amountFiat !== 0 && amountFiat.includes('.') && value === '.') { - return; - } else if (value === '<') { - if (amountFiat !== 0) { - amountFiat = amountFiat.slice(0, amountFiat.length - 1); - if (amountFiat.length === 0) { - amountFiat = 0; - amount = 0; - } - } - } else if (value !== '.' && value !== '<' && parseInt(amountFiat + value) > rate) { - warning($t('user.receive.lessThan1BTCWarning')); - } else if (amountFiat !== 0 && amountFiat.match(/\.../)) { - return; - } else { - amountFiat = amountFiat + value; - } - } else { - if (value === '.') { - return; - } else if (!amount && value !== '<' && value !== '0') { - amount = parseInt(value); - } else if (value === '<') { - if (amount !== 0) { - amount = Math.floor(amount / 10); - if (amount.length === 0) { - amount = 0; - amountFiat = 0; - } - } - } else if (value !== '<' && parseInt(amount + value) > sats) { - warning($t('user.receive.lessThan1BTCWarning')); - } else { - console.log('CLICK', amount.toString(), value); - amount = parseInt(amount.toString() + value); - console.log('AMOUNT', amount); - } - } - }; + const handleInput = (value) => { + if (fiat) { + if (amountFiat === 0 && value !== "." && value !== "<" && value !== "0") { + amountFiat = value; + } else if ((amountFiat === 0 || amountFiat === "0") && value === "0") { + return; + } else if (amountFiat === 0 && value === ".") { + amountFiat = "0."; + } else if ( + amountFiat !== 0 && + amountFiat.includes(".") && + value === "." + ) { + return; + } else if (value === "<") { + if (amountFiat !== 0) { + amountFiat = amountFiat.slice(0, amountFiat.length - 1); + if (amountFiat.length === 0) { + amountFiat = 0; + amount = 0; + } + } + } else if ( + value !== "." && + value !== "<" && + parseInt(amountFiat + value) > rate + ) { + warning($t("user.receive.lessThan1BTCWarning")); + } else if (amountFiat !== 0 && amountFiat.match(/\.../)) { + return; + } else { + amountFiat = amountFiat + value; + } + } else { + if (value === ".") { + return; + } else if (!amount && value !== "<" && value !== "0") { + amount = parseInt(value); + } else if (value === "<") { + if (amount !== 0) { + amount = Math.floor(amount / 10); + if (amount.length === 0) { + amount = 0; + amountFiat = 0; + } + } + } else if (value !== "<" && parseInt(amount + value) > sats) { + warning($t("user.receive.lessThan1BTCWarning")); + } else { + console.log("CLICK", amount.toString(), value); + amount = parseInt(amount.toString() + value); + console.log("AMOUNT", amount); + } + } + }; - $: html = fiat ? amountFiat : s(amount); + $: html = fiat ? amountFiat : s(amount); - let prev = ''; + let prev = ""; - let input = (e) => { - if (prev === '0' && html !== '0.') { - prev = ''; - html = html.replace('0', ''); - } + let input = (e) => { + if (prev === "0" && html !== "0.") { + prev = ""; + html = html.replace("0", ""); + } - let sel = getSelection(); - let i = sel.focusOffset; + let sel = getSelection(); + let i = sel.focusOffset; - let clean = html - .substr(0, 15) - .replace(/[^0-9.,]+/g, '') - .replace('.', 'F') - .replace(/\./g, '') - .replace('F', '.'); + let clean = html + .substr(0, 15) + .replace(/[^0-9.,]+/g, "") + .replace(".", "F") + .replace(/\./g, "") + .replace("F", "."); - if (fiat) clean = clean.replace(',', ''); - if (clean !== html) { - html = clean; - i = html.length; - } + if (fiat) clean = clean.replace(",", ""); + if (clean !== html) { + html = clean; + i = html.length; + } - if (amount > sats && html.length > prev.length) { - html = prev; - warning($t('user.receive.lessThan1BTCWarning')); - i--; - } + if (amount > sats && html.length > prev.length) { + html = prev; + warning($t("user.receive.lessThan1BTCWarning")); + i--; + } - if (fiat) { - amountFiat = html; - } else { - amount = parseInt(html.replace(/,/g, '')); - } + if (fiat) { + amountFiat = html; + } else { + amount = parseInt(html.replace(/,/g, "")); + } - if (!amount) amount = null; + if (!amount) amount = null; - setTimeout(() => { - if (!fiat) { - let p = prev.split(',')[0].length; - if (html.length > prev.length && p === 3) i++; - if (html.length < prev.length && p === 1 && i > 1) i--; - } + setTimeout(() => { + if (!fiat) { + let p = prev.split(",")[0].length; + if (html.length > prev.length && p === 3) i++; + if (html.length < prev.length && p === 1 && i > 1) i--; + } - let node = e.target.childNodes[0]; - let range = document.createRange(); + let node = e.target.childNodes[0]; + let range = document.createRange(); - if (node) { - range.setStart(node, i); - range.setEnd(node, i); + if (node) { + range.setStart(node, i); + range.setEnd(node, i); - let sel = getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - } + let sel = getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } - prev = html.toString(); - }, 0); - }; + prev = html.toString(); + }, 0); + }; - let focus = (e) => { - let range = document.createRange(); - range.selectNodeContents(e.target); - let sel = getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - }; + let focus = (e) => { + let range = document.createRange(); + range.selectNodeContents(e.target); + let sel = getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + }; - let keydown = (e) => e.key === 'Enter' && (e.preventDefault() || submit.click()); + let keydown = (e) => + e.key === "Enter" && (e.preventDefault() || submit.click()); </script> <div class="flex justify-center items-center mb-3 px-3"> - <div class="space-y-5"> - <div class="text-center"> - <div class="text-5xl md:text-6xl font-semibold tracking-widest flex justify-center"> - <div class="my-auto" class:text-5xl={!fiat}>{fiat ? symbol : '⚡️'}</div> - <div - contenteditable - bind:innerHTML={html} - on:focus={focus} - on:input={input} - on:keydown={keydown} - class="outline-none my-auto" - /> - </div> - <div class="mt-2"> - <span class="text-secondary mr-1">{fiat ? sat(amount) : f(amountFiat, currency)}</span> - <button - type="button" - on:click={() => { - if (fiat) { - amount = parseInt((amountFiat / (rate / sats)).toFixed(0)); - } else { - amountFiat = - (amount * (rate / sats)).toFixed(2) > 0.0 ? (amount * (rate / sats)).toFixed(2) : 0; - } - fiat = !fiat; - }}><Icon icon="swap" style="inline hover:opacity-80" /></button - > - </div> - </div> + <div class="space-y-5"> + <div class="text-center"> + <div + class="text-5xl md:text-6xl font-semibold tracking-widest flex justify-center" + > + <div class="my-auto" class:text-5xl={!fiat}> + {fiat ? symbol : "⚡️"} + </div> + <div + contenteditable + bind:innerHTML={html} + on:focus={focus} + on:input={input} + on:keydown={keydown} + class="outline-none my-auto" + /> + </div> + <div class="mt-2"> + <span class="text-secondary mr-1" + >{fiat ? sat(amount) : f(amountFiat, currency)}</span + > + <button + type="button" + on:click={() => { + if (fiat) { + amount = parseInt((amountFiat / (rate / sats)).toFixed(0)); + } else { + amountFiat = + (amount * (rate / sats)).toFixed(2) > 0.0 + ? (amount * (rate / sats)).toFixed(2) + : 0; + } + fiat = !fiat; + }}><Icon icon="swap" style="inline hover:opacity-80" /></button + > + </div> + </div> - <div class="grid grid-cols-3 gap-2 w-[300px] mx-auto"> - {#each numPad as value} - {#if value === arrow} - <button - type="button" - class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white flex justify-center items-center hover:opacity-80" - on:click={() => handleInput(value)} - > - <Left /> - </button> - {:else} - <button - type="button" - class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white hover:opacity-80" - on:click={() => handleInput(value)}>{value}</button - > - {/if} - {/each} - </div> - </div> + <div class="grid grid-cols-3 gap-2 w-[300px] mx-auto"> + {#each numPad as value} + {#if value === arrow} + <button + type="button" + class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white flex justify-center items-center hover:opacity-80" + on:click={() => handleInput(value)} + > + <Left /> + </button> + {:else} + <button + type="button" + class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white hover:opacity-80" + on:click={() => handleInput(value)}>{value}</button + > + {/if} + {/each} + </div> + </div> </div> diff --git a/src/components/Password.svelte b/src/components/Password.svelte index a84c0b16..007ce718 100644 --- a/src/components/Password.svelte +++ b/src/components/Password.svelte @@ -1,77 +1,83 @@ <script> - import { Icon } from '$comp'; - import { focus, fail, post } from '$lib/utils'; - import { password as pw, passwordPrompt } from '$lib/store'; + import { Icon } from "$comp"; + import { focus, fail, post } from "$lib/utils"; + import { password as pw, passwordPrompt } from "$lib/store"; - export let user; + export let user; - let password; - let { username } = user; - let revealPassword = false; + let password; + let { username } = user; + let revealPassword = false; - let cancel = () => ($passwordPrompt = false); + let cancel = () => ($passwordPrompt = false); - let submit = async () => { - try { - await post('/password', { password }); - $passwordPrompt = false; - $pw = password; - } catch (e) { - fail('Invalid password, try again'); - } - }; + let submit = async () => { + try { + await post("/password", { password }); + $passwordPrompt = false; + $pw = password; + } catch (e) { + fail("Invalid password, try again"); + } + }; </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">Please enter your password</h1> - <form on:submit|preventDefault={submit}> - <div class="relative mb-5"> - {#if revealPassword} - <input - name="password" - type="text" - required - class="bg-primary" - bind:value={password} - autocapitalize="none" - use:focus - /> - {:else} - <input - name="password" - type="password" - required - class="bg-primary" - bind:value={password} - autocapitalize="none" - use:focus - /> - {/if} - <button - type="button" - on:click={() => (revealPassword = !revealPassword)} - class="absolute right-5 top-6" - > - <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> - </button> - </div> - <div class="w-full flex"> - <button - type="button" - class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto" - on:click={cancel} - on:keydown={cancel} - > - <div class="my-auto">Cancel</div> - </button> - <button - type="submit" - class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto" - > - <div class="my-auto">Submit</div> - </button> - </div> - </form> - </div> +<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"> + Please enter your password + </h1> + <form on:submit|preventDefault={submit}> + <div class="relative mb-5"> + {#if revealPassword} + <input + name="password" + type="text" + required + class="bg-primary" + bind:value={password} + autocapitalize="none" + use:focus + /> + {:else} + <input + name="password" + type="password" + required + class="bg-primary" + bind:value={password} + autocapitalize="none" + use:focus + /> + {/if} + <button + type="button" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-5 top-6" + > + <Icon icon={revealPassword ? "eye" : "eye-off"} /> + </button> + </div> + <div class="w-full flex"> + <button + type="button" + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto" + on:click={cancel} + on:keydown={cancel} + > + <div class="my-auto">Cancel</div> + </button> + <button + type="submit" + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto" + > + <div class="my-auto">Submit</div> + </button> + </div> + </form> + </div> </div> diff --git a/src/components/Pin.svelte b/src/components/Pin.svelte index 0cff904f..bd51cb0a 100644 --- a/src/components/Pin.svelte +++ b/src/components/Pin.svelte @@ -1,59 +1,63 @@ <script> - import { t } from '$lib/translations'; - import { post, success, fail, focus } from '$lib/utils'; - import { Pinpad } from '$comp'; - import { pin } from '$lib/store'; - import { onMount } from 'svelte'; + import { t } from "$lib/translations"; + import { post, success, fail, focus } from "$lib/utils"; + import { Pinpad } from "$comp"; + import { pin } from "$lib/store"; + import { onMount } from "svelte"; - export let value = ''; - export let title = $t('user.settings.verifyPIN'); - export let cancel = () => (p = ''); - export let persist = true; - export let notify = true; + export let value = ""; + export let title = $t("user.settings.verifyPIN"); + export let cancel = () => (p = ""); + export let persist = true; + export let notify = true; - let p = ''; + let p = ""; - $: update(value); - let update = (value) => (p = value); + $: update(value); + let update = (value) => (p = value); - $: p.toString().length > 5 && (value = p.toString()); + $: p.toString().length > 5 && (value = p.toString()); - let loaded; - onMount(() => setTimeout(() => (loaded = true), 500)); + let loaded; + onMount(() => setTimeout(() => (loaded = true), 500)); - $: loaded && checkPin(p); - let checkPin = async (p) => { - if (p?.length !== 6) return; - let result; - try { - result = await post('/pin', { pin: p }); - } catch (e) { - console.log('Pin check failed', e); - } + $: loaded && checkPin(p); + let checkPin = async (p) => { + if (p?.length !== 6) return; + let result; + try { + result = await post("/pin", { pin: p }); + } catch (e) { + console.log("Pin check failed", e); + } - if (result) { - if (notify) success('Pin confirmed'); - if (persist) $pin = p; - } else { - fail('Invalid pin'); - value = ''; - } - }; + if (result) { + if (notify) success("Pin confirmed"); + if (persist) $pin = p; + } else { + fail("Invalid pin"); + value = ""; + } + }; </script> {#if loaded} - <div class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> - <div class="relative top-40 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white space-y-5"> - <h1 class="text-center text-2xl font-semibold">{title}</h1> - <Pinpad bind:v={p} {cancel} /> - <div class="w-full flex"> - <button - class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto" - on:click|preventDefault={cancel} - > - <div class="my-auto">Cancel</div> - </button> - </div> - </div> - </div> + <div + class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50" + > + <div + class="relative top-40 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white space-y-5" + > + <h1 class="text-center text-2xl font-semibold">{title}</h1> + <Pinpad bind:v={p} {cancel} /> + <div class="w-full flex"> + <button + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto" + on:click|preventDefault={cancel} + > + <div class="my-auto">Cancel</div> + </button> + </div> + </div> + </div> {/if} diff --git a/src/components/Pinpad.svelte b/src/components/Pinpad.svelte index f55dc40d..5db23154 100644 --- a/src/components/Pinpad.svelte +++ b/src/components/Pinpad.svelte @@ -1,80 +1,88 @@ <script> - import { Left, Icon } from '$comp'; - import { focus, post, warning } from '$lib/utils'; - import { t } from '$lib/translations'; + import { Left, Icon } from "$comp"; + import { focus, post, warning } from "$lib/utils"; + import { t } from "$lib/translations"; - export let v; - export let cancel; + export let v; + export let cancel; - let arrow = '<'; - let loading = false; + let arrow = "<"; + let loading = false; - const numPad = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', '0', '<']; + const numPad = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "C", "0", "<"]; - const handleInput = (n) => { - v = (v || '').toString(); - if (n === 'C') return (v = ''); - if (n === '<') return (v = v.length > 1 ? Math.floor(v / 10) : ''); - if (v.length > 5) n = ''; - if (parseInt(n) >= 0) v += n; - }; + const handleInput = (n) => { + v = (v || "").toString(); + if (n === "C") return (v = ""); + if (n === "<") return (v = v.length > 1 ? Math.floor(v / 10) : ""); + if (v.length > 5) n = ""; + if (parseInt(n) >= 0) v += n; + }; - let validate = (e) => { - let k = e.key || e.code; - if (k === 'Escape') cancel(); - if (e.key.length <= 1 && !(e.metaKey || e.ctrlKey || e.altKey) && !(k >= '0' && k <= '9')) { - if (e.preventDefault) e.preventDefault(); - else e.returnValue = false; - } - }; + let validate = (e) => { + let k = e.key || e.code; + if (k === "Escape") cancel(); + if ( + e.key.length <= 1 && + !(e.metaKey || e.ctrlKey || e.altKey) && + !(k >= "0" && k <= "9") + ) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + } + }; - let hide = true; + let hide = true; </script> <div class="flex justify-center items-center"> - <div class="space-y-5"> - <div class="text-5xl md:text-6xl font-semibold relative w-[300px]"> - {#if hide} - <input - on:keydown={validate} - use:focus - bind:value={v} - type="password" - class="outline-none border-0 text-center py-0" - pattern="[0-9]+" - /> - {:else} - <input - on:keydown={validate} - bind:value={v} - class="outline-none border-0 text-center py-0" - type="text" - pattern="[0-9]+" - /> - {/if} - <button type="button" on:click={() => (hide = !hide)} class="absolute -right-5 top-5"> - <Icon icon={hide ? 'eye-off' : 'eye'} style="w-8" /> - </button> - </div> + <div class="space-y-5"> + <div class="text-5xl md:text-6xl font-semibold relative w-[300px]"> + {#if hide} + <input + on:keydown={validate} + use:focus + bind:value={v} + type="password" + class="outline-none border-0 text-center py-0" + pattern="[0-9]+" + /> + {:else} + <input + on:keydown={validate} + bind:value={v} + class="outline-none border-0 text-center py-0" + type="text" + pattern="[0-9]+" + /> + {/if} + <button + type="button" + on:click={() => (hide = !hide)} + class="absolute -right-5 top-5" + > + <Icon icon={hide ? "eye-off" : "eye"} style="w-8" /> + </button> + </div> - <div class="grid grid-cols-3 gap-2 w-[300px] mx-auto grayscale"> - {#each numPad as value} - {#if value === arrow} - <button - type="button" - class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white flex justify-center items-center hover:opacity-80" - on:click={() => handleInput(value)} - > - <Left /> - </button> - {:else} - <button - type="button" - class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white hover:opacity-80" - on:click={() => handleInput(value)}>{value}</button - > - {/if} - {/each} - </div> - </div> + <div class="grid grid-cols-3 gap-2 w-[300px] mx-auto grayscale"> + {#each numPad as value} + {#if value === arrow} + <button + type="button" + class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white flex justify-center items-center hover:opacity-80" + on:click={() => handleInput(value)} + > + <Left /> + </button> + {:else} + <button + type="button" + class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white hover:opacity-80" + on:click={() => handleInput(value)}>{value}</button + > + {/if} + {/each} + </div> + </div> </div> diff --git a/src/components/Popup.svelte b/src/components/Popup.svelte index 8fe1d160..5d2b9b6a 100644 --- a/src/components/Popup.svelte +++ b/src/components/Popup.svelte @@ -1,38 +1,42 @@ <script> - import { page } from '$app/stores'; - import { Avatar } from '$comp'; - export let tags; + import { page } from "$app/stores"; + import { Avatar } from "$comp"; + export let tags; - let addr = (tags) => { - let { 'addr:housenumber': house, 'addr:street': street, 'addr:city': city } = tags; - if (house) return `${house} ${street}`; - return ''; - }; + let addr = (tags) => { + let { + "addr:housenumber": house, + "addr:street": street, + "addr:city": city, + } = tags; + if (house) return `${house} ${street}`; + return ""; + }; </script> {#if tags} - {#if tags.user} - <a href={`/${tags.user.username}`}> - <div class="space-y-5"> - <div> - <span class="block font-bold">{tags.name}</span> - <span class="block">{addr(tags)}</span> - </div> + {#if tags.user} + <a href={`/${tags.user.username}`}> + <div class="space-y-5"> + <div> + <span class="block font-bold">{tags.name}</span> + <span class="block">{addr(tags)}</span> + </div> - <div> - <div class="flex w-full"> - <div class="mx-auto"> - <Avatar user={tags.user} size={16} /> - </div> - </div> - <div class="break-all text-center"> - {$page.url.host}/{tags.user.username.toLowerCase()} - </div> - </div> - </div> - </a> - {:else} - <span class="block font-bold">{tags.name}</span> - <span class="block">{addr(tags)}</span> - {/if} + <div> + <div class="flex w-full"> + <div class="mx-auto"> + <Avatar user={tags.user} size={16} /> + </div> + </div> + <div class="break-all text-center"> + {$page.url.host}/{tags.user.username.toLowerCase()} + </div> + </div> + </div> + </a> + {:else} + <span class="block font-bold">{tags.name}</span> + <span class="block">{addr(tags)}</span> + {/if} {/if} diff --git a/src/components/Qr.svelte b/src/components/Qr.svelte index c67d134e..8a716b14 100644 --- a/src/components/Qr.svelte +++ b/src/components/Qr.svelte @@ -1,14 +1,14 @@ <script> - import screenfull from 'screenfull'; - export let src; - let qr; + import screenfull from "screenfull"; + export let src; + let qr; </script> <div - class="w-80 mx-auto my-5 cursor-pointer" - bind:this={qr} - on:click={() => screenfull.toggle(qr)} - on:keydown={() => screenfull.toggle(qr)} + class="w-80 mx-auto my-5 cursor-pointer" + bind:this={qr} + on:click={() => screenfull.toggle(qr)} + on:keydown={() => screenfull.toggle(qr)} > - <img {src} class="mx-auto" alt="QR Code" /> + <img {src} class="mx-auto" alt="QR Code" /> </div> diff --git a/src/components/Quote.svelte b/src/components/Quote.svelte index 1e099061..8901dae1 100644 --- a/src/components/Quote.svelte +++ b/src/components/Quote.svelte @@ -1,11 +1,11 @@ <svg - aria-hidden="true" - class="w-10 h-10 text-gray-400 dark:text-gray-600" - viewBox="0 0 24 27" - fill="none" - xmlns="http://www.w3.org/2000/svg" - ><path - d="M14.017 18L14.017 10.609C14.017 4.905 17.748 1.039 23 0L23.995 2.151C21.563 3.068 20 5.789 20 8h34V18H14.017ZM0 18V10.609C0 4.905 3.748 1.038 9 0L9.996 2.151C7.563 3.068 6 5.789 6 8H9.983L9.983 18L0 18Z" - fill="currentColor" - /></svg + aria-hidden="true" + class="w-10 h-10 text-gray-400 dark:text-gray-600" + viewBox="0 0 24 27" + fill="none" + xmlns="http://www.w3.org/2000/svg" + ><path + d="M14.017 18L14.017 10.609C14.017 4.905 17.748 1.039 23 0L23.995 2.151C21.563 3.068 20 5.789 20 8h34V18H14.017ZM0 18V10.609C0 4.905 3.748 1.038 9 0L9.996 2.151C7.563 3.068 6 5.789 6 8H9.983L9.983 18L0 18Z" + fill="currentColor" + /></svg > diff --git a/src/components/RegisterOrLogin.svelte b/src/components/RegisterOrLogin.svelte index 57e0eeed..5617fc7b 100644 --- a/src/components/RegisterOrLogin.svelte +++ b/src/components/RegisterOrLogin.svelte @@ -1,179 +1,191 @@ <script> - import { applyAction, deserialize } from '$app/forms'; - import { tick } from 'svelte'; - import { fly } from 'svelte/transition'; - import { enhance } from '$app/forms'; - import { Pin, Icon, Spinner } from '$comp'; - import { focus, fail } from '$lib/utils'; - import { password, loginRedirect } from '$lib/store'; - import { t } from '$lib/translations'; - import { page } from '$app/stores'; - import { generate } from '$lib/nostr'; - import { invalidateAll } from '$app/navigation'; + import { applyAction, deserialize } from "$app/forms"; + import { tick } from "svelte"; + import { fly } from "svelte/transition"; + import { enhance } from "$app/forms"; + import { Pin, Icon, Spinner } from "$comp"; + import { focus, fail } from "$lib/utils"; + import { password, loginRedirect } from "$lib/store"; + import { t } from "$lib/translations"; + import { page } from "$app/stores"; + import { generate } from "$lib/nostr"; + import { invalidateAll } from "$app/navigation"; - export let form; - export let pageID; + export let form; + export let pageID; - let token, formElement; - let code = []; - let redirect; + let token, formElement; + let code = []; + let redirect; - $password = undefined; + $password = undefined; - $: need2fa = form?.message === '2fa'; - let cancel = () => (need2fa = false); - $: if (form?.message === '2fa' && form.token === token) token = ''; + $: need2fa = form?.message === "2fa"; + let cancel = () => (need2fa = false); + $: if (form?.message === "2fa" && form.token === token) token = ""; - let username, email, btn; + let username, email, btn; - $: update(form); - let update = (form) => form && ({ username, password: $password } = form); + $: update(form); + let update = (form) => form && ({ username, password: $password } = form); - $: token && token?.length === 6 && tick().then(() => btn.click()); + $: token && token?.length === 6 && tick().then(() => btn.click()); - let revealPassword = false; + let revealPassword = false; - let action = { - register: '/register', - signIn: '/login' - }[pageID]; + let action = { + register: "/register", + signIn: "/login", + }[pageID]; - let loading; - async function handleSubmit(e) { - loading = true; + let loading; + async function handleSubmit(e) { + loading = true; - let data = new FormData(this); - let user = Object.fromEntries(data); - await generate(user); + let data = new FormData(this); + let user = Object.fromEntries(data); + await generate(user); - for (let k in user) { - data.set(k, user[k]); - } + for (let k in user) { + data.set(k, user[k]); + } - const response = await fetch(action, { - method: 'POST', - body: data - }); + const response = await fetch(action, { + method: "POST", + body: data, + }); - const result = deserialize(await response.text()); + const result = deserialize(await response.text()); - if (result.type === 'success') { - await invalidateAll(); - } + if (result.type === "success") { + await invalidateAll(); + } - applyAction(result); - loading = false; - } + applyAction(result); + loading = false; + } </script> {#if need2fa} - <Pin bind:value={token} title="Enter 2FA Code" {cancel} notify={false} /> + <Pin bind:value={token} title="Enter 2FA Code" {cancel} notify={false} /> {/if} <div class="pt-10"> - <div class="w-[243px] mx-auto mb-10"> - <a href="/"> - <Icon icon="logo" /> - </a> - </div> + <div class="w-[243px] mx-auto mb-10"> + <a href="/"> + <Icon icon="logo" /> + </a> + </div> - <div class="flex justify-center items-center"> - <div class="shadow-xl rounded-3xl p-10 pb-12 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]"> - <h1 class="text-2xl font-bold text-center">{$t('login.' + pageID)}</h1> + <div class="flex justify-center items-center"> + <div + class="shadow-xl rounded-3xl p-10 pb-12 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]" + > + <h1 class="text-2xl font-bold text-center">{$t("login." + pageID)}</h1> - {#if form?.error} - <div class="text-red-600 text-center" in:fly> - {form.error} - </div> - {/if} + {#if form?.error} + <div class="text-red-600 text-center" in:fly> + {form.error} + </div> + {/if} - <form class="space-y-5" on:submit|preventDefault={handleSubmit} method="POST"> - <input - type="hidden" - name="loginRedirect" - value={$loginRedirect || $page.url.searchParams.get('redirect')} - /> - <input type="hidden" name="token" value={token} /> + <form + class="space-y-5" + on:submit|preventDefault={handleSubmit} + method="POST" + > + <input + type="hidden" + name="loginRedirect" + value={$loginRedirect || $page.url.searchParams.get("redirect")} + /> + <input type="hidden" name="token" value={token} /> - <div> - <label for="username" class="font-semibold">{$t('login.username')}</label> - <input - name="username" - type="text" - required - class="bg-primary" - bind:value={username} - use:focus - autocapitalize="none" - /> - </div> + <div> + <label for="username" class="font-semibold" + >{$t("login.username")}</label + > + <input + name="username" + type="text" + required + class="bg-primary" + bind:value={username} + use:focus + autocapitalize="none" + /> + </div> - <div class="relative"> - <label for="password" class="block font-semibold">{$t('login.password')}</label> - {#if revealPassword} - <input - name="password" - type="text" - required - class="bg-primary" - bind:value={$password} - autocapitalize="none" - /> - {:else} - <input - name="password" - type="password" - required - class="bg-primary" - bind:value={$password} - autocapitalize="none" - /> - {/if} - <button - type="button" - on:click={() => (revealPassword = !revealPassword)} - class="absolute right-5 top-10" - > - <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> - </button> - </div> + <div class="relative"> + <label for="password" class="block font-semibold" + >{$t("login.password")}</label + > + {#if revealPassword} + <input + name="password" + type="text" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + /> + {:else} + <input + name="password" + type="password" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + /> + {/if} + <button + type="button" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-5 top-10" + > + <Icon icon={revealPassword ? "eye" : "eye-off"} /> + </button> + </div> - {#if pageID === 'register'} - <p class="text-secondary text-sm"> - {$t('login.passwordRecommendation')} - </p> - {:else} - <!-- TODO --> - <!-- <div class="flex justify-end items-center"> --> - <!-- <a href="/forgot" class="underline underline-offset-4 text-black text-sm" --> - <!-- >{$t('login.forgotUserOrPassword')}</a --> - <!-- > --> - <!-- </div> --> - {/if} + {#if pageID === "register"} + <p class="text-secondary text-sm"> + {$t("login.passwordRecommendation")} + </p> + {:else} + <!-- TODO --> + <!-- <div class="flex justify-end items-center"> --> + <!-- <a href="/forgot" class="underline underline-offset-4 text-black text-sm" --> + <!-- >{$t('login.forgotUserOrPassword')}</a --> + <!-- > --> + <!-- </div> --> + {/if} - <button - type="submit" - class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" - disabled={loading} - bind:this={btn} - > - {#if loading} - <Spinner /> - {:else} - {$t('login.' + pageID)} - {/if} - </button> - </form> + <button + type="submit" + class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" + disabled={loading} + bind:this={btn} + > + {#if loading} + <Spinner /> + {:else} + {$t("login." + pageID)} + {/if} + </button> + </form> - <p class="text-secondary text-center font-medium"> - {pageID === 'register' ? $t('login.haveAccount') : $t('login.noAccount')} - <a - href={pageID === 'register' ? '/login' : '/register'} - class="block md:inline text-black underline underline-offset-4 hover:opacity-80" - > - {pageID === 'register' ? $t('login.signIn') : $t('login.register')} - </a> - </p> - </div> - </div> + <p class="text-secondary text-center font-medium"> + {pageID === "register" + ? $t("login.haveAccount") + : $t("login.noAccount")} + <a + href={pageID === "register" ? "/login" : "/register"} + class="block md:inline text-black underline underline-offset-4 hover:opacity-80" + > + {pageID === "register" ? $t("login.signIn") : $t("login.register")} + </a> + </p> + </div> + </div> </div> diff --git a/src/components/Request.svelte b/src/components/Request.svelte index 44f8516d..f3331954 100644 --- a/src/components/Request.svelte +++ b/src/components/Request.svelte @@ -1,34 +1,36 @@ <script> - import { t } from '$lib/translations'; - import { fly } from 'svelte/transition'; - import { Avatar, Icon } from '$comp'; - import { request, requestRedirect } from '$lib/store'; - import { page } from '$app/stores'; + import { t } from "$lib/translations"; + import { fly } from "svelte/transition"; + import { Avatar, Icon } from "$comp"; + import { request, requestRedirect } from "$lib/store"; + import { page } from "$app/stores"; - export let user; + export let user; - $requestRedirect = $page.url.pathname; + $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> + <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/Slider.svelte b/src/components/Slider.svelte index 3c94d127..b82b6bf9 100644 --- a/src/components/Slider.svelte +++ b/src/components/Slider.svelte @@ -1,56 +1,56 @@ <script> - export let value, - handle, - min = 0, - max = 100; + export let value, + handle, + min = 0, + max = 100; </script> <input - id="slider" - type="range" - {min} - {max} - bind:value - class="px-0 py-0 my-5" - style="--bgPercent: {((value - min) * 100) / (max - min)}%;" - on:input={handle} + id="slider" + type="range" + {min} + {max} + bind:value + class="px-0 py-0 my-5" + style="--bgPercent: {((value - min) * 100) / (max - min)}%;" + on:input={handle} /> <style> - #slider { - -webkit-appearance: none; - width: 100%; - height: 6px; - border-radius: 5px; - background: linear-gradient( - to right, - black 0%, - black var(--bgPercent), - #f5f7fa var(--bgPercent), - #f5f7fa 100% - ); - outline: none; - } + #slider { + -webkit-appearance: none; + width: 100%; + height: 6px; + border-radius: 5px; + background: linear-gradient( + to right, + black 0%, + black var(--bgPercent), + #f5f7fa var(--bgPercent), + #f5f7fa 100% + ); + outline: none; + } - #slider:active { - background-color: black; - } + #slider:active { + background-color: black; + } - #slider::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 30px; - height: 30px; - border-radius: 50%; - background: black; - cursor: pointer; - } + #slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 30px; + height: 30px; + border-radius: 50%; + background: black; + cursor: pointer; + } - #slider::-moz-range-thumb { - width: 30px; - height: 30px; - border-radius: 50%; - background: black; - cursor: pointer; - } + #slider::-moz-range-thumb { + width: 30px; + height: 30px; + border-radius: 50%; + background: black; + cursor: pointer; + } </style> diff --git a/src/components/Spinner.svelte b/src/components/Spinner.svelte index eccf98ce..e9c3348f 100644 --- a/src/components/Spinner.svelte +++ b/src/components/Spinner.svelte @@ -1,13 +1,20 @@ <svg - class="mx-auto h-6 w-6 animate-spin text-white" - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" + class="mx-auto h-6 w-6 animate-spin text-white" + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" > - <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" /> - <path - class="opacity-75" - fill="currentColor" - d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" - /> + <circle + class="opacity-25" + cx="12" + cy="12" + r="10" + stroke="currentColor" + stroke-width="4" + /> + <path + class="opacity-75" + fill="currentColor" + d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" + /> </svg> diff --git a/src/components/Toggle.svelte b/src/components/Toggle.svelte index 3516e48b..1e4cf4c4 100644 --- a/src/components/Toggle.svelte +++ b/src/components/Toggle.svelte @@ -1,11 +1,17 @@ <script> - export let id; - export let value; + export let id; + export let value; </script> <label for={id} class="inline-flex relative items-center mr-5 cursor-pointer"> - <input type="checkbox" bind:checked={value} {id} name={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-gray-300 dark:peer-focus:ring-gray-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-gray-600" - /> + <input + type="checkbox" + bind:checked={value} + {id} + name={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-gray-300 dark:peer-focus:ring-gray-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-gray-600" + /> </label> diff --git a/src/components/index.js b/src/components/index.js index 53838581..661ad4fa 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,40 +1,40 @@ -export { default as Popup } from './Popup.svelte'; -export { default as Code } from './Code.svelte'; -export { default as Slider } from './Slider.svelte'; -export { default as Pinpad } from './Pinpad.svelte'; -export { default as Balance } from './Balance.svelte'; -export { default as Lnurlp } from './Lnurlp.svelte'; -export { default as Lnurlw } from './Lnurlw.svelte'; -export { default as Event } from './Event.svelte'; -export { default as Password } from './Password.svelte'; -export { default as Feed } from './Feed.svelte'; -export { default as Invoice } from './Invoice.svelte'; -export { default as Request } from './Request.svelte'; -export { default as Pin } from './Pin.svelte'; -export { default as Card } from './Card.svelte'; -export { default as Lock } from './Lock.svelte'; -export { default as Calendar } from './Calendar.svelte'; -export { default as Avatar } from './Avatar.svelte'; -export { default as Left } from './Left.svelte'; -export { default as Numpad } from './Numpad.svelte'; -export { default as Spinner } from './Spinner.svelte'; -export { default as Check } from './Check.svelte'; -export { default as Heart } from './Heart.svelte'; -export { default as Quote } from './Quote.svelte'; -export { default as LandingHeader } from './LandingHeader.svelte'; -export { default as LandingHero } from './LandingHero.svelte'; -export { default as LandingInfoCard } from './LandingInfoCard.svelte'; -export { default as HowItWorksCard } from './HowItWorksCard.svelte'; -export { default as FaqCard } from './FaqCard.svelte'; -export { default as Icon } from './Icon.svelte'; -export { default as Image } from './Image.svelte'; -export { default as RegisterOrLogin } from './RegisterOrLogin.svelte'; -export { default as AppHeader } from './AppHeader.svelte'; -export { default as LocaleSelector } from './LocaleSelector.svelte'; -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'; -export { default as LocationsMap } from './LocationsMap.svelte'; -export { default as Qr } from './Qr.svelte'; +export { default as Popup } from "./Popup.svelte"; +export { default as Code } from "./Code.svelte"; +export { default as Slider } from "./Slider.svelte"; +export { default as Pinpad } from "./Pinpad.svelte"; +export { default as Balance } from "./Balance.svelte"; +export { default as Lnurlp } from "./Lnurlp.svelte"; +export { default as Lnurlw } from "./Lnurlw.svelte"; +export { default as Event } from "./Event.svelte"; +export { default as Password } from "./Password.svelte"; +export { default as Feed } from "./Feed.svelte"; +export { default as Invoice } from "./Invoice.svelte"; +export { default as Request } from "./Request.svelte"; +export { default as Pin } from "./Pin.svelte"; +export { default as Card } from "./Card.svelte"; +export { default as Lock } from "./Lock.svelte"; +export { default as Calendar } from "./Calendar.svelte"; +export { default as Avatar } from "./Avatar.svelte"; +export { default as Left } from "./Left.svelte"; +export { default as Numpad } from "./Numpad.svelte"; +export { default as Spinner } from "./Spinner.svelte"; +export { default as Check } from "./Check.svelte"; +export { default as Heart } from "./Heart.svelte"; +export { default as Quote } from "./Quote.svelte"; +export { default as LandingHeader } from "./LandingHeader.svelte"; +export { default as LandingHero } from "./LandingHero.svelte"; +export { default as LandingInfoCard } from "./LandingInfoCard.svelte"; +export { default as HowItWorksCard } from "./HowItWorksCard.svelte"; +export { default as FaqCard } from "./FaqCard.svelte"; +export { default as Icon } from "./Icon.svelte"; +export { default as Image } from "./Image.svelte"; +export { default as RegisterOrLogin } from "./RegisterOrLogin.svelte"; +export { default as AppHeader } from "./AppHeader.svelte"; +export { default as LocaleSelector } from "./LocaleSelector.svelte"; +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"; +export { default as LocationsMap } from "./LocationsMap.svelte"; +export { default as Qr } from "./Qr.svelte"; diff --git a/src/lib/english.js b/src/lib/english.js index d6c205d9..52a0da82 100644 --- a/src/lib/english.js +++ b/src/lib/english.js @@ -2045,4 +2045,4 @@ youth zebra zero zone -zoo`.split('\n'); +zoo`.split("\n"); diff --git a/src/lib/invoice.js b/src/lib/invoice.js index 218972c6..87707c89 100644 --- a/src/lib/invoice.js +++ b/src/lib/invoice.js @@ -1,36 +1,37 @@ -import { auth, get, post } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import { auth, get, post } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export default async ({ cookies, request, url }) => { - let form = await request.formData(); + let form = await request.formData(); - let rates = await get('/rates'); - let amount = parseInt(form.get('amount')); - let request_id = form.get('request_id'); - if (request_id === 'undefined') request_id = undefined; + let rates = await get("/rates"); + let amount = parseInt(form.get("amount")); + let request_id = form.get("request_id"); + if (request_id === "undefined") request_id = undefined; - let invoice = { - amount, - tip: parseInt(form.get('tip')) || 0, - type: form.get('type'), - prompt: form.get('prompt') === 'true', - rate: parseFloat(form.get('rate')) || rates[form.get('currency')], - request_id - }; + let invoice = { + amount, + tip: parseInt(form.get("tip")) || 0, + type: form.get("type"), + prompt: form.get("prompt") === "true", + rate: parseFloat(form.get("rate")) || rates[form.get("currency")], + request_id, + }; - let user = { username: form.get('username'), currency: form.get('currency') }; + let user = { username: form.get("username"), currency: form.get("currency") }; - invoice = await post('/invoice', { invoice, user }, auth(cookies)); - let { id } = invoice; + invoice = await post("/invoice", { invoice, user }, auth(cookies)); + let { id } = invoice; - if (request_id) { - if (url.pathname.endsWith('tip')) { - throw redirect(307, `/send/invoice/${id}`); - } + if (request_id) { + if (url.pathname.endsWith("tip")) { + throw redirect(307, `/send/invoice/${id}`); + } - throw redirect(307, `/${user.username}/request/${request_id}`); - } + throw redirect(307, `/${user.username}/request/${request_id}`); + } - if (invoice.prompt) throw redirect(307, `/${user.username}/invoice/${id}/tip`); - else throw redirect(307, `/${user.username}/invoice/${id}`); + if (invoice.prompt) + throw redirect(307, `/${user.username}/invoice/${id}/tip`); + else throw redirect(307, `/${user.username}/invoice/${id}`); }; diff --git a/src/lib/nostr.js b/src/lib/nostr.js index c2634c83..5ab87e51 100644 --- a/src/lib/nostr.js +++ b/src/lib/nostr.js @@ -1,144 +1,164 @@ -import { get } from 'svelte/store'; -import { Buffer } from 'buffer'; -import { fail, wait, post, stretch } from '$lib/utils'; -import { decrypted, password as pw, passwordPrompt, pin, loginRedirect } from '$lib/store'; -import { goto, invalidate } from '$app/navigation'; -import { getPublicKey, signEvent, getEventHash, nip04, nip06, nip19 } from 'nostr-tools'; -import { mnemonicToEntropy, entropyToMnemonic } from '@scure/bip39'; -import wordlist from '$lib/english'; -import { bech32m } from '@scure/base'; +import { get } from "svelte/store"; +import { Buffer } from "buffer"; +import { fail, wait, post, stretch } from "$lib/utils"; +import { + decrypted, + password as pw, + passwordPrompt, + pin, + loginRedirect, +} from "$lib/store"; +import { goto, invalidate } from "$app/navigation"; +import { + getPublicKey, + signEvent, + getEventHash, + nip04, + nip06, + nip19, +} from "nostr-tools"; +import { mnemonicToEntropy, entropyToMnemonic } from "@scure/bip39"; +import wordlist from "$lib/english"; +import { bech32m } from "@scure/base"; const { generateSeedWords, privateKeyFromSeedWords } = nip06; const { encode, decode, toWords, fromWords } = bech32m; export let generate = async (user) => { - if (!get(pin)?.length === 6) return; - let password = get(pw); + if (!get(pin)?.length === 6) return; + let password = get(pw); - let salt = crypto.getRandomValues(new Uint8Array(16)); - let mnemonic = generateSeedWords(); - let sk = privateKeyFromSeedWords(mnemonic); + let salt = crypto.getRandomValues(new Uint8Array(16)); + let mnemonic = generateSeedWords(); + let sk = privateKeyFromSeedWords(mnemonic); - let bytes = new Uint8Array( - await crypto.subtle.encrypt( - { name: 'AES-GCM', iv: new Uint8Array(16) }, - await stretch(password, salt), - Uint8Array.from(Buffer.from(mnemonicToEntropy(mnemonic, wordlist), 'hex')) - ) - ); + let bytes = new Uint8Array( + await crypto.subtle.encrypt( + { name: "AES-GCM", iv: new Uint8Array(16) }, + await stretch(password, salt), + Uint8Array.from(Buffer.from(mnemonicToEntropy(mnemonic, wordlist), "hex")) + ) + ); - user.pubkey = getPublicKey(sk); - user.cipher = encode('en', toWords(bytes), 180); - user.salt = Buffer.from(salt).toString('hex'); + user.pubkey = getPublicKey(sk); + user.cipher = encode("en", toWords(bytes), 180); + user.salt = Buffer.from(salt).toString("hex"); }; export let encrypt = async ({ message, recipient, user }) => { - return nip04.encrypt(await getPrivateKey(user), recipient, message); + return nip04.encrypt(await getPrivateKey(user), recipient, message); }; export let decrypt = async ({ event, user }) => { - let cache = get(decrypted); - try { - let { content, pubkey, id } = event; - if (cache[id]) return cache[id]; - if (pubkey === user.pubkey) pubkey = event.tags[0][1]; + let cache = get(decrypted); + try { + let { content, pubkey, id } = event; + if (cache[id]) return cache[id]; + if (pubkey === user.pubkey) pubkey = event.tags[0][1]; - let message = await nip04.decrypt(await getPrivateKey(user), pubkey, content); + let message = await nip04.decrypt( + await getPrivateKey(user), + pubkey, + content + ); - cache[id] = Buffer.from(message).toString('utf8'); - decrypted.set(cache); + cache[id] = Buffer.from(message).toString("utf8"); + decrypted.set(cache); - return cache[id]; - } catch (e) { - // console.log(e); - } + return cache[id]; + } catch (e) { + // console.log(e); + } }; function typedArrayToBuffer(array) { - return array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset); + return array.buffer.slice( + array.byteOffset, + array.byteLength + array.byteOffset + ); } export let getMnemonic = async (user) => { - let { cipher, username, salt } = user; - let password = get(pw); + let { cipher, username, salt } = user; + let password = get(pw); - if (!password) { - passwordPrompt.set(true); - } + if (!password) { + passwordPrompt.set(true); + } - try { - await post('/password', { password }); - } catch (e) { - passwordPrompt.set(true); - } + try { + await post("/password", { password }); + } catch (e) { + passwordPrompt.set(true); + } - await wait(() => !!get(pw)); - password = get(pw); + await wait(() => !!get(pw)); + password = get(pw); - let mnemonic, key, seed, entropy, child, privkey; + let mnemonic, key, seed, entropy, child, privkey; - entropy = new Uint8Array( - await crypto.subtle.decrypt( - { name: 'AES-GCM', iv: new Uint8Array(16) }, - await stretch(password, Buffer.from(salt, 'hex')), - Uint8Array.from(fromWords(decode(cipher, 180).words)) - ) - ); + entropy = new Uint8Array( + await crypto.subtle.decrypt( + { name: "AES-GCM", iv: new Uint8Array(16) }, + await stretch(password, Buffer.from(salt, "hex")), + Uint8Array.from(fromWords(decode(cipher, 180).words)) + ) + ); - return entropyToMnemonic(entropy, wordlist); + return entropyToMnemonic(entropy, wordlist); }; let getPrivateKey = async (user) => { - return privateKeyFromSeedWords(await getMnemonic(user)); + return privateKeyFromSeedWords(await getMnemonic(user)); }; export let getNsec = async (user) => { - return nip19.nsecEncode(await getPrivateKey(user)); + return nip19.nsecEncode(await getPrivateKey(user)); }; export let sign = async ({ event, user }) => { - event.id = getEventHash(event); - event.sig = signEvent(event, await getPrivateKey(user)); + event.id = getEventHash(event); + event.sig = signEvent(event, await getPrivateKey(user)); }; export let send = (event) => { - return post('/events', { event }); + return post("/events", { event }); }; export let reEncryptEntropy = async (user, newPassword) => { - let { cipher, username, salt } = user; - let password = get(pw); + let { cipher, username, salt } = user; + let password = get(pw); - if (!password) { - passwordPrompt.set(true); - } + if (!password) { + passwordPrompt.set(true); + } - try { - await post('/password', { password }); - } catch (e) { - passwordPrompt.set(true); - } + try { + await post("/password", { password }); + } catch (e) { + passwordPrompt.set(true); + } - await wait(() => !!get(pw)); - password = get(pw); + await wait(() => !!get(pw)); + password = get(pw); - let mnemonic, key, seed, entropy, child, privkey; - entropy = Buffer.from( - await crypto.subtle.decrypt( - { name: 'AES-GCM', iv: new Uint8Array(16) }, - await stretch(password, Buffer.from(salt, 'hex')), - Uint8Array.from(fromWords(decode(cipher, 180).words)) - ), - 'hex' - ).toString('hex'); + let mnemonic, key, seed, entropy, child, privkey; + entropy = Buffer.from( + await crypto.subtle.decrypt( + { name: "AES-GCM", iv: new Uint8Array(16) }, + await stretch(password, Buffer.from(salt, "hex")), + Uint8Array.from(fromWords(decode(cipher, 180).words)) + ), + "hex" + ).toString("hex"); - let bytes = new Uint8Array( - await crypto.subtle.encrypt( - { name: 'AES-GCM', iv: new Uint8Array(16) }, - await stretch(newPassword, Buffer.from(salt, 'hex')), - Uint8Array.from(Buffer.from(entropy, 'hex')) - ) - ); + let bytes = new Uint8Array( + await crypto.subtle.encrypt( + { name: "AES-GCM", iv: new Uint8Array(16) }, + await stretch(newPassword, Buffer.from(salt, "hex")), + Uint8Array.from(Buffer.from(entropy, "hex")) + ) + ); - return encode('en', toWords(bytes), 180); + return encode("en", toWords(bytes), 180); }; diff --git a/src/lib/punks.js b/src/lib/punks.js index a87feb1f..e5551062 100644 --- a/src/lib/punks.js +++ b/src/lib/punks.js @@ -1,66 +1,66 @@ export default [ - '6e1f1626a9308abd0b7732fecbc7d49366ced46dead63ef3603fb0f4ec7aa53d', - '96ea95735b61c48c6f465db8ca0246a62539f34c3a977be6338770d92461da89', - '8c18c036b257d7b9c5a970a786efa04c320f3cf5afa73fa310a6603744d1ee3a', - 'a7a6187e54e83acdb893d456c8b4ad6877e4738ea9e0d47c9b2070bf2c98a35f', - '836d72150b6f3033b6220d15ac9d49457d81041a700d12898bd3b8e737b4d265', - 'e3d1c40538a8aed282a430647e3ff16ff94b64b8ed0a7cf427e8b0e2780a9b2a', - '2155224ef35cfb4bbdb06d982b42bb9b93d97b2bc691c22c804e1759a0a04882', - '5f6549ad9dd15dd57c204b1a9b60c68c376e77d7d114cfa13219e6a1a71f901f', - 'ba8dd5a7070aee7102d4f8bc83ac497091e2e473a0f448a1c513569167dd0791', - '4f07e1da3685a858970a9f39300564971e97257f5d37b55a6d9882014aecc421', - '01aa9348244c4b65de3df758d49c98076e17a109aef04bc211abc77cf42dc51d', - '080574eb9e0613926e0e5d9b667c132e12965a1b1d47218a0b290a2d55c55d2f', - '08ce4840184f63a779bcd1f91bbcb56a2e8cba75c3ba2dbd290a559a57e4238e', - '27caa38c14a85ad64b4c4469a34e61a3965a1cbc4cad9a85707da374301c830d', - 'eccc90741370ba4acc70bc65f4ccc38fddb1327d977c366156018974bc52fe4a', - '3759946d9e875ac2fc5c51ed850686193bb1e0716219e42efd52f3965993b275', - 'db5fe28b8ddb9ae5e4f5e0e757ee60bc938bffbdfdd38de807939e212d3c5066', - '3f8f861e663accee51e68b987705ce647a7446b7f118abc94c632e158acbb44f', - '43bab95a047112dca6705506f916a97252363a20da4a4c0a5ab7b676e11083fc', - 'd3018e45c66dd485697f738ad191af98267e6255d562496dd06b1a662e0c7a88', - '0ec2e8d07e053d686140857493ece653cde52764a60aa1d25651d81811d44a3d', - '4595bb092682af7177799464364c047fbfa7ede767336734949bce5d085fce15', - '6524936709e0719f9e28bf1caac91f86f323d1a79f10ab65a03fa9ea2e6b3270', - '8ba4736edc68da8e5b21c23af4de1f9131f47778c2a5b2a970a799223a9380b3', - '2aaba839e8546a8cb6d04a81ff5be83ac4e700ca4491607a16ebd60dc4995750', - '5f88f7f579ce50d7bcf60d6e9556f77acf361ba54caf3eb61dc4f2ca9c94c6a2', - 'd1ca262272613d7386fecce7f1ad268188940b59ea83fdd1f8d7ab85771e3f43', - '5211890e4128d8d730802cc75ee105867e8878581d5e72ffc98a0d60027a5398', - 'c1dececcbd7b623bfaa726392512e6ea110db93a0649dd01bf9e559318514f7c', - '43ce93c9835e983403a01329918d184832c5516e1b0ea4e0878b4c4def042777', - 'c84724a398bda8d20436fb0663d059c196628fa71192bb720fa5adf6137bed9b', - 'f307620a90a36045fa550316d61be4755c1f6c7f3fc81c95fc4d590ac568b2c4', - '78ae0118856a1f17c13f292628fcf3dccc150c7278578caaceb9f35ca8bc9f55', - '9d9a9a40c490f05d7dbafde8bf60d68aab7dcb79a7976eef62d1448a4549816b', - 'f6017bec50563dbbb24b979214ae225f96b01dea5b090b174038ff2842a1c6b1', - '6dbec748ea87e959390b0df429c005fccffe9e672864f555c14c7a407b940870', - '11701e76233395ebdba8d1d50334d1a0cc3a158f3452260c9a7b9a067b78d753', - '7354f3829dd05c838aa16d66b00316f7994797927a4d3b594c8af04a2b4ae86a', - '14d254333c6240fbdacbbbe261f7ae15be2e83bffcf084f4f59f459bae0a4571', - '7849ac0f31056265efb0110038f86e61effe17b7db836377d88433b73312e65a', - '9018be885177c675baf9b36978428d841ca30a9185e11de1a31238a08e3ec1af', - '777c5715de7423597fea6c03c496ea46414f0508cd96e119261f002965beb805', - 'b742b7509a97c2b346ea762f0510e8475b30dc988417f0f146ae00a800c6dd56', - '7af3029a826e6dfba23ad502901a3632ef4ee047bd45d618b1f106717b0af7ea', - '82390020b2bca461204cc49b20ee6fec1b93dad476726f377785d04040a4d6c3', - '6bc2990699be7f610c9f6a49bc7a19b034040727bc3de63fee89a1573fe00673', - '50c7c2e8e8c7f4aabffb195187a1ac1105f5712859f02d491d794ba2d8498e08', - '5d1d3769cd141603ce907df5dca159e389ca4d75338eddbe1b7db1c0766cbfd6', - '4d8ad7a761d068b7ef9b058fb4ba924b276e7a45a3ad47a79b767e08e821e038', - 'd5b5b2167602f7277cdac76cfda8bdad0befcb8ff12df495dfabf40c91d1120c', - '6f2618272bb0164247e9b0f40b19f020c1c9253510dc658a3cc078d079ef01bf', - 'fd502d5007bfc62a91eb7a34c1ae23b9de4a20cf5e1bbd86dadc421d7083099c', - '914813583b901044970b9c7d6dcdfde4344339325f5988d0dd681d377f1243fb', - '85cb7cf0245613d08b3027f879f58a6db645bf0db107242beb856b340ee49ee8', - 'd6dbb18d5df5e92c8c74ee59aa86d19dff25a5d0f9d030e84285a686f86804d7', - '65309f82cd9f9926a578d09e1ad13909f68a06d02b853760731a4225f7aecef4', - '433519d13f2b64fe82a90cd054797d4779507fea93c1054bd51c35ba846a0f7f', - '62a55d4d6e4b6f992a1c1840f041585c957affcdbf875202cf9e1db9def05aeb', - 'e19eb1eeabdeffa04ccd792eb09776690cf2121887b26a7c5d20d66e3e8fd0fb', - 'a80951fc593035abb2dda045a2076d29a30f5cc470e0a20db39c3ec588ed61d8', - '4f1b39906714266a7a1936bfa8e08c5d891285dd2b188b4bcd4afb0107671004', - 'f46310b2e60727af071d09e759814f52f933688a0ff0995c7da373b381e70005', - '2cc94821f56d21918ba4f2770d97f280e06fd99f4199167e2f4ddc780f404c8a', - '698a2ca7a09e88fa6594801da037f4dbce6ae841d1d33736aebcf8f6fe104e01', + "6e1f1626a9308abd0b7732fecbc7d49366ced46dead63ef3603fb0f4ec7aa53d", + "96ea95735b61c48c6f465db8ca0246a62539f34c3a977be6338770d92461da89", + "8c18c036b257d7b9c5a970a786efa04c320f3cf5afa73fa310a6603744d1ee3a", + "a7a6187e54e83acdb893d456c8b4ad6877e4738ea9e0d47c9b2070bf2c98a35f", + "836d72150b6f3033b6220d15ac9d49457d81041a700d12898bd3b8e737b4d265", + "e3d1c40538a8aed282a430647e3ff16ff94b64b8ed0a7cf427e8b0e2780a9b2a", + "2155224ef35cfb4bbdb06d982b42bb9b93d97b2bc691c22c804e1759a0a04882", + "5f6549ad9dd15dd57c204b1a9b60c68c376e77d7d114cfa13219e6a1a71f901f", + "ba8dd5a7070aee7102d4f8bc83ac497091e2e473a0f448a1c513569167dd0791", + "4f07e1da3685a858970a9f39300564971e97257f5d37b55a6d9882014aecc421", + "01aa9348244c4b65de3df758d49c98076e17a109aef04bc211abc77cf42dc51d", + "080574eb9e0613926e0e5d9b667c132e12965a1b1d47218a0b290a2d55c55d2f", + "08ce4840184f63a779bcd1f91bbcb56a2e8cba75c3ba2dbd290a559a57e4238e", + "27caa38c14a85ad64b4c4469a34e61a3965a1cbc4cad9a85707da374301c830d", + "eccc90741370ba4acc70bc65f4ccc38fddb1327d977c366156018974bc52fe4a", + "3759946d9e875ac2fc5c51ed850686193bb1e0716219e42efd52f3965993b275", + "db5fe28b8ddb9ae5e4f5e0e757ee60bc938bffbdfdd38de807939e212d3c5066", + "3f8f861e663accee51e68b987705ce647a7446b7f118abc94c632e158acbb44f", + "43bab95a047112dca6705506f916a97252363a20da4a4c0a5ab7b676e11083fc", + "d3018e45c66dd485697f738ad191af98267e6255d562496dd06b1a662e0c7a88", + "0ec2e8d07e053d686140857493ece653cde52764a60aa1d25651d81811d44a3d", + "4595bb092682af7177799464364c047fbfa7ede767336734949bce5d085fce15", + "6524936709e0719f9e28bf1caac91f86f323d1a79f10ab65a03fa9ea2e6b3270", + "8ba4736edc68da8e5b21c23af4de1f9131f47778c2a5b2a970a799223a9380b3", + "2aaba839e8546a8cb6d04a81ff5be83ac4e700ca4491607a16ebd60dc4995750", + "5f88f7f579ce50d7bcf60d6e9556f77acf361ba54caf3eb61dc4f2ca9c94c6a2", + "d1ca262272613d7386fecce7f1ad268188940b59ea83fdd1f8d7ab85771e3f43", + "5211890e4128d8d730802cc75ee105867e8878581d5e72ffc98a0d60027a5398", + "c1dececcbd7b623bfaa726392512e6ea110db93a0649dd01bf9e559318514f7c", + "43ce93c9835e983403a01329918d184832c5516e1b0ea4e0878b4c4def042777", + "c84724a398bda8d20436fb0663d059c196628fa71192bb720fa5adf6137bed9b", + "f307620a90a36045fa550316d61be4755c1f6c7f3fc81c95fc4d590ac568b2c4", + "78ae0118856a1f17c13f292628fcf3dccc150c7278578caaceb9f35ca8bc9f55", + "9d9a9a40c490f05d7dbafde8bf60d68aab7dcb79a7976eef62d1448a4549816b", + "f6017bec50563dbbb24b979214ae225f96b01dea5b090b174038ff2842a1c6b1", + "6dbec748ea87e959390b0df429c005fccffe9e672864f555c14c7a407b940870", + "11701e76233395ebdba8d1d50334d1a0cc3a158f3452260c9a7b9a067b78d753", + "7354f3829dd05c838aa16d66b00316f7994797927a4d3b594c8af04a2b4ae86a", + "14d254333c6240fbdacbbbe261f7ae15be2e83bffcf084f4f59f459bae0a4571", + "7849ac0f31056265efb0110038f86e61effe17b7db836377d88433b73312e65a", + "9018be885177c675baf9b36978428d841ca30a9185e11de1a31238a08e3ec1af", + "777c5715de7423597fea6c03c496ea46414f0508cd96e119261f002965beb805", + "b742b7509a97c2b346ea762f0510e8475b30dc988417f0f146ae00a800c6dd56", + "7af3029a826e6dfba23ad502901a3632ef4ee047bd45d618b1f106717b0af7ea", + "82390020b2bca461204cc49b20ee6fec1b93dad476726f377785d04040a4d6c3", + "6bc2990699be7f610c9f6a49bc7a19b034040727bc3de63fee89a1573fe00673", + "50c7c2e8e8c7f4aabffb195187a1ac1105f5712859f02d491d794ba2d8498e08", + "5d1d3769cd141603ce907df5dca159e389ca4d75338eddbe1b7db1c0766cbfd6", + "4d8ad7a761d068b7ef9b058fb4ba924b276e7a45a3ad47a79b767e08e821e038", + "d5b5b2167602f7277cdac76cfda8bdad0befcb8ff12df495dfabf40c91d1120c", + "6f2618272bb0164247e9b0f40b19f020c1c9253510dc658a3cc078d079ef01bf", + "fd502d5007bfc62a91eb7a34c1ae23b9de4a20cf5e1bbd86dadc421d7083099c", + "914813583b901044970b9c7d6dcdfde4344339325f5988d0dd681d377f1243fb", + "85cb7cf0245613d08b3027f879f58a6db645bf0db107242beb856b340ee49ee8", + "d6dbb18d5df5e92c8c74ee59aa86d19dff25a5d0f9d030e84285a686f86804d7", + "65309f82cd9f9926a578d09e1ad13909f68a06d02b853760731a4225f7aecef4", + "433519d13f2b64fe82a90cd054797d4779507fea93c1054bd51c35ba846a0f7f", + "62a55d4d6e4b6f992a1c1840f041585c957affcdbf875202cf9e1db9def05aeb", + "e19eb1eeabdeffa04ccd792eb09776690cf2121887b26a7c5d20d66e3e8fd0fb", + "a80951fc593035abb2dda045a2076d29a30f5cc470e0a20db39c3ec588ed61d8", + "4f1b39906714266a7a1936bfa8e08c5d891285dd2b188b4bcd4afb0107671004", + "f46310b2e60727af071d09e759814f52f933688a0ff0995c7da373b381e70005", + "2cc94821f56d21918ba4f2770d97f280e06fd99f4199167e2f4ddc780f404c8a", + "698a2ca7a09e88fa6594801da037f4dbce6ae841d1d33736aebcf8f6fe104e01", ]; diff --git a/src/lib/samples.js b/src/lib/samples.js index ac9e2d7e..1a1f5d9c 100644 --- a/src/lib/samples.js +++ b/src/lib/samples.js @@ -1,32 +1,32 @@ export default { - register: `curl "https://coinos.io/api/register" -H "content-type: application/json" -d '{ + register: `curl "https://coinos.io/api/register" -H "content-type: application/json" -d '{ "user": { "username": "demo", "password": "hunter2" } }'`, - login: `curl "https://coinos.io/api/login" -H "content-type: application/json" -d '{ + login: `curl "https://coinos.io/api/login" -H "content-type: application/json" -d '{ "username": "demo", "password": "hunter2" }'`, - payments: `curl "https://coinos.io/api/payments" + payments: `curl "https://coinos.io/api/payments" -H "content-type: application/json" -H "Authorization: Bearer $token" `, - lightning: `curl "https://coinos.io/api/payments" + lightning: `curl "https://coinos.io/api/payments" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ "payreq": "lnbc1pj94d8fsp5n77k340ps4m9jn7kp8he8yynpddvurv6mcsrrqpnq5l2jxdxzlwqpp5m96pqhc5nrlk8cqsu9ufdxxa43sarp8vwf9egvm2pg9nl0zu9r8qdq2vdhkjmn0wvxqztgcqpjrzjqwhmav82kntsppmkp8jp4vg4h9nns78tsy8mg7ve4lq5txrkp0h56zlarvqqdtcqqsqqqqqqqqqqp6cq9q9qyysgqzxlypywzphyujm3ga5j5csfcmqvlnae0fgnvymkaaw94eeg7py5rqzysyjkr3ev2snq63qpsc69vf54adkd0szvmvwt5cuadjnuy95sq4xfa40" }'`, - bitcoin: `curl "https://coinos.io/api/payments" + bitcoin: `curl "https://coinos.io/api/payments" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ "amount": 5000, "hash": "bc1q3unh97w4rmelflrm2hvdwz37d8kray3vn4d5ca" }'`, - internal: `HASH=$(curl "https://coinos.io/api/invoice" + internal: `HASH=$(curl "https://coinos.io/api/invoice" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ @@ -46,10 +46,10 @@ export default { "amount": 5000, "hash": $HASH }'`, - rates: `curl "https://coinos.io/api/rates" + rates: `curl "https://coinos.io/api/rates" -H "content-type: application/json" }'`, - lightningInvoice: `curl "https://coinos.io/api/invoice" + lightningInvoice: `curl "https://coinos.io/api/invoice" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ @@ -58,7 +58,7 @@ export default { "type": "lightning" } }'`, - bitcoinAddress: `curl "https://coinos.io/api/invoice" + bitcoinAddress: `curl "https://coinos.io/api/invoice" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ @@ -67,7 +67,7 @@ export default { "type": "bitcoin" } }'`, - webhook: `curl "https://coinos.io/api/invoice" + webhook: `curl "https://coinos.io/api/invoice" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ @@ -77,9 +77,9 @@ export default { "secret": "webhooksecret" } }'`, - fetchInvoice: `curl "https://coinos.io/api/invoice/bc1qmhfk9stzffhd9umzmld92vff7zg3mdlh7rvvaj" + fetchInvoice: `curl "https://coinos.io/api/invoice/bc1qmhfk9stzffhd9umzmld92vff7zg3mdlh7rvvaj" -H "content-type: application/json"'`, - invoiceResponse: `{ + invoiceResponse: `{ "amount":3141 "tip":0 "type":"bitcoin" @@ -92,5 +92,5 @@ export default { "received":0 "created":1677537428134 }`, - socketAuth: `echo '{"type":"login","data":{"username":"user","password":"password"}}' | websocat -n https://coinos.io/ws` + socketAuth: `echo '{"type":"login","data":{"username":"user","password":"password"}}' | websocat -n https://coinos.io/ws`, }; diff --git a/src/lib/socket.js b/src/lib/socket.js index 96fde317..d51ab39b 100644 --- a/src/lib/socket.js +++ b/src/lib/socket.js @@ -1,50 +1,58 @@ -import { get } from 'svelte/store'; -import { event, invoice, request, newPayment, last, rate, user } from '$lib/store'; -import { success, sat } from '$lib/utils'; -import { PUBLIC_SOCKET } from '$env/static/public'; -import { invalidate } from '$app/navigation'; -import { browser } from '$app/environment'; +import { get } from "svelte/store"; +import { + event, + invoice, + request, + newPayment, + last, + rate, + user, +} from "$lib/store"; +import { success, sat } from "$lib/utils"; +import { PUBLIC_SOCKET } from "$env/static/public"; +import { invalidate } from "$app/navigation"; +import { browser } from "$app/environment"; let socket, token; -export const auth = () => token && send('login', token); +export const auth = () => token && send("login", token); export const send = (type, data) => { - socket?.readyState === 1 && socket.send(JSON.stringify({ type, data })); - return true; + socket?.readyState === 1 && socket.send(JSON.stringify({ type, data })); + return true; }; export const messages = (data) => ({ - id() { - last.set(Date.now()); - }, - invoice() { - invoice.set(data); - }, - event() { - event.set(data); - }, - rate() { - rate.set(data); - }, + id() { + last.set(Date.now()); + }, + invoice() { + invoice.set(data); + }, + event() { + event.set(data); + }, + rate() { + rate.set(data); + }, - request() { - request.set(data); - invalidate('app:invoice'); - }, + request() { + request.set(data); + invalidate("app:invoice"); + }, - async payment() { - let { amount, confirmed } = data; - invalidate('app:user'); - invalidate('app:invoice'); - invalidate('app:payments'); + async payment() { + let { amount, confirmed } = data; + invalidate("app:user"); + invalidate("app:invoice"); + invalidate("app:payments"); - if (amount > 0) { - success(`${confirmed ? 'Received' : 'Detected'} ${sat(amount)}!`); - } - }, + if (amount > 0) { + success(`${confirmed ? "Received" : "Detected"} ${sat(amount)}!`); + } + }, - connected: auth + connected: auth, }); const initialReconnectDelay = 1000; @@ -53,39 +61,39 @@ const maxReconnectDelay = 16000; let currentReconnectDelay = initialReconnectDelay; export function connect(t) { - token = t; + token = t; - if (socket) return auth(); + if (socket) return auth(); - socket = new WebSocket(PUBLIC_SOCKET); - socket.addEventListener('open', onWebsocketOpen); - socket.addEventListener('close', onWebsocketClose); - socket.addEventListener('message', onWebsocketMessage); + socket = new WebSocket(PUBLIC_SOCKET); + socket.addEventListener("open", onWebsocketOpen); + socket.addEventListener("close", onWebsocketClose); + socket.addEventListener("message", onWebsocketMessage); } export function close() { - if (socket) socket.close(); + if (socket) socket.close(); } function onWebsocketMessage(msg) { - let { type, data } = JSON.parse(msg.data); - messages(data)[type] && messages(data)[type](); + let { type, data } = JSON.parse(msg.data); + messages(data)[type] && messages(data)[type](); } function onWebsocketOpen() { - currentReconnectDelay = initialReconnectDelay; + currentReconnectDelay = initialReconnectDelay; } function onWebsocketClose() { - socket = null; - setTimeout(() => { - reconnectToWebsocket(); - }, currentReconnectDelay + Math.floor(Math.random() * 3000)); + socket = null; + setTimeout(() => { + reconnectToWebsocket(); + }, currentReconnectDelay + Math.floor(Math.random() * 3000)); } function reconnectToWebsocket() { - if (currentReconnectDelay < maxReconnectDelay) { - currentReconnectDelay *= 2; - } - connect(token); + if (currentReconnectDelay < maxReconnectDelay) { + currentReconnectDelay *= 2; + } + connect(token); } diff --git a/src/lib/store.js b/src/lib/store.js index 403ee816..2ae8c9b9 100644 --- a/src/lib/store.js +++ b/src/lib/store.js @@ -1,56 +1,60 @@ -import { get, writable } from 'svelte/store'; -import { browser } from '$app/environment'; +import { get, writable } from "svelte/store"; +import { browser } from "$app/environment"; const persistSession = (key, defaultValue) => { - let s = writable( - browser && sessionStorage.getItem(key) && sessionStorage.getItem(key) !== 'undefined' - ? JSON.parse(sessionStorage.getItem(key)) - : defaultValue - ); + let s = writable( + browser && + sessionStorage.getItem(key) && + sessionStorage.getItem(key) !== "undefined" + ? JSON.parse(sessionStorage.getItem(key)) + : defaultValue + ); - s.subscribe((v) => { - browser && sessionStorage.setItem(key, JSON.stringify(v)); - }); + s.subscribe((v) => { + browser && sessionStorage.setItem(key, JSON.stringify(v)); + }); - return s; + return s; }; const persistLocal = (key, defaultValue) => { - let s = writable( - browser && localStorage.getItem(key) && localStorage.getItem(key) !== 'undefined' - ? JSON.parse(localStorage.getItem(key)) - : defaultValue - ); + let s = writable( + browser && + localStorage.getItem(key) && + localStorage.getItem(key) !== "undefined" + ? JSON.parse(localStorage.getItem(key)) + : defaultValue + ); - s.subscribe((v) => { - try { - browser && localStorage.setItem(key, JSON.stringify(v)); - } catch (e) { - console.log('problem setting key', v); - console.log(e); - } - }); + s.subscribe((v) => { + try { + browser && localStorage.setItem(key, JSON.stringify(v)); + } catch (e) { + console.log("problem setting key", v); + console.log(e); + } + }); - return s; + return s; }; -export const animatedRate = persistLocal('rate', 0); +export const animatedRate = persistLocal("rate", 0); export const avatar = writable(); export const banner = writable(); -export const colorTheme = writable('from-primary to-gradient'); +export const colorTheme = writable("from-primary to-gradient"); export const event = writable(); export const events = writable({}); export const invoice = writable(); export const last = writable(); export const loginRedirect = writable(); -export const decrypted = persistLocal('decrypted', {}); -export const newPayment = persistLocal('newPayment'); +export const decrypted = persistLocal("decrypted", {}); +export const newPayment = persistLocal("newPayment"); export const password = writable(); export const passwordPrompt = writable(); -export const pin = persistSession('pin'); +export const pin = persistSession("pin"); export const rate = writable(); export const request = writable(); export const requestRedirect = writable(); -export const token = persistSession('token'); +export const token = persistSession("token"); export const user = writable(); export const ndef = writable(); diff --git a/src/lib/ticket.js b/src/lib/ticket.js index e1ebcb8c..73ef7716 100644 --- a/src/lib/ticket.js +++ b/src/lib/ticket.js @@ -1,31 +1,31 @@ -import { auth, get, post } from '$lib/utils'; -import { env } from '$env/dynamic/private'; -import tickets from '$lib/tickets'; +import { auth, get, post } from "$lib/utils"; +import { env } from "$env/dynamic/private"; +import tickets from "$lib/tickets"; export const createTicket = async (cookies, username) => { - let { ticket } = await get('/ticket'); + let { ticket } = await get("/ticket"); - let { address } = await post( - `/${username}/invoice`, - { invoice: { network: 'liquid' }, user: { username } }, - auth(cookies) - ); + let { address } = await post( + `/${username}/invoice`, + { invoice: { network: "liquid" }, user: { username } }, + auth(cookies) + ); - let { asset } = await post( - '/assets', - { - address, - name: `Launch Party Ticket ${ticket}`, - ticker: 'LPT', - precision: 0, - asset_amount: '1', - token_amount: 0, - domain: 'coinos.io', - filename: tickets[ticket] - }, - { authorization: `Bearer ${env.LAUNCH}` } - ); + let { asset } = await post( + "/assets", + { + address, + name: `Launch Party Ticket ${ticket}`, + ticker: "LPT", + precision: 0, + asset_amount: "1", + token_amount: 0, + domain: "coinos.io", + filename: tickets[ticket], + }, + { authorization: `Bearer ${env.LAUNCH}` } + ); - await post('/ticket', { asset }, { authorization: `Bearer ${env.LAUNCH}` }); - return asset; + await post("/ticket", { asset }, { authorization: `Bearer ${env.LAUNCH}` }); + return asset; }; diff --git a/src/lib/tickets.js b/src/lib/tickets.js index b0ca0ac0..1f8cbdac 100644 --- a/src/lib/tickets.js +++ b/src/lib/tickets.js @@ -1,52 +1,52 @@ export default [ - 'QmPbFkiXNuaQyfwD4W9GcXP7qWaNd6wFd9KCnJir5mes1J', - 'QmPVjvbaMxSJRpDxMLYTdUWQbknej1vYLckBQ2pAgxZgJN', - 'QmVasycSFzCYhyXrcvTE7mN3d6W78RFnrhzg5cRF6Dp6Zq', - 'QmTmnhjpTBjZdeHwXgT9Dftc3uWMW73qsZpZ5ECrTyiKKZ', - 'QmWDBiyVPyFkzUrEdvy7DSJe1rBQpene1m6quKAtyjBP6a', - 'QmcxxzQZwgG7SE9QbHNvoTiCi6Dhs9rrz9roqvxm2JQA88', - 'Qmd9ThEpXLLHimEzRUWEFrAGLSw5PAHzn2SmwfCnjNLwWt', - 'Qmc7pWw1yxc2ARBWitDMYiyPkWK61NsXXU8oYAQwNAvcx1', - 'QmX9Hgw1rMFUgBBuq8QfZZDzLjN5xCED19fwFdDeDDqwmr', - 'QmconCAyNyaagqKvvfKCbG7Eu8c6vCtkv2ivvxMoFN9YwA', - 'QmWbFmvViy5gx4JdD5sw5YJETet4ZQcXtYgijYnrsnSZ3a', - 'QmP6QiiJwoqkduZMqdd5e5htHZ4aYHCsvEu7WEEUknKYa5', - 'QmefHk8hvBUHsuKugntok5u75Q8i886cnPwsj4LZZB6uWg', - 'QmdrSDK5ByYHxc88hfDu8hQgK5XYFpXSfPorUXdyTbE437', - 'QmZ6ZycjRzYpsjnLm1Lfgk1fXXDdhcYRkyfmqYJZVGwADy', - 'QmSkBxS5yLPjB2VPs89oQRZANsoyodK6s8s52E8GKXr2zt', - 'QmSn8TCPwq3UZ1Z4eyko76CvQo2H2Hyef1NGboDQN22m5i', - 'QmP6CTH5zrKMzZwtjy7VaK9NXiq4Gw53kmuRjUbJjeovoe', - 'QmdwT4yvz9Xf8g3t6irKRoh3Wa7MwRQrcUJckQxyQe1Bc5', - 'QmcwuHH3bD5JExqjDvKqRQ3V5xEVGqs94qPNVDzaCqJa5a', - 'QmRfVduHwn8sXwSoyfgZpxSi8L7EjP4LMp6fmijo9VApDp', - 'Qmc4TGj1Ahbg9HJ9WDXDm2bXsNkT4zRDp8Wdw9wqqdEwuH', - 'QmY8uQiHMWA8ztf2wAurNFW7eiTHQEUiLTQ2vGceMwVqz3', - 'QmXiXbaD12F4mkbUSQcKhWshkPU4oi29cUawZGnPk86VVU', - 'QmZaiAGjvBZBw4suz33s9JQNzY2Kshean2mQQPCa6gKMQe', - 'Qme8bDSdzX7tanPYf672xXW8vPdkLkeSLqLzHJqidRXsYJ', - 'QmRJirjuUKgh2AMyBF4jNquaXHFPBtDJf3gKFC614hX7qu', - 'QmdRoSKSgabcrpekNBPB6T47RBtJnCETYE8ry2a3P37MUe', - 'QmXoqArruHSghrUGW35vVQHPknBG5Cj939iWWHHXU3B616', - 'QmYCxtDuoHLMAFhNpifUsLU9ptAeFKwyxSiSEJhDQLMZtN', - 'QmQZQBw8wgzUsrmVDEejdePK23AEJbk9aUracvxLPyHz2x', - 'QmSsbu7U3i62AQKuYqfisRo5E6nqdMZfHRKWYJgouaXDxD', - 'QmaP4adVQuWyqtN4QCXeYXj4nr19Je4EqsaTG1zM9ywqFS', - 'QmPPsvgfXsiN3wNqdAwHAhQVpw6SZWNpry9Na6R2Xb7zXY', - 'QmR7z6wQmcfZUvRsVK83GRRboyvwZEDQujLwumMY33nAit', - 'QmQXgkgorDLPUyQpFbW8aLUW6jioQjUKamtXw5UuNCttDg', - 'QmcwYBeXt1BPfdSrtSrXFpWctRZ7RDHvmKTttC5hPPTSzG', - 'QmTUexZnw3xPy3YVdUsqJALwTvy2qFax5b3QpJVMvTCJQb', - 'QmNLkYXFUvctpfeeDTZxruZE2f834EUnYMM6JJUeTcUB4J', - 'QmT7544UaTPgKFYMCpPE4unXphNGX71BLZBPKLkxAijSmj', - 'QmTpVR4dqX1QV4fae5pZ1ACtRcANHwEWCLwpMXFmwE95Sm', - 'Qmdfyps5fW8d8wstXMfpvpei2DTmaADBWgmxLT7e7uan4P', - 'QmUSK8byipYoG3H97QpmPrHw9vYrvHygyhkiM81XmaKyxS', - 'QmQUGRvp96LTw5i8Ly9f5LoMvUpeQHCLGNqC88KV74wEHK', - 'Qma693TFLh9Z1MeBFzhPHi6Yk4tmTcZY2jogwJ8cPkU2B4', - 'QmNu8FyMpmkFdbCXGRVG8mKJtMvvCoLP8LNzFsxBQwxQeS', - 'QmWZHE87PKujL1MGLuFgP3D12o5GQxYbvbeTRgr12f1XMu', - 'QmcZJn8gPGd3poS9yuiNJh6iP4cn9uy5fnypky71r4sSSw', - 'QmTwrAF4E9ucpdnrUZFj6EfrQamGv7Qyn7ceSx1DUQYceC', - 'QmexssxU5iTXypgNqrmrQwMDJnTNxyZUxor5wDLnwb5TZp' + "QmPbFkiXNuaQyfwD4W9GcXP7qWaNd6wFd9KCnJir5mes1J", + "QmPVjvbaMxSJRpDxMLYTdUWQbknej1vYLckBQ2pAgxZgJN", + "QmVasycSFzCYhyXrcvTE7mN3d6W78RFnrhzg5cRF6Dp6Zq", + "QmTmnhjpTBjZdeHwXgT9Dftc3uWMW73qsZpZ5ECrTyiKKZ", + "QmWDBiyVPyFkzUrEdvy7DSJe1rBQpene1m6quKAtyjBP6a", + "QmcxxzQZwgG7SE9QbHNvoTiCi6Dhs9rrz9roqvxm2JQA88", + "Qmd9ThEpXLLHimEzRUWEFrAGLSw5PAHzn2SmwfCnjNLwWt", + "Qmc7pWw1yxc2ARBWitDMYiyPkWK61NsXXU8oYAQwNAvcx1", + "QmX9Hgw1rMFUgBBuq8QfZZDzLjN5xCED19fwFdDeDDqwmr", + "QmconCAyNyaagqKvvfKCbG7Eu8c6vCtkv2ivvxMoFN9YwA", + "QmWbFmvViy5gx4JdD5sw5YJETet4ZQcXtYgijYnrsnSZ3a", + "QmP6QiiJwoqkduZMqdd5e5htHZ4aYHCsvEu7WEEUknKYa5", + "QmefHk8hvBUHsuKugntok5u75Q8i886cnPwsj4LZZB6uWg", + "QmdrSDK5ByYHxc88hfDu8hQgK5XYFpXSfPorUXdyTbE437", + "QmZ6ZycjRzYpsjnLm1Lfgk1fXXDdhcYRkyfmqYJZVGwADy", + "QmSkBxS5yLPjB2VPs89oQRZANsoyodK6s8s52E8GKXr2zt", + "QmSn8TCPwq3UZ1Z4eyko76CvQo2H2Hyef1NGboDQN22m5i", + "QmP6CTH5zrKMzZwtjy7VaK9NXiq4Gw53kmuRjUbJjeovoe", + "QmdwT4yvz9Xf8g3t6irKRoh3Wa7MwRQrcUJckQxyQe1Bc5", + "QmcwuHH3bD5JExqjDvKqRQ3V5xEVGqs94qPNVDzaCqJa5a", + "QmRfVduHwn8sXwSoyfgZpxSi8L7EjP4LMp6fmijo9VApDp", + "Qmc4TGj1Ahbg9HJ9WDXDm2bXsNkT4zRDp8Wdw9wqqdEwuH", + "QmY8uQiHMWA8ztf2wAurNFW7eiTHQEUiLTQ2vGceMwVqz3", + "QmXiXbaD12F4mkbUSQcKhWshkPU4oi29cUawZGnPk86VVU", + "QmZaiAGjvBZBw4suz33s9JQNzY2Kshean2mQQPCa6gKMQe", + "Qme8bDSdzX7tanPYf672xXW8vPdkLkeSLqLzHJqidRXsYJ", + "QmRJirjuUKgh2AMyBF4jNquaXHFPBtDJf3gKFC614hX7qu", + "QmdRoSKSgabcrpekNBPB6T47RBtJnCETYE8ry2a3P37MUe", + "QmXoqArruHSghrUGW35vVQHPknBG5Cj939iWWHHXU3B616", + "QmYCxtDuoHLMAFhNpifUsLU9ptAeFKwyxSiSEJhDQLMZtN", + "QmQZQBw8wgzUsrmVDEejdePK23AEJbk9aUracvxLPyHz2x", + "QmSsbu7U3i62AQKuYqfisRo5E6nqdMZfHRKWYJgouaXDxD", + "QmaP4adVQuWyqtN4QCXeYXj4nr19Je4EqsaTG1zM9ywqFS", + "QmPPsvgfXsiN3wNqdAwHAhQVpw6SZWNpry9Na6R2Xb7zXY", + "QmR7z6wQmcfZUvRsVK83GRRboyvwZEDQujLwumMY33nAit", + "QmQXgkgorDLPUyQpFbW8aLUW6jioQjUKamtXw5UuNCttDg", + "QmcwYBeXt1BPfdSrtSrXFpWctRZ7RDHvmKTttC5hPPTSzG", + "QmTUexZnw3xPy3YVdUsqJALwTvy2qFax5b3QpJVMvTCJQb", + "QmNLkYXFUvctpfeeDTZxruZE2f834EUnYMM6JJUeTcUB4J", + "QmT7544UaTPgKFYMCpPE4unXphNGX71BLZBPKLkxAijSmj", + "QmTpVR4dqX1QV4fae5pZ1ACtRcANHwEWCLwpMXFmwE95Sm", + "Qmdfyps5fW8d8wstXMfpvpei2DTmaADBWgmxLT7e7uan4P", + "QmUSK8byipYoG3H97QpmPrHw9vYrvHygyhkiM81XmaKyxS", + "QmQUGRvp96LTw5i8Ly9f5LoMvUpeQHCLGNqC88KV74wEHK", + "Qma693TFLh9Z1MeBFzhPHi6Yk4tmTcZY2jogwJ8cPkU2B4", + "QmNu8FyMpmkFdbCXGRVG8mKJtMvvCoLP8LNzFsxBQwxQeS", + "QmWZHE87PKujL1MGLuFgP3D12o5GQxYbvbeTRgr12f1XMu", + "QmcZJn8gPGd3poS9yuiNJh6iP4cn9uy5fnypky71r4sSSw", + "QmTwrAF4E9ucpdnrUZFj6EfrQamGv7Qyn7ceSx1DUQYceC", + "QmexssxU5iTXypgNqrmrQwMDJnTNxyZUxor5wDLnwb5TZp", ]; diff --git a/src/lib/translations/index.js b/src/lib/translations/index.js index 5f4f1754..a07a6dbf 100644 --- a/src/lib/translations/index.js +++ b/src/lib/translations/index.js @@ -1,68 +1,71 @@ -import i18n from 'sveltekit-i18n'; -import lang from './lang.json'; +import i18n from "sveltekit-i18n"; +import lang from "./lang.json"; // setup loaders & translations from lang.json data function createLocaleLoader(localeCode) { - return { - locale: localeCode, - key: '', - loader: async () => (await import(`../../locales/${localeCode}.json`)).default - }; + return { + locale: localeCode, + key: "", + loader: async () => + (await import(`../../locales/${localeCode}.json`)).default, + }; } const availableLocales = Object.keys(lang); const translations = {}; for (let i = 0; i < availableLocales.length; i++) { - translations[availableLocales[i]] = { lang }; + translations[availableLocales[i]] = { lang }; } const loaders = availableLocales.map(createLocaleLoader); const config = { - initLocale: 'en', - fallbackLocale: 'en', - translations, - loaders: [ - { - locale: 'en', - key: '', - loader: async () => (await import('../../locales/en.json')).default - }, - { - locale: 'es', - key: '', - loader: async () => (await import('../../locales/es.json')).default - }, - { - locale: 'fr', - key: '', - loader: async () => (await import('../../locales/fr.json')).default - }, - { - locale: 'pt', - key: '', - loader: async () => (await import('../../locales/pt.json')).default - }, - { - locale: 'de', - key: '', - loader: async () => (await import('../../locales/de.json')).default - }, - { - locale: 'zh', - key: '', - loader: async () => (await import('../../locales/zh.json')).default - }, - { - locale: 'fa', - key: '', - loader: async () => (await import('../../locales/fa.json')).default - }, - { - locale: 'el', - key: '', - loader: async () => (await import('../../locales/el.json')).default - } - ] + initLocale: "en", + fallbackLocale: "en", + translations, + loaders: [ + { + locale: "en", + key: "", + loader: async () => (await import("../../locales/en.json")).default, + }, + { + locale: "es", + key: "", + loader: async () => (await import("../../locales/es.json")).default, + }, + { + locale: "fr", + key: "", + loader: async () => (await import("../../locales/fr.json")).default, + }, + { + locale: "pt", + key: "", + loader: async () => (await import("../../locales/pt.json")).default, + }, + { + locale: "de", + key: "", + loader: async () => (await import("../../locales/de.json")).default, + }, + { + locale: "zh", + key: "", + loader: async () => (await import("../../locales/zh.json")).default, + }, + { + locale: "fa", + key: "", + loader: async () => (await import("../../locales/fa.json")).default, + }, + { + locale: "el", + key: "", + loader: async () => (await import("../../locales/el.json")).default, + }, + ], }; -export const { t, loading, locales, locale, loadTranslations } = new i18n(config); +export const { t, loading, locales, locale, loadTranslations } = new i18n( + config +); diff --git a/src/lib/translations/lang.json b/src/lib/translations/lang.json index 0acc1de6..d18a67e3 100644 --- a/src/lib/translations/lang.json +++ b/src/lib/translations/lang.json @@ -1,10 +1,10 @@ { - "en": "English", - "fr": "Français", - "es": "Español", - "de": "Deutsch", - "pt": "Português", - "zh": "中国人", - "fa": "فارسی ", - "el": "Ελληνικά" + "en": "English", + "fr": "Français", + "es": "Español", + "de": "Deutsch", + "pt": "Português", + "zh": "中国人", + "fa": "فارسی ", + "el": "Ελληνικά" } diff --git a/src/lib/upload.js b/src/lib/upload.js index 92f5290f..552e4a7a 100644 --- a/src/lib/upload.js +++ b/src/lib/upload.js @@ -1,19 +1,19 @@ export const upload = async (file, type, progress, token) => { - let url = `/api/upload/${type}`; - let formData = new FormData(); - formData.append('file', file); + let url = `/api/upload/${type}`; + let formData = new FormData(); + formData.append("file", file); - return new Promise((resolve, reject) => { - let ajax = new XMLHttpRequest(); - ajax.addEventListener('load', () => { - if (ajax.readyState === ajax.DONE) { - if (ajax.status === 200) resolve(ajax.response); - else reject(new Error(`Upload failed: ${ajax.response}`)); - } - }); - ajax.upload.addEventListener('progress', progress, false); - ajax.open('POST', url); - // ajax.setRequestHeader('Authorization', `Bearer ${token}`); - ajax.send(formData); - }); + return new Promise((resolve, reject) => { + let ajax = new XMLHttpRequest(); + ajax.addEventListener("load", () => { + if (ajax.readyState === ajax.DONE) { + if (ajax.status === 200) resolve(ajax.response); + else reject(new Error(`Upload failed: ${ajax.response}`)); + } + }); + ajax.upload.addEventListener("progress", progress, false); + ajax.open("POST", url); + // ajax.setRequestHeader('Authorization', `Bearer ${token}`); + ajax.send(formData); + }); }; diff --git a/src/lib/utils.js b/src/lib/utils.js index 9b3e5592..c0df81ae 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,266 +1,282 @@ -import { browser } from '$app/environment'; -import { toast } from '@zerodevx/svelte-toast'; -import { goto } from '$app/navigation'; -import { PUBLIC_COINOS_URL } from '$env/static/public'; -import { page } from '$app/stores'; -import { get as getStore } from 'svelte/store'; +import { browser } from "$app/environment"; +import { toast } from "@zerodevx/svelte-toast"; +import { goto } from "$app/navigation"; +import { PUBLIC_COINOS_URL } from "$env/static/public"; +import { page } from "$app/stores"; +import { get as getStore } from "svelte/store"; export function scroll(section) { - if (getStore(page).url.pathname !== '/') goto('/'); - setTimeout(() => section.scrollIntoView({ behavior: 'smooth' }), 500); + if (getStore(page).url.pathname !== "/") goto("/"); + setTimeout(() => section.scrollIntoView({ behavior: "smooth" }), 500); } -let base = browser ? '' : PUBLIC_COINOS_URL; +let base = browser ? "" : PUBLIC_COINOS_URL; -export let punk = (k) => Math.floor((parseInt(k.slice(-2), 16) / 256) * 64) + 1 + '.webp'; +export let punk = (k) => + Math.floor((parseInt(k.slice(-2), 16) / 256) * 64) + 1 + ".webp"; export let g = (url, fetch, headers) => - fetch(base + url, { headers }) - .then((r) => r.text()) - .then((body) => { - try { - return JSON.parse(body); - } catch (e) { - throw new Error(body); - } - }); + fetch(base + url, { headers }) + .then((r) => r.text()) + .then((body) => { + try { + return JSON.parse(body); + } catch (e) { + throw new Error(body); + } + }); export let get = (url, headers = {}) => { - return fetch(base + url, { headers }) - .then(async (r) => { - if (r.ok) return r.text(); - throw new Error(await r.text()); - }) - .then((body) => { - try { - return JSON.parse(body); - } catch (e) { - return body; - } - }); -} + return fetch(base + url, { headers }) + .then(async (r) => { + if (r.ok) return r.text(); + throw new Error(await r.text()); + }) + .then((body) => { + try { + return JSON.parse(body); + } catch (e) { + return body; + } + }); +}; export let post = (url, body, headers) => { - headers = { ...headers, 'content-type': 'application/json', accept: 'application/json' }; - return fetch(base + url, { method: 'POST', body: JSON.stringify(body), headers }) - .then(async (r) => { - if (r.ok) return r.text(); - let text = await r.text(); + headers = { + ...headers, + "content-type": "application/json", + accept: "application/json", + }; + return fetch(base + url, { + method: "POST", + body: JSON.stringify(body), + headers, + }) + .then(async (r) => { + if (r.ok) return r.text(); + let text = await r.text(); - let message; - try { - ({ message } = JSON.parse(text)); - } catch (e) {} + let message; + try { + ({ message } = JSON.parse(text)); + } catch (e) {} - if (message) throw new Error(message); - throw new Error(text); - }) - .then((body) => { - try { - body = JSON.parse(body); - } catch (e) { - throw new Error(body); - } + if (message) throw new Error(message); + throw new Error(text); + }) + .then((body) => { + try { + body = JSON.parse(body); + } catch (e) { + throw new Error(body); + } - if (body.error instanceof Error) throw body.error; - if (body instanceof Error) throw body; - if (body.name === 'Error') throw new Error(body.message); + if (body.error instanceof Error) throw body.error; + if (body instanceof Error) throw body; + if (body.name === "Error") throw new Error(body.message); - return body; - }); + return body; + }); }; export let copy = (text) => { - navigator.clipboard.writeText(text); - success('Copied!'); + navigator.clipboard.writeText(text); + success("Copied!"); }; export let copyNoNewlines = (text) => { - let stripped = text.replace(/\n/g, ' ').replace(/\s+/g, ' '); - navigator.clipboard.writeText(stripped); - success('Copied!'); + let stripped = text.replace(/\n/g, " ").replace(/\s+/g, " "); + navigator.clipboard.writeText(stripped); + success("Copied!"); }; export function reverseFormat(val, locale) { - let parts = new Intl.NumberFormat(locale).formatToParts(1111.1); - let group = parts.find((part) => part.type === 'group').value; - let decimal = parts.find((part) => part.type === 'decimal').value; - let reversedVal = val.replace(new RegExp('\\' + group, 'g'), ''); - reversedVal = reversedVal.replace(new RegExp('\\' + decimal, 'g'), '.'); - return Number.isNaN(reversedVal) ? 0 : +reversedVal; + let parts = new Intl.NumberFormat(locale).formatToParts(1111.1); + let group = parts.find((part) => part.type === "group").value; + let decimal = parts.find((part) => part.type === "decimal").value; + let reversedVal = val.replace(new RegExp("\\" + group, "g"), ""); + reversedVal = reversedVal.replace(new RegExp("\\" + decimal, "g"), "."); + return Number.isNaN(reversedVal) ? 0 : +reversedVal; } export let protectedRoutes = [/customers/, /settings/, /payments/]; export let success = (m) => { - toast.pop(); - toast.push(m, { - theme: { - '--toastBarBackground': '#F5F7FA' - } - }); + toast.pop(); + toast.push(m, { + theme: { + "--toastBarBackground": "#F5F7FA", + }, + }); }; export let warning = (m) => { - toast.pop(); - toast.push(m, { - theme: { - '--toastBarBackground': '#FFCE22' - } - }); + toast.pop(); + toast.push(m, { + theme: { + "--toastBarBackground": "#FFCE22", + }, + }); }; export let fail = (m) => { - toast.pop(); - toast.push(m, { - theme: { - '--toastBarBackground': '#E93535' - } - }); + toast.pop(); + toast.push(m, { + theme: { + "--toastBarBackground": "#E93535", + }, + }); }; export let info = (m) => { - toast.pop(); - toast.push(m, { - theme: { - '--toastBarBackground': 'white' - } - }); + toast.pop(); + toast.push(m, { + theme: { + "--toastBarBackground": "white", + }, + }); }; export let login = async (user, cookies, ip) => { - let maxAge = 380 * 24 * 60 * 60; + let maxAge = 380 * 24 * 60 * 60; - let res = await fetch(base + '/login', { - method: 'POST', - body: JSON.stringify(user), - headers: { - 'content-type': 'application/json', - accept: 'application/json', - 'cf-connecting-ip': ip - } - }); - if (res.status === 401) { - let text = await res.text(); - if (text.startsWith('2fa')) throw new Error('2fa'); - } + let res = await fetch(base + "/login", { + method: "POST", + body: JSON.stringify(user), + headers: { + "content-type": "application/json", + accept: "application/json", + "cf-connecting-ip": ip, + }, + }); + if (res.status === 401) { + let text = await res.text(); + if (text.startsWith("2fa")) throw new Error("2fa"); + } - let { token } = await res.json(); - if (!token) throw new Error('Login failed'); + let { token } = await res.json(); + if (!token) throw new Error("Login failed"); - let expires = new Date(); - expires.setSeconds(expires.getSeconds() + maxAge); + let expires = new Date(); + expires.setSeconds(expires.getSeconds() + maxAge); - let opts = { path: '/', expires }; - cookies.set('username', user.username, opts); - cookies.set('token', token, opts); + let opts = { path: "/", expires }; + cookies.set("username", user.username, opts); + cookies.set("token", token, opts); }; -export let auth = (cookies) => ({ authorization: `Bearer ${cookies.get('token')}` }); +export let auth = (cookies) => ({ + authorization: `Bearer ${cookies.get("token")}`, +}); export let fiat = (amount, rate) => (amount * rate) / sats; export let fd = async (req) => { - let obj = Object.fromEntries(await req.formData()); - for (let k in obj) - (obj[k] === 'undefined' && (obj[k] = undefined)) || (obj[k] === 'false' && (obj[k] = false)); - return obj; + let obj = Object.fromEntries(await req.formData()); + for (let k in obj) + (obj[k] === "undefined" && (obj[k] = undefined)) || + (obj[k] === "false" && (obj[k] = false)); + return obj; }; export let f = (s, currency) => - new Intl.NumberFormat('en-US', { - style: 'currency', - currency - }) - .format(s) - .replace('CA', ''); + new Intl.NumberFormat("en-US", { + style: "currency", + currency, + }) + .format(s) + .replace("CA", ""); -export let s = (s) => new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(s); +export let s = (s) => + new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(s); export let sat = (s) => { - s = Math.abs(s); - let p = Math.floor(Math.log(s) / Math.LN10 + 0.000000001); - let d = Math.floor((p + 1) / 3); + s = Math.abs(s); + let p = Math.floor(Math.log(s) / Math.LN10 + 0.000000001); + let d = Math.floor((p + 1) / 3); - return ( - '⚡️' + - (parseInt(s) > 0 - ? new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 }).format(s / Math.pow(1000, d)) + - ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z'][d] - : 0 - ).toString() - ); + return ( + "⚡️" + + (parseInt(s) > 0 + ? new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }).format( + s / Math.pow(1000, d) + ) + ["", "K", "M", "G", "T", "P", "E", "Z"][d] + : 0 + ).toString() + ); }; export let sats = 100000000; -export let back = () => browser && (history.length ? history.go(-1) : goto('/')); -export let focus = (el) => browser && screen.width > 1280 && setTimeout(() => el.focus(), 1); +export let back = () => + browser && (history.length ? history.go(-1) : goto("/")); +export let focus = (el) => + browser && screen.width > 1280 && setTimeout(() => el.focus(), 1); export let sleep = (n) => new Promise((r) => setTimeout(r, n)); export let wait = async (f, n = 100, s = 300) => { - let i = 0; - while (!(await f()) && i < s) (await sleep(n)) && i++; - if (i >= s) throw new Error('timeout'); - return f(); + let i = 0; + while (!(await f()) && i < s) (await sleep(n)) && i++; + if (i >= s) throw new Error("timeout"); + return f(); }; export let stretch = async (password, salt) => - crypto.subtle.deriveKey( - { - name: 'PBKDF2', - salt, - iterations: 100000, - hash: 'SHA-256' - }, - await crypto.subtle.importKey( - 'raw', - new TextEncoder().encode(password), - { name: 'PBKDF2' }, - false, - ['deriveBits', 'deriveKey'] - ), - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'] - ); + crypto.subtle.deriveKey( + { + name: "PBKDF2", + salt, + iterations: 100000, + hash: "SHA-256", + }, + await crypto.subtle.importKey( + "raw", + new TextEncoder().encode(password), + { name: "PBKDF2" }, + false, + ["deriveBits", "deriveKey"] + ), + { name: "AES-GCM", length: 256 }, + true, + ["encrypt", "decrypt"] + ); function get_bigrams(string) { - var s = string.toLowerCase(); - var v = s.split(''); - for (var i = 0; i < v.length; i++) { - v[i] = s.slice(i, i + 2); - } - return v; + var s = string.toLowerCase(); + var v = s.split(""); + for (var i = 0; i < v.length; i++) { + v[i] = s.slice(i, i + 2); + } + return v; } export function ss(str1, str2) { - if (str1.length > 0 && str2.length > 0) { - var pairs1 = get_bigrams(str1); - var pairs2 = get_bigrams(str2); - var union = pairs1.length + pairs2.length; - var hits = 0; - for (var x = 0; x < pairs1.length; x++) { - for (var y = 0; y < pairs2.length; y++) { - if (pairs1[x] == pairs2[y]) hit_count++; - } - } - if (hits > 0) return (2.0 * hits) / union; - } + if (str1.length > 0 && str2.length > 0) { + var pairs1 = get_bigrams(str1); + var pairs2 = get_bigrams(str2); + var union = pairs1.length + pairs2.length; + var hits = 0; + for (var x = 0; x < pairs1.length; x++) { + for (var y = 0; y < pairs2.length; y++) { + if (pairs1[x] == pairs2[y]) hit_count++; + } + } + if (hits > 0) return (2.0 * hits) / union; + } - return 0.0; + return 0.0; } export let types = { - bitcoin: 'bitcoin', - lightning: 'lightning', - internal: 'internal', - fund: 'fund', - classic: 'classic' + bitcoin: "bitcoin", + lightning: "lightning", + internal: "internal", + fund: "fund", + classic: "classic", }; -export let ease = (t) => (t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t); - - export function shuffleArray(array) { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } - } +export let ease = (t) => + t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t; +export function shuffleArray(array) { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } +} diff --git a/src/locales/de.json b/src/locales/de.json index 73fa778e..652ee218 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -1,300 +1,300 @@ { - "404": { - "404": "404", - "pageNotFound": "Diese Seite konnte nicht gefunden werden." - }, - "about": { - "header": "Über uns", - "p1": "Coinos ist eine Open-Source-Bitcoin-Bezahl-App. Im September 2012 starteten wir die Entwicklung in Vancouver, um lokalen Händlern eine bequeme Möglichkeit zu bieten, Bitcoin-Zahlungen zu akzeptieren.", - "p2": "Sie können den Quellcode herunterladen, um die Coinos-App mit Ihrem eigenen Bitcoin- und Lightning-Node zu verbinden. Gerne helfen wir Ihnen dabei, ein wertvoller Dienstleister sowie Zahlungsanbieter für Ihre Kunden zu werden.", - "p3": "Transaktionen zwischen Nutzerkonten auf unserem Server werden Off-Chain durchgeführt. Diese werden sofort abgewickelt und sind völlig kostenlos sowie unlimitiert.", - "team": { - "header": "Das Team", - "adam": { - "name": "Adam Soltys", - "position": "Chief Technical Officer", - "bio": "Adam ist ein Webentwickler und Bitcoin-Enthusiast, der davon überzeugt ist, dass hartes Geld der Schlüssel zur Steigerung des globalen Wohlstands ist. Er lebt mit seiner Frau Chelsea und seinen zwei Kindern in North Vancouver." - }, - "secondl1ght": { - "name": "secondl1ght", - "position": "Web Developer", - "bio": "Secondl1ght hat sich das Programmieren selbst beigebracht, ist kopfüber in den Bitcoin-Kaninchenbau gesprungen und hat seinen Fiat-Job aufgegeben, um sich voll und ganz auf die Bitcoin-Entwicklung zu konzentrieren. Er wirkt bei Open-Source-Projekten mit, ist ein Verfechter der Privatsphäre und ein Freiheitsliebhaber." - }, + "404": { + "404": "404", + "pageNotFound": "Diese Seite konnte nicht gefunden werden." + }, + "about": { + "header": "Über uns", + "p1": "Coinos ist eine Open-Source-Bitcoin-Bezahl-App. Im September 2012 starteten wir die Entwicklung in Vancouver, um lokalen Händlern eine bequeme Möglichkeit zu bieten, Bitcoin-Zahlungen zu akzeptieren.", + "p2": "Sie können den Quellcode herunterladen, um die Coinos-App mit Ihrem eigenen Bitcoin- und Lightning-Node zu verbinden. Gerne helfen wir Ihnen dabei, ein wertvoller Dienstleister sowie Zahlungsanbieter für Ihre Kunden zu werden.", + "p3": "Transaktionen zwischen Nutzerkonten auf unserem Server werden Off-Chain durchgeführt. Diese werden sofort abgewickelt und sind völlig kostenlos sowie unlimitiert.", + "team": { + "header": "Das Team", + "adam": { + "name": "Adam Soltys", + "position": "Chief Technical Officer", + "bio": "Adam ist ein Webentwickler und Bitcoin-Enthusiast, der davon überzeugt ist, dass hartes Geld der Schlüssel zur Steigerung des globalen Wohlstands ist. Er lebt mit seiner Frau Chelsea und seinen zwei Kindern in North Vancouver." + }, + "secondl1ght": { + "name": "secondl1ght", + "position": "Web Developer", + "bio": "Secondl1ght hat sich das Programmieren selbst beigebracht, ist kopfüber in den Bitcoin-Kaninchenbau gesprungen und hat seinen Fiat-Job aufgegeben, um sich voll und ganz auf die Bitcoin-Entwicklung zu konzentrieren. Er wirkt bei Open-Source-Projekten mit, ist ein Verfechter der Privatsphäre und ein Freiheitsliebhaber." + }, - "karnage": { - "name": "Karnage", - "position": "Product Designer", - "bio": "Meine Mission ist es, Startup-Gründern zum Erfolg zu verhelfen. Ich entwickle Produkte, die dabei helfen, dieses Ziel zu erreichen." - }, - "shauna": { - "name": "Shauna", - "position": "Operations & Marketing", - "bio": "Strategie- & Projektmanagement. Unser Universalgenie in allen Bereichen. Findet immer Zeit, um sich auf einen Kaffee zu treffen." - }, - "cole": { - "name": "Cole", - "position": "Bitcoin Experte", - "bio": "Als zertifizierter Bitcoin-Experte ist Cole ein engagierter Mensch mit großer Leidenschaft für Freiheit. Er entdeckte Bitcoin im Jahr 2016 und beschäftigte sich eingehend damit. Jetzt möchte er das Lightning-Netzwerk so vielen Nutzern wie möglich näherbringen." - }, - "john": { - "name": "John Barratt", - "position": "Full Stack Web Developer", - "bio": "Wenn er nicht gerade programmiert, findet man John in der Natur beim Entdecken der vielen, großartigen Wanderwege, die BC zu bieten hat." - } - }, - "locations": { - "header": "Standorte", - "description": "Hier finden Sie einige Unternehmen, welche bereits Zahlungen mit Bitcoin über Coinos akzeptieren. Wir hoffen, dass Ihr Unternehmen das nächste sein wird!" - } - }, - "error": { - "header": "Error", - "message": "Bitte versuchen Sie es erneut oder kontaktieren Sie unseren Support.", - "signIn": "Bitte melden Sie sich an, um fortzufahren." - }, - "faq": { - "header": "FAQ", - "why": { - "question": "Warum Bitcoin akzeptieren?", - "answer": "Mit Bitcoin sparen Händler bei den Gebühren, Verbraucher gewinnen an Privatsphäre und Sparer an Kaufkraft. Wir bewerben Ihr Geschäft bei einer begeisterten Gemeinschaft, die bevorzugt in Geschäften einkauft, die Bitcoin akzeptieren." - }, - "cost": { - "question": "Was kostet das?", - "answer": "Zahlungen auf Ihr Konto zu erhalten ist kostenlos. Auszahlungen an externe Wallets verursachen eine geringe Gebühr, die je nach Netzwerkbedingungen variiert. Wir berechnen 0,1% für die Umwandlung zwischen Lightning und On-Chain Bitcoin. Das Senden zu anderen Coinos-Konten ist kostenlos." - }, - "individuals": { - "question": "Wer kann es nutzen?", - "answer": "Jeder, überall auf der Welt. Coinos ist sowohl für Einzelpersonen als auch für Unternehmen geeignet." - }, - "compatibility": { - "question": "Benötige ich ein spezielles Gerät oder eine spezielle Software?", - "answer": "Nein, Coinos ist eine Web-App, die überall funktioniert, wo es Internet gibt. Händler könnten es auf einem Tablet verwenden oder einen QR-Code anbieten, den Kunden mit ihrem Handy scannen können." - }, - "safety": { - "question": "Wann wird mein Guthaben beglichen?", - "answer": "Lightning-Zahlungen werden sofort beglichen. Sie können Ihr Guthaben innerhalb von Sekunden auf eine andere Wallet übertragen. On-Chain Einzahlungen dauern im Durchschnitt 5-10 Minuten zur Bestätigung." - }, - "pos": { - "question": "Kann Coinos mit meinem vorhandenen PoS integriert werden?", - "answer": "Wir empfehlen, Verkäufe in Ihrem vorhandenen PoS als Bartransaktionen einzugeben." - }, - "accounting": { - "question": "Was ist mit der Buchhaltung?", - "answer": "Wir zeigen alle Zahlungen in Ihrer lokalen Währung an und ermöglichen es Ihnen, nach Datum zu filtern, sodass Sie Ihr Einkommen wie gewohnt melden können. Sie können den Verlauf Ihres Kontos in eine CSV-Datei exportieren. Wenn Sie Bitcoin mit Gewinn verkaufen, möchten Sie vielleicht Kapitalerträge melden." - }, - "offramp": { - "question": "Kann ich den erhaltenen Bitcoin in meine lokale Währung umwandeln?", - "answer": "Sie können Ihre Gelder jederzeit an eine Börse oder einen Makler abziehen, um Ihren Bitcoin zu verkaufen und eine Banküberweisung zu erhalten. Wir könnten bereit sein, direkte Abrechnungen per E-Transfer für ausgewählte kanadische Händler anzubieten." - }, - "bitcoin": { - "question": "Was kann ich mit Bitcoin machen?", - "answer": "Geben Sie es bei anderen Händlern aus oder decken Sie damit Ihre Ausgaben, indem Sie Ihr Personal, Ihre Lieferanten und Ihren Vermieter dazu bringen, es zu akzeptieren." - } - }, - "footer": { - "company": "Unternehmen", - "followUs": "Folge uns", - "github": "GitHub", - "jobs": "Jobs", - "support": "Support", - "telegram": "Telegram", - "twitter": "Twitter", - "news": "Neuigkeiten" - }, - "howItWorks": { - "header": "So funktioniert's", - "step1": { - "header": "1. Erstellen Sie Ihr Konto", - "description": "Sie benötigen nur einen Benutzernamen und ein Passwort - und schon geht's los!" - }, - "step2": { - "header": "2. Zahlungen anfordern", - "description": "Ganz gleich, wie Sie es normalerweise machen würden, allerdings mit Bitcoin." - }, - "step3": { - "header": "3. Das war's!", - "description": "Bezahlen Sie mit Ihrem Bitcoin-Bestand bei anderen Händlern oder sparen Sie diese für schlechtere Zeiten - Sie allein haben die Wahl." - } - }, - "invoice": { - "addTip": "Trinkgeld hinzufügen", - "addTipq": "Trinkgeld hinzufügen?", - "done": "Fertig", - "paymentSuccessful": "Zahlung erhalten!", - "invoice": "Rechnung", - "total": "Gesamtbetrag", - "tip": "Trinkgeld" - }, - "jobs": { - "header": "Jobs", - "subheader": "Offene Stellen", - "pitch1": "Wir sind stets auf der Suche nach Entwicklern, die unser wachsendes Team verstärken möchten (Vollzeit oder Teilzeit, Homeoffice möglich).", - "pitch2": "Möchten Sie mit uns an Open-Source-Bitcoin-Projekten arbeiten? Dann werfen Sie doch einen Blick auf unsere offenen Stellen auf", - "github": "GitHub", - "pitch3": "Wenn wir Ihr Interesse geweckt haben, würden wir uns freuen, von Ihnen zu hören. Schicken Sie uns gerne Ihren Lebenslauf an" - }, - "landing": { - "header_before_icon": "Der", - "header_with_icon": "einfachste", - "header_after_icon": "Weg um Bitcoin in Ihrem Unternehmen zu akzeptieren.", - "subheader": "Erweitern Sie Ihren Kundenstamm und Ihren Umsatz, indem Sie Bitcoin mit einer einfach zu bedienenden Web-App akzeptieren. Keine Software oder Hardware ist dafür erforderlich.", - "info1": { - "title": "Sofortzahlungen über Lightning", - "description": "Unmittelbar nachdem die Zahlung von Ihrem Kunden abgeschlossen wurde, ist diese auch bei Ihnen ersichtlich. Sie müssen sich weder um Rücklastschriften kümmern, noch auf Banküberweisungen warten. Sekundenschnelle Abrechnung - und das Beste daran: Es fallen keine 3% Kreditkartenbearbeitungsgebühren an." - }, - "info2": { - "title": "Einfache Handhabung", - "description1": "Geben Sie einfach einen Betrag in Ihrer Landeswährung ein, teilen Sie den QR-Code mit Ihrem Kunden und schon sind Sie fertig.", - "description2": "Wir nehmen Ihnen die ganze Arbeit ab, damit Sie sich auf Ihr Geschäft konzentrieren können.", - "description3": "Sie haben jederzeit die Möglichkeit, selbst über Ihr Geld zu verfügen und es sofort abzuheben." - }, - "info3": { - "title": "Gestalten Sie Ihre Übersicht nach Ihrem Geschmack", - "description": "Passen Sie die Übersicht dem Branding Ihres Unternehmens an oder nutzen Sie unser Standard-Design." - } - }, - "login": { - "optional": "Optional", - "token": "2FA Token", - "username": "Benutzername", - "password": "Passwort", - "email": "E-Mail", - "passwordRecommendation": "Wir empfehlen ein langes, zufälliges Passwort, welches mit einem Passwortgenerator erstellt wurde.", - "forgotUserOrPassword": "Benutzername oder Passwort vergessen?", - "haveAccount": "Bereits registriert?", - "noAccount": "Noch nicht registriert?", - "register": "Registrieren", - "signIn": "Einloggen", - "failed": "Login fehlgeschlagen" - }, - "nav": { - "profile": "Profil", - "startInSeconds": "Jetzt loslegen", - "account": "Konto", - "signIn": "Einloggen", - "signOut": "Ausloggen", - "dashboard": "Übersicht", - "settings": "Einstellungen", - "support": "Support" - }, - "newSiteHeader": "Kommen Sie zu unserer <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">Eröffnungsfeier</a> in Vancouver am 26. Oktober! Auf der Suche nach der alten Coinos-Version? Besuchen Sie <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", - "news": { - "header": "News", - "subheader": "Letzte Updates", - "update1": "" - }, - "user": { - "bitcoin": "Bitcoin", - "lightning": "Lightning", - "invoice": "Rechnung", - "unfollow": "Nicht mehr folgen", - "following": "Folgen", - "follow": "Folgen", - "remove": "Entfernen", - "scanToPayBTC": "Den QR-Code scannen um mit Bitcoin zu zahlen", - "this": "Das", - "lightningAddress": "Lightning Adresse", - "dashboard": { - "go": "Los", - "dashboard": "Übersicht", - "accountBalance": "Kontostand", - "bitcoinPrice": "Bitcoin Preis:", - "conversionFeeWarning": "Es wird eine Abhebungsgebühr von 1% des Gesambetrags erhoben.", - "dataFromBinance": "Daten von <span class=\"text-black\">Binance</span>.", - "fetchingRate": "Abfragen...", - "optionalNote": "Notiz (optional)", - "send": "Senden", - "receive": "Erhalten", - "withdraw": "Senden", - "deposit": "Einzahlung", - "voucher": "Beleg" - }, - "receive": { - "lessThan1BTCWarning": "Bitte geben Sie einen Betrag unter 1 Bitcoin ein.", - "showQR": "QR-Code zeigen" - }, - "send": { - "contacts": "Kontakte", - "scan": "Scannen", - "paste": "Einfügen", - "next": "Weiter", - "placeholder": "Geben Sie eine Bitcoin-Adresse, eine Lightning-Rechnung oder einen Coinos-Benutzernamen ein" - }, - "settings": { - "bannerImage": "Logo", - "profileImage": "Profilbild", - "oneTimeCode": "Code eingeben", - "accountId": "Kontoverbindung", - "header": "Einstellungen", - "ACCOUNT": "KONTO", - "POINT_OF_SALE": "VERKAUFSSTELLE", - "SECURITY": "SICHERHEIT", - "locale": "Sprache", - "username": "Benutzername", - "email": "E-Mail", - "newPassword": "Neues Passwort", - "businessAddress": "Unternehmensadresse", - "emailNotifications": "E-Mail-Benachrichtigungen", - "emailNotificationsDescription": "Sie werden per E-Mail benachrichtigt, wenn eine Änderung auf Ihrem Konto vorgenommen wurde oder Ihnen jemand Zahlungen sendet.", - "smsNotifications": "SMS-Benachrichtigungen", - "smsNotificationsDescription": "Coinos wird Benachrichtigungen per SMS zusenden, wenn diese Funktion aktiviert ist.", - "theme": "Farbdesign", - "themeDescription": "Wählen Sie aus unseren Standard-Farbdesigns.", - "localCurrency": "Landeswährung", - "btcUnit": "Bitcoin anzeigen in:", - "bitcoin": "Bitcoin", - "satoshis": "Satoshis", - "securityPIN": "Sicherheitscode", - "securityPINDescription": "Legen Sie einen Sicherheitscode fest, der zum Entsperren Ihrer Einstellungen und zum Senden ausgehender Zahlungen verwendet wird.", - "setPIN": "Set PIN", - "autoLock": "Timer für automatische Sperre", - "autoLockDescription": "Ihr Konto wird nach einer bestimmten Zeit automatisch in einen sicheren Modus versetzt:", - "twofa": "2-Faktor Authentifizierung", - "twofaDescription": "Erhöhen Sie das Sicherheitslevel Ihres Geräts, indem Sie die 2-Faktor-Authentifizierung aktivieren.", - "twofaSetup": "2FA einrichten", - "twofaDisable": "2FA deaktivieren", - "select": "Auswählen", - "verifyPIN": "Sicherheitscode verifizieren", - "enablePIN": "PIN aktivieren", - "disablePIN": "PIN deaktivieren" - }, - "support": { - "header": "Support", - "email": "E-Mail", - "accountName": "Kontoname", - "message": "Nachricht", - "send": "Senden" - } - }, - "payments": { - "header": "Transaktionen", - "AMOUNT": "GESAMTBETRAG", - "TYPE": "TYP", - "TIME": "ZEIT", - "sent": "Gesendet", - "received": "Erhalten", - "empty": "Noch keine Transaktionen erhalten.", - "send": "Senden", - "next": "Weiter", - "sendersChoice": "Absender", - "sendTo": "Senden an", - "to": "an", - "pay": "Bezahlen", - "requesting": "ersucht um", - "sentInvoiceTo": "Rechnung senden an", - "sentRequestTo": "Anfrage senden an", - "pleasePay": "Bitte bezahlen Sie", - "day": "Tag", - "week": "Woche", - "month": "Monat", - "all": "Alles", - "export": "Exportieren", - "invoice": "Rechnung", - "readyToPay": "Steht zur Zahlung bereit", - "describeOrder": "Benutzen Sie dieses Feld um Ihre Bestellung zu beschreiben", - "requestAnInvoice": "Rechnung anfordern", - "submit": "Übermitteln", - "isRequesting": "Anfragen", - "awaiting": "Warten auf" - } + "karnage": { + "name": "Karnage", + "position": "Product Designer", + "bio": "Meine Mission ist es, Startup-Gründern zum Erfolg zu verhelfen. Ich entwickle Produkte, die dabei helfen, dieses Ziel zu erreichen." + }, + "shauna": { + "name": "Shauna", + "position": "Operations & Marketing", + "bio": "Strategie- & Projektmanagement. Unser Universalgenie in allen Bereichen. Findet immer Zeit, um sich auf einen Kaffee zu treffen." + }, + "cole": { + "name": "Cole", + "position": "Bitcoin Experte", + "bio": "Als zertifizierter Bitcoin-Experte ist Cole ein engagierter Mensch mit großer Leidenschaft für Freiheit. Er entdeckte Bitcoin im Jahr 2016 und beschäftigte sich eingehend damit. Jetzt möchte er das Lightning-Netzwerk so vielen Nutzern wie möglich näherbringen." + }, + "john": { + "name": "John Barratt", + "position": "Full Stack Web Developer", + "bio": "Wenn er nicht gerade programmiert, findet man John in der Natur beim Entdecken der vielen, großartigen Wanderwege, die BC zu bieten hat." + } + }, + "locations": { + "header": "Standorte", + "description": "Hier finden Sie einige Unternehmen, welche bereits Zahlungen mit Bitcoin über Coinos akzeptieren. Wir hoffen, dass Ihr Unternehmen das nächste sein wird!" + } + }, + "error": { + "header": "Error", + "message": "Bitte versuchen Sie es erneut oder kontaktieren Sie unseren Support.", + "signIn": "Bitte melden Sie sich an, um fortzufahren." + }, + "faq": { + "header": "FAQ", + "why": { + "question": "Warum Bitcoin akzeptieren?", + "answer": "Mit Bitcoin sparen Händler bei den Gebühren, Verbraucher gewinnen an Privatsphäre und Sparer an Kaufkraft. Wir bewerben Ihr Geschäft bei einer begeisterten Gemeinschaft, die bevorzugt in Geschäften einkauft, die Bitcoin akzeptieren." + }, + "cost": { + "question": "Was kostet das?", + "answer": "Zahlungen auf Ihr Konto zu erhalten ist kostenlos. Auszahlungen an externe Wallets verursachen eine geringe Gebühr, die je nach Netzwerkbedingungen variiert. Wir berechnen 0,1% für die Umwandlung zwischen Lightning und On-Chain Bitcoin. Das Senden zu anderen Coinos-Konten ist kostenlos." + }, + "individuals": { + "question": "Wer kann es nutzen?", + "answer": "Jeder, überall auf der Welt. Coinos ist sowohl für Einzelpersonen als auch für Unternehmen geeignet." + }, + "compatibility": { + "question": "Benötige ich ein spezielles Gerät oder eine spezielle Software?", + "answer": "Nein, Coinos ist eine Web-App, die überall funktioniert, wo es Internet gibt. Händler könnten es auf einem Tablet verwenden oder einen QR-Code anbieten, den Kunden mit ihrem Handy scannen können." + }, + "safety": { + "question": "Wann wird mein Guthaben beglichen?", + "answer": "Lightning-Zahlungen werden sofort beglichen. Sie können Ihr Guthaben innerhalb von Sekunden auf eine andere Wallet übertragen. On-Chain Einzahlungen dauern im Durchschnitt 5-10 Minuten zur Bestätigung." + }, + "pos": { + "question": "Kann Coinos mit meinem vorhandenen PoS integriert werden?", + "answer": "Wir empfehlen, Verkäufe in Ihrem vorhandenen PoS als Bartransaktionen einzugeben." + }, + "accounting": { + "question": "Was ist mit der Buchhaltung?", + "answer": "Wir zeigen alle Zahlungen in Ihrer lokalen Währung an und ermöglichen es Ihnen, nach Datum zu filtern, sodass Sie Ihr Einkommen wie gewohnt melden können. Sie können den Verlauf Ihres Kontos in eine CSV-Datei exportieren. Wenn Sie Bitcoin mit Gewinn verkaufen, möchten Sie vielleicht Kapitalerträge melden." + }, + "offramp": { + "question": "Kann ich den erhaltenen Bitcoin in meine lokale Währung umwandeln?", + "answer": "Sie können Ihre Gelder jederzeit an eine Börse oder einen Makler abziehen, um Ihren Bitcoin zu verkaufen und eine Banküberweisung zu erhalten. Wir könnten bereit sein, direkte Abrechnungen per E-Transfer für ausgewählte kanadische Händler anzubieten." + }, + "bitcoin": { + "question": "Was kann ich mit Bitcoin machen?", + "answer": "Geben Sie es bei anderen Händlern aus oder decken Sie damit Ihre Ausgaben, indem Sie Ihr Personal, Ihre Lieferanten und Ihren Vermieter dazu bringen, es zu akzeptieren." + } + }, + "footer": { + "company": "Unternehmen", + "followUs": "Folge uns", + "github": "GitHub", + "jobs": "Jobs", + "support": "Support", + "telegram": "Telegram", + "twitter": "Twitter", + "news": "Neuigkeiten" + }, + "howItWorks": { + "header": "So funktioniert's", + "step1": { + "header": "1. Erstellen Sie Ihr Konto", + "description": "Sie benötigen nur einen Benutzernamen und ein Passwort - und schon geht's los!" + }, + "step2": { + "header": "2. Zahlungen anfordern", + "description": "Ganz gleich, wie Sie es normalerweise machen würden, allerdings mit Bitcoin." + }, + "step3": { + "header": "3. Das war's!", + "description": "Bezahlen Sie mit Ihrem Bitcoin-Bestand bei anderen Händlern oder sparen Sie diese für schlechtere Zeiten - Sie allein haben die Wahl." + } + }, + "invoice": { + "addTip": "Trinkgeld hinzufügen", + "addTipq": "Trinkgeld hinzufügen?", + "done": "Fertig", + "paymentSuccessful": "Zahlung erhalten!", + "invoice": "Rechnung", + "total": "Gesamtbetrag", + "tip": "Trinkgeld" + }, + "jobs": { + "header": "Jobs", + "subheader": "Offene Stellen", + "pitch1": "Wir sind stets auf der Suche nach Entwicklern, die unser wachsendes Team verstärken möchten (Vollzeit oder Teilzeit, Homeoffice möglich).", + "pitch2": "Möchten Sie mit uns an Open-Source-Bitcoin-Projekten arbeiten? Dann werfen Sie doch einen Blick auf unsere offenen Stellen auf", + "github": "GitHub", + "pitch3": "Wenn wir Ihr Interesse geweckt haben, würden wir uns freuen, von Ihnen zu hören. Schicken Sie uns gerne Ihren Lebenslauf an" + }, + "landing": { + "header_before_icon": "Der", + "header_with_icon": "einfachste", + "header_after_icon": "Weg um Bitcoin in Ihrem Unternehmen zu akzeptieren.", + "subheader": "Erweitern Sie Ihren Kundenstamm und Ihren Umsatz, indem Sie Bitcoin mit einer einfach zu bedienenden Web-App akzeptieren. Keine Software oder Hardware ist dafür erforderlich.", + "info1": { + "title": "Sofortzahlungen über Lightning", + "description": "Unmittelbar nachdem die Zahlung von Ihrem Kunden abgeschlossen wurde, ist diese auch bei Ihnen ersichtlich. Sie müssen sich weder um Rücklastschriften kümmern, noch auf Banküberweisungen warten. Sekundenschnelle Abrechnung - und das Beste daran: Es fallen keine 3% Kreditkartenbearbeitungsgebühren an." + }, + "info2": { + "title": "Einfache Handhabung", + "description1": "Geben Sie einfach einen Betrag in Ihrer Landeswährung ein, teilen Sie den QR-Code mit Ihrem Kunden und schon sind Sie fertig.", + "description2": "Wir nehmen Ihnen die ganze Arbeit ab, damit Sie sich auf Ihr Geschäft konzentrieren können.", + "description3": "Sie haben jederzeit die Möglichkeit, selbst über Ihr Geld zu verfügen und es sofort abzuheben." + }, + "info3": { + "title": "Gestalten Sie Ihre Übersicht nach Ihrem Geschmack", + "description": "Passen Sie die Übersicht dem Branding Ihres Unternehmens an oder nutzen Sie unser Standard-Design." + } + }, + "login": { + "optional": "Optional", + "token": "2FA Token", + "username": "Benutzername", + "password": "Passwort", + "email": "E-Mail", + "passwordRecommendation": "Wir empfehlen ein langes, zufälliges Passwort, welches mit einem Passwortgenerator erstellt wurde.", + "forgotUserOrPassword": "Benutzername oder Passwort vergessen?", + "haveAccount": "Bereits registriert?", + "noAccount": "Noch nicht registriert?", + "register": "Registrieren", + "signIn": "Einloggen", + "failed": "Login fehlgeschlagen" + }, + "nav": { + "profile": "Profil", + "startInSeconds": "Jetzt loslegen", + "account": "Konto", + "signIn": "Einloggen", + "signOut": "Ausloggen", + "dashboard": "Übersicht", + "settings": "Einstellungen", + "support": "Support" + }, + "newSiteHeader": "Kommen Sie zu unserer <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">Eröffnungsfeier</a> in Vancouver am 26. Oktober! Auf der Suche nach der alten Coinos-Version? Besuchen Sie <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", + "news": { + "header": "News", + "subheader": "Letzte Updates", + "update1": "" + }, + "user": { + "bitcoin": "Bitcoin", + "lightning": "Lightning", + "invoice": "Rechnung", + "unfollow": "Nicht mehr folgen", + "following": "Folgen", + "follow": "Folgen", + "remove": "Entfernen", + "scanToPayBTC": "Den QR-Code scannen um mit Bitcoin zu zahlen", + "this": "Das", + "lightningAddress": "Lightning Adresse", + "dashboard": { + "go": "Los", + "dashboard": "Übersicht", + "accountBalance": "Kontostand", + "bitcoinPrice": "Bitcoin Preis:", + "conversionFeeWarning": "Es wird eine Abhebungsgebühr von 1% des Gesambetrags erhoben.", + "dataFromBinance": "Daten von <span class=\"text-black\">Binance</span>.", + "fetchingRate": "Abfragen...", + "optionalNote": "Notiz (optional)", + "send": "Senden", + "receive": "Erhalten", + "withdraw": "Senden", + "deposit": "Einzahlung", + "voucher": "Beleg" + }, + "receive": { + "lessThan1BTCWarning": "Bitte geben Sie einen Betrag unter 1 Bitcoin ein.", + "showQR": "QR-Code zeigen" + }, + "send": { + "contacts": "Kontakte", + "scan": "Scannen", + "paste": "Einfügen", + "next": "Weiter", + "placeholder": "Geben Sie eine Bitcoin-Adresse, eine Lightning-Rechnung oder einen Coinos-Benutzernamen ein" + }, + "settings": { + "bannerImage": "Logo", + "profileImage": "Profilbild", + "oneTimeCode": "Code eingeben", + "accountId": "Kontoverbindung", + "header": "Einstellungen", + "ACCOUNT": "KONTO", + "POINT_OF_SALE": "VERKAUFSSTELLE", + "SECURITY": "SICHERHEIT", + "locale": "Sprache", + "username": "Benutzername", + "email": "E-Mail", + "newPassword": "Neues Passwort", + "businessAddress": "Unternehmensadresse", + "emailNotifications": "E-Mail-Benachrichtigungen", + "emailNotificationsDescription": "Sie werden per E-Mail benachrichtigt, wenn eine Änderung auf Ihrem Konto vorgenommen wurde oder Ihnen jemand Zahlungen sendet.", + "smsNotifications": "SMS-Benachrichtigungen", + "smsNotificationsDescription": "Coinos wird Benachrichtigungen per SMS zusenden, wenn diese Funktion aktiviert ist.", + "theme": "Farbdesign", + "themeDescription": "Wählen Sie aus unseren Standard-Farbdesigns.", + "localCurrency": "Landeswährung", + "btcUnit": "Bitcoin anzeigen in:", + "bitcoin": "Bitcoin", + "satoshis": "Satoshis", + "securityPIN": "Sicherheitscode", + "securityPINDescription": "Legen Sie einen Sicherheitscode fest, der zum Entsperren Ihrer Einstellungen und zum Senden ausgehender Zahlungen verwendet wird.", + "setPIN": "Set PIN", + "autoLock": "Timer für automatische Sperre", + "autoLockDescription": "Ihr Konto wird nach einer bestimmten Zeit automatisch in einen sicheren Modus versetzt:", + "twofa": "2-Faktor Authentifizierung", + "twofaDescription": "Erhöhen Sie das Sicherheitslevel Ihres Geräts, indem Sie die 2-Faktor-Authentifizierung aktivieren.", + "twofaSetup": "2FA einrichten", + "twofaDisable": "2FA deaktivieren", + "select": "Auswählen", + "verifyPIN": "Sicherheitscode verifizieren", + "enablePIN": "PIN aktivieren", + "disablePIN": "PIN deaktivieren" + }, + "support": { + "header": "Support", + "email": "E-Mail", + "accountName": "Kontoname", + "message": "Nachricht", + "send": "Senden" + } + }, + "payments": { + "header": "Transaktionen", + "AMOUNT": "GESAMTBETRAG", + "TYPE": "TYP", + "TIME": "ZEIT", + "sent": "Gesendet", + "received": "Erhalten", + "empty": "Noch keine Transaktionen erhalten.", + "send": "Senden", + "next": "Weiter", + "sendersChoice": "Absender", + "sendTo": "Senden an", + "to": "an", + "pay": "Bezahlen", + "requesting": "ersucht um", + "sentInvoiceTo": "Rechnung senden an", + "sentRequestTo": "Anfrage senden an", + "pleasePay": "Bitte bezahlen Sie", + "day": "Tag", + "week": "Woche", + "month": "Monat", + "all": "Alles", + "export": "Exportieren", + "invoice": "Rechnung", + "readyToPay": "Steht zur Zahlung bereit", + "describeOrder": "Benutzen Sie dieses Feld um Ihre Bestellung zu beschreiben", + "requestAnInvoice": "Rechnung anfordern", + "submit": "Übermitteln", + "isRequesting": "Anfragen", + "awaiting": "Warten auf" + } } diff --git a/src/locales/el.json b/src/locales/el.json index d94c2627..198ce457 100644 --- a/src/locales/el.json +++ b/src/locales/el.json @@ -1,298 +1,298 @@ { - "404": { - "404": "404", - "pageNotFound": "Η σελίδα δεν βρέθηκε." - }, - "about": { - "header": "Σχετικά με", - "team": { - "header": "Η Oμάδα", - "adam": { - "name": "Adam Soltys", - "position": "Επικεφαλής Τεχνικός Διευθυντής", - "bio": "Ο Adam είναι προγραμματιστής ιστού και λάτρης του bitcoin που πιστεύει ότι το ισχυρό νόμισμα είναι το κλειδί για την αύξηση της παγκόσμιας ευημερίας. Ζει στο Βόρειο Vancouver με τη σύζυγό του Chelsea και τα δύο παιδιά του." - }, - "cole": { - "name": "Cole", - "position": "Πιστοποιημένος Eπαγγελματίας Bitcoin", - "bio": "Ο Cole είναι ένα άτομο με πάθος για ελευθερία. Ήρθε σε επαφή με το bitcoin το 2016 και το μελέτησε σε βάθος. Τώρα θέλει να εισαγάγει το δίκτυο lightning σε όσο το δυνατόν περισσότερους χρήστες." - } - }, - "locations": { - "header": "Τοποθεσίες", - "description": "Ακολουθούν ορισμένες επιχειρήσεις που δέχονται bitcoin με Coinos. Ελπίζουμε ότι η δική σας θα είναι η επόμενη!" - } - }, - "error": { - "header": "Σφάλμα", - "message": "Παρακαλώ δοκιμάστε ξανά ή επικοινωνήστε με την υποστήριξη.", - "signIn": "Συνδεθείτε για να συνεχίσετε." - }, - "faq": { - "header": "Συχνές Ερωτήσεις", - "why": { - "question": "Γιατί να δεχτώ Bitcoin;", - "answer": "Με το Bitcoin, οι εμπόροι εξοικονομούν σε τέλη, οι καταναλωτές αποκτούν ιδιωτικότητα και οι αποταμιευτές αυξάνουν την αγοραστική τους δύναμη. Θα προωθήσουμε την επιχείρησή σας σε μια ενθουσιασμένη κοινότητα που προτιμά να ψωνίζει σε μέρη που δέχονται Bitcoin." - }, - "cost": { - "question": "Πόσο κοστίζει;", - "answer": "Η λήψη πληρωμών στον λογαριασμό σας είναι δωρεάν. Οι αναλήψεις σε εξωτερικά πορτοφόλια έχουν μικρή χρέωση που διαφέρει ανάλογα με τις συνθήκες του δικτύου. Χρεώνουμε 0,1% για τη μετατροπή μεταξύ lightning και on-chain Bitcoin. Η αποστολή σε άλλους λογαριασμούς Coinos είναι δωρεάν." - }, - "individuals": { - "question": "Ποιος μπορεί να το χρησιμοποιήσει;", - "answer": "Οποιοσδήποτε, οπουδήποτε στον κόσμο. Το Coinos είναι κατάλληλο τόσο για ιδιώτες όσο και για επιχειρήσεις." - }, - "compatibility": { - "question": "Χρειάζομαι κάποια ειδική συσκευή ή λογισμικό;", - "answer": "Όχι, το Coinos είναι μια εφαρμογή ιστού που λειτουργεί οπουδήποτε υπάρχει Ίντερνετ. Οι εμπόροι μπορεί να το χρησιμοποιούν σε tablet ή να παρέχουν έναν κώδικα QR για τους πελάτες να σκανάρουν με το τηλέφωνό τους." - }, - "safety": { - "question": "Πότε εξοφλούνται τα χρήματά μου;", - "answer": "Οι πληρωμές μέσω Lightning εξοφλούνται άμεσα. Μπορείτε να αναλάβετε το υπόλοιπό σας σε άλλο πορτοφόλι μέσα σε δευτερόλεπτα. Οι καταθέσεις on-chain χρειάζονται κατά μέσο όρο 5-10 λεπτά για επιβεβαίωση." - }, - "pos": { - "question": "Μπορεί το Coinos να ενσωματωθεί στο υπάρχον PoS μου;", - "answer": "Συνιστούμε να καταχωρίσετε τις πωλήσεις στο υπάρχον PoS σας ως συναλλαγές μετρητών." - }, - "accounting": { - "question": "Τι γίνεται με τη λογιστική;", - "answer": "Εμφανίζουμε όλες τις πληρωμές σε όρους του τοπικού σας νομίσματος και σας επιτρέπουμε να φιλτράρετε ανά ημερομηνία, ώστε να μπορείτε να δηλώνετε το εισόδημά σας κανονικά. Μπορείτε να εξαγάγετε το ιστορικό του λογαριασμού σας σε ένα αρχείο csv. Εάν πουλήσετε Bitcoin με κέρδος, μπορεί να επιθυμείτε να αναφέρετε τα κερδηφόρα κεφάλαια." - }, - "offramp": { - "question": "Μπορώ να μετατρέψω το Bitcoin που λαμβάνω στο τοπικό μου νόμισμα;", - "answer": "Μπορείτε να αναλάβετε τα χρήματά σας ανά πάσα στιγμή σε ένα χρηματιστήριο ή σε έναν μεσίτη για να πουλήσετε το Bitcoin σας και να λάβετε μια τραπεζική μεταφορά. Ίσως είμαστε πρόθυμοι να προσφέρουμε άμεσες εκκαθαρίσεις μέσω E-Transfer για επιλεγμένους καναδικούς εμπόρους." - }, - "bitcoin": { - "question": "Τι μπορώ να κάνω με το Bitcoin;", - "answer": "Ξοδέψτε το σε άλλους εμπόρους ή καλύψτε τις δαπάνες σας παίρνοντας το προσωπικό σας, τους προμηθευτές σας και τον ιδιοκτήτη να το δεχτούν." - } + "404": { + "404": "404", + "pageNotFound": "Η σελίδα δεν βρέθηκε." }, - "footer": { - "company": "Εταιρία", - "followUs": "Ακολουθησε μας", - "github": "GitHub", - "linktree": "Linktree", - "jobs": "Linktree", - "support": "Υποστήριξη", - "telegram": "Telegram", - "twitter": "Twitter", - "documentation": "Τεκμηρίωση" - }, - "howItWorks": { - "header": "Πως δουλεύει", - "step1": { - "header": "1. Δημιουργήστε τον λογαριασμό σας", - "description": "Ένα όνομα χρήστη και κωδικός πρόσβασης είναι όλα όσα χρειάζεστε για να ξεκινήσετε." - }, - "step2": { - "header": "2. Ζητήστε πληρωμές", - "description": "Ότι θα κάνατε συνήθως, αλλά με το bitcoin." - }, - "step3": { - "header": "3. Αυτό είναι όλο!", - "description": "Ξοδέψτε το νόμισμά σας αμέσως ή αποθηκεύστε το και δείτε το να αυξάνεται, η επιλογή είναι δική σας." - } - }, - "invoice": { - "addTip": "Προσθήκη φιλοδωρήματος", - "addTipq": "Προσθήκη φιλοδωρήματος;", - "done": "Εγινε", - "paymentSuccessful": "Η πληρωμή ελήφθη!", - "invoice": "Τιμολόγιο", - "total": "Σύνολο", - "tip": "Φιλοδώρημα" - }, - "jobs": { - "header": "Δουλειές", - "subheader": "Τρέχουσες θέσεις εργασίας", - "pitch1": "«Η Coinos αναζητά ενεργά προγραμματιστές για να ενταχθούν στην αναπτυσσόμενη ομάδα μας (πλήρους ή μερικής απασχόλησης, απομακρυσμένη εργασία αποδεκτή) ", - "pitch2": "Είστε ενθουσιασμενοι που εργάζεστε σε bitcoin ανοιχτού κώδικα; Τότε ρίξτε μια ματιά στα αποθετήρια μας στο", - "github": "GitHub", - "pitch3": "Αν πιστεύετε ότι μπορείτε να ασκήσετε επιρροή, θα θέλαμε να σας ακούσουμε. Στείλτε μας το βιογραφικό σας στ" - }, - "landing": { - "header_before_icon": "Ο", - "header_with_icon": "πιο εύκολος", - "header_after_icon": "τρόπος για να ξεκινήσετε με το bitcoin ", - "subheader": "Ένα απλό αλλά ισχυρό πορτοφόλι ιστού για όλους. Δεν απαιτείται software ή hardware", - "info1": { - "title": "Άμεσες πληρωμές μέσω Lightning", - "description": "Διευκολύνουμε τους εμπόρους να λαμβάνουν πληρωμές λιανικής. Χωρίς αναμονή για τραπεζικούς διακανονισμούς ή ανησυχία για αντιστροφές χρεώσεων " - }, - "info2": { - "title": "Απλό στη χρήση", - "description1": "Απλώς πληκτρολογήστε ένα ποσό στο τοπικό σας νόμισμα, μοιραστείτε τον κωδικό QR και τελειώσατε.", - "description2": "Αναλαμβάνουμε τη δύσκολη δουλειά, ώστε να μπορείτε να επικεντρωθείτε στην επιχείρησή σας.", - "description3": "Έχετε πάντα τη δυνατότητα να αναλάβετε τη διαχείριση των χρημάτων σας, κάνοντας ανάληψη άμεσα και ανά πάσα στιγμή " - }, - "info3": { - "title": "Φτιάξτε το με τον δικό σας τρόπο!", - "description": "Προσαρμόστε την εμφάνιση και την αίσθηση της σελίδας του προφίλ σας ή διατηρήστε την ως προεπιλογή - εξαρτάται αποκλειστικά από εσάς " - } - }, - "login": { - "optional": "Προαιρετικό", - "token": "2FA Token", - "username": "Όνομα χρήστη", - "password": "Κωδικός πρόσβασης", - "email": "Διεύθυνση Ηλεκτρονικού Ταχυδρομείου", - "passwordRecommendation": "Συνιστούμε ένα μεγάλο, τυχαίο κωδικό πρόσβασης που δημιουργείται με έναν διαχειριστή κωδικών πρόσβασης ", - "forgotUserOrPassword": "Ξεχάσατε το όνομα χρήστη ή τον κωδικό πρόσβασης;", - "haveAccount": "Εχετε ήδη λογαριασμό;", - "noAccount": "Δεν έχετε λογαριασμό;", - "register": "Έγγραφη", - "signIn": "Σύνδεση", - "failed": "Η σύνδεση απέτυχ" - }, - "nav": { - "profile": "Προφίλ", - "startInSeconds": "Ξεκινήστε σε δευτερόλεπτα", - "account": "Λογαριασμός", - "signIn": "Σύνδεση", - "signOut": "Αποσύνδεση", - "dashboard": "Πίνακας Ελέγχου", - "settings": "Ρυθμίσεις", - "support": "Υποστήριξ" - }, - "news": { - "header": "Νέα", - "subheader": "Τελευταίες ενημερώσεις" - }, - "user": { - "bitcoin": "Bitcoin", - "lightning": "Lightning", - "invoice": "Τιμολόγιο", - "unfollow": "Κατάργηση παρακολούθησης", - "following": "Ακολουθείτε", - "follow": "Ακολουθήστε", - "followers": "Ακόλουθοι", - "remove": "Αφαίρεση", - "noNotes": "Δεν υπάρχουν σημειώσεις ακόμα", - "message": "Μήνυμα", - "messages": "Μηνύματα", - "feed": "Ροή", - "notes": "Σημειώσεις", - "pay": "Πληρωμή", - "scanToPayBTC": "Σάρωση για πληρωμή με bitcoin", - "this": "Αυτό", - "lightningAddress": "διεύθυνση lightning", - "dashboard": { - "go": "Πηγαίνετε", - "dashboard": "Πίνακας Ελέγχου", - "accountBalance": "Υπόλοιπο Λογαριασμού", - "bitcoinPrice": "Τιμή bitcoin:", - "conversionFeeWarning": "Θα χρεωθείτε με χρέωση ανάληψης 1% επί του ποσού ", - "send": "Αποστολή", - "receive": "Λήψη", - "withdraw": "Αποστολή", - "deposit": "Κατάθεση", - "voucher": "Κουπόν" - }, - "receive": { - "lessThan1BTCWarning": "Παρακαλώ εισάγετε ένα ποσό μικρότερο από 1 bitcoin ", - "showQR": "Εμφάνιση Q" - }, - "send": { - "contacts": "Επαφές", - "scan": "Σάρωση", - "paste": "Επικόλληση", - "next": "Επόμενο", - "placeholder": "Επικολλήστε μια διεύθυνση bitcoin, ένα τιμολόγιο lighting ή ένα όνομα χρήστη coino" - }, - "settings": { - "saveSettings": "Αποθήκευση ρυθμίσεων", - "bannerImage": "Εικόνα Banner", - "profileImage": "Εικόνα Προφίλ", - "oneTimeCode": "Εισαγάγετε κωδικό μίας χρήσης", - "accountId": "Μυστικός λογαριασμός", - "header": "Ρυθμίσεις", - "ACCOUNT": "ΛΟΓΑΡΙΑΣΜΟΣ", - "POINT_OF_SALE": "ΠΡΟΤΙΜΗΣΕΙΣ", - "SECURITY": "ΑΣΦΑΛΕΙΑ", - "locale": "Γλώσσα", - "username": "Όνομα χρήστη", - "email": "Ηλεκτρονικό Ταχυδρομεί", - "newPassword": "Αλλαγή κωδικού", - "about": "Σχετικά με", - "aboutPlaceholder": "Λίγα πράγματα για εσάς ή τη διεύθυνση και τις ώρες της επιχείρησής σας", - "emailNotifications": "Ειδοποιήσεις ηλεκτρονικού ταχυδρομείου", - "emailNotificationsDescription": "Λάβετε ειδοποιήσεις μέσω ηλεκτρονικού ταχυδρομείου όταν συμβαίνει μια αλλαγή στο λογαριασμό σας ή όταν οι άνθρωποι σας στέλνουν πληρωμές ", - "smsNotifications": "Ειδοποιήσεις SMS", - "smsNotificationsDescription": "Το Coinos θα σας στείλει ειδοποιήσεις κειμένου όταν αυτές είναι ενεργοποιημένες ", - "theme": "Χρωματικό θέμα", - "tipPrompt": "Προτροπή για φιλοδώρημα", - "tipPromptDescription": "Ρωτήστε τους ανθρώπους εάν θέλουν να προσθέσουν ένα φιλοδώρημα πριν πληρώσουν τα τιμολόγιά τους", - "displayName": "Εμφανιζόμενο όνομα", - "themeDescription": "Επιλέξτε από τα προεπιλεγμένα χρωματικά θέματα.", - "localCurrency": "Νόμισμα", - "btcUnit": "Εμφάνιση bitcoin σε:", - "bitcoin": "bitcoin", - "minutes": "λεπτά", - "hour": "ώρα", - "hours": "ώρες", - "satoshis": "Satoshis", - "securityPIN": "PIN Ασφαλείας", - "securityPINDescription": "Ορίστε μια καρφίτσα ασφαλείας που θα χρησιμοποιείται για το ξεκλείδωμα των ρυθμίσεών σας και για την αποστολή εξερχόμενων πληρωμών ", - "setPIN": "Ορισμός PIN", - "autoLock": "Αυτόματο κλείδωμα χρονοδιακόπτη", - "autoLockDescription": "Ο λογαριασμός σας θα μεταβεί σε ασφαλή λειτουργία, απαιτώντας από εσάς να εισαγάγετε ξανά τα διαπιστευτήριά σας μετά από μια καθορισμένη ώρα:", - "twofa": "Έλεγχος ταυτότητας 2 παραγόντων", - "twofaDescription": "Προσθέστε ένα επιπλέον επίπεδο ασφάλειας στη συσκευή σας ενεργοποιώντας τον έλεγχο ταυτότητας 2 παραγόντων ", - "twofaSetup": "Ρύθμιση 2FA", - "twofaDisable": "Απενεργοποίηση 2FA", - "select": "Επιλογή", - "verifyPIN": "Επαλήθευση PIN", - "enablePIN": "Ενεργοποίηση PIN", - "disablePIN": "Απενεργοποίηση PI" - }, - "support": { - "header": "Υποστήριξη", - "email": "Ηλεκτρονικό Ταχυδρομείο", - "accountName": "Ονομα λογαριασμού", - "message": "Μήνυμα", - "send": "Αποστολ" - } - }, - "payments": { - "amount": "Ποσό", - "date": "Ημερομηνία", - "id": "Ταυτότητα συναλλαγής", - "details": "Λεπτομέρειες Συναλλαγής", - "header": "Πληρωμές", - "preimage": "Απόδειξη πληρωμής", - "type": "Τύπος", - "time": "Χρόνος", - "sent": "Απεστάλει", - "received": "Παραλήφθηκε", - "empty": "Αποστολή", - "send": "Επόμενο", - "next": "Επιλογή αποστολέα", - "sendersChoice": "Αποστολή σε", - "sendTo": "προς", - "to": "προς", - "pay": "Πληρωμή", - "requesting": "απαιτείται", - "sentInvoiceTo": "Εστάλει τιμολόγιο σε", - "sentRequestTo": "Αίτημα εστάλει!", - "pleasePay": "Παρακαλώ πληρώστε", - "day": "Ημέρα", - "week": "Εβδομάδ", - "month": "Μήνας", - "all": "Ολα", - "export": "Εξαγωγή", - "invoice": "Τιμολόγιο", - "readyToPay": "είναι έτοιμο για πληρωμή", - "describeOrder": "Χρησιμοποιήστε αυτό το πεδίο για να περιγράψετε την παραγγελία σας", - "requestAnInvoice": "Αίτημα Τιμολογίου", - "submit": "Υποβολή", - "isRequesting": "απαιτείται", - "invoiceFrom": "Τιμολόγιο από", - "awaiting": "Αναμονή", - "tapAnywhere": "Πατήστε οπουδήποτε για να συνεχίσετε", - "from": "από", - "redeem": "Εξαργύρωση", - "redeemed": "Εξαργυρώθηκε", - "redeemedBy": "Εξαργυρώθηκε από", - "sendMessage": "Γράψτε ένα μήνυμα..." - } + "about": { + "header": "Σχετικά με", + "team": { + "header": "Η Oμάδα", + "adam": { + "name": "Adam Soltys", + "position": "Επικεφαλής Τεχνικός Διευθυντής", + "bio": "Ο Adam είναι προγραμματιστής ιστού και λάτρης του bitcoin που πιστεύει ότι το ισχυρό νόμισμα είναι το κλειδί για την αύξηση της παγκόσμιας ευημερίας. Ζει στο Βόρειο Vancouver με τη σύζυγό του Chelsea και τα δύο παιδιά του." + }, + "cole": { + "name": "Cole", + "position": "Πιστοποιημένος Eπαγγελματίας Bitcoin", + "bio": "Ο Cole είναι ένα άτομο με πάθος για ελευθερία. Ήρθε σε επαφή με το bitcoin το 2016 και το μελέτησε σε βάθος. Τώρα θέλει να εισαγάγει το δίκτυο lightning σε όσο το δυνατόν περισσότερους χρήστες." + } + }, + "locations": { + "header": "Τοποθεσίες", + "description": "Ακολουθούν ορισμένες επιχειρήσεις που δέχονται bitcoin με Coinos. Ελπίζουμε ότι η δική σας θα είναι η επόμενη!" + } + }, + "error": { + "header": "Σφάλμα", + "message": "Παρακαλώ δοκιμάστε ξανά ή επικοινωνήστε με την υποστήριξη.", + "signIn": "Συνδεθείτε για να συνεχίσετε." + }, + "faq": { + "header": "Συχνές Ερωτήσεις", + "why": { + "question": "Γιατί να δεχτώ Bitcoin;", + "answer": "Με το Bitcoin, οι εμπόροι εξοικονομούν σε τέλη, οι καταναλωτές αποκτούν ιδιωτικότητα και οι αποταμιευτές αυξάνουν την αγοραστική τους δύναμη. Θα προωθήσουμε την επιχείρησή σας σε μια ενθουσιασμένη κοινότητα που προτιμά να ψωνίζει σε μέρη που δέχονται Bitcoin." + }, + "cost": { + "question": "Πόσο κοστίζει;", + "answer": "Η λήψη πληρωμών στον λογαριασμό σας είναι δωρεάν. Οι αναλήψεις σε εξωτερικά πορτοφόλια έχουν μικρή χρέωση που διαφέρει ανάλογα με τις συνθήκες του δικτύου. Χρεώνουμε 0,1% για τη μετατροπή μεταξύ lightning και on-chain Bitcoin. Η αποστολή σε άλλους λογαριασμούς Coinos είναι δωρεάν." + }, + "individuals": { + "question": "Ποιος μπορεί να το χρησιμοποιήσει;", + "answer": "Οποιοσδήποτε, οπουδήποτε στον κόσμο. Το Coinos είναι κατάλληλο τόσο για ιδιώτες όσο και για επιχειρήσεις." + }, + "compatibility": { + "question": "Χρειάζομαι κάποια ειδική συσκευή ή λογισμικό;", + "answer": "Όχι, το Coinos είναι μια εφαρμογή ιστού που λειτουργεί οπουδήποτε υπάρχει Ίντερνετ. Οι εμπόροι μπορεί να το χρησιμοποιούν σε tablet ή να παρέχουν έναν κώδικα QR για τους πελάτες να σκανάρουν με το τηλέφωνό τους." + }, + "safety": { + "question": "Πότε εξοφλούνται τα χρήματά μου;", + "answer": "Οι πληρωμές μέσω Lightning εξοφλούνται άμεσα. Μπορείτε να αναλάβετε το υπόλοιπό σας σε άλλο πορτοφόλι μέσα σε δευτερόλεπτα. Οι καταθέσεις on-chain χρειάζονται κατά μέσο όρο 5-10 λεπτά για επιβεβαίωση." + }, + "pos": { + "question": "Μπορεί το Coinos να ενσωματωθεί στο υπάρχον PoS μου;", + "answer": "Συνιστούμε να καταχωρίσετε τις πωλήσεις στο υπάρχον PoS σας ως συναλλαγές μετρητών." + }, + "accounting": { + "question": "Τι γίνεται με τη λογιστική;", + "answer": "Εμφανίζουμε όλες τις πληρωμές σε όρους του τοπικού σας νομίσματος και σας επιτρέπουμε να φιλτράρετε ανά ημερομηνία, ώστε να μπορείτε να δηλώνετε το εισόδημά σας κανονικά. Μπορείτε να εξαγάγετε το ιστορικό του λογαριασμού σας σε ένα αρχείο csv. Εάν πουλήσετε Bitcoin με κέρδος, μπορεί να επιθυμείτε να αναφέρετε τα κερδηφόρα κεφάλαια." + }, + "offramp": { + "question": "Μπορώ να μετατρέψω το Bitcoin που λαμβάνω στο τοπικό μου νόμισμα;", + "answer": "Μπορείτε να αναλάβετε τα χρήματά σας ανά πάσα στιγμή σε ένα χρηματιστήριο ή σε έναν μεσίτη για να πουλήσετε το Bitcoin σας και να λάβετε μια τραπεζική μεταφορά. Ίσως είμαστε πρόθυμοι να προσφέρουμε άμεσες εκκαθαρίσεις μέσω E-Transfer για επιλεγμένους καναδικούς εμπόρους." + }, + "bitcoin": { + "question": "Τι μπορώ να κάνω με το Bitcoin;", + "answer": "Ξοδέψτε το σε άλλους εμπόρους ή καλύψτε τις δαπάνες σας παίρνοντας το προσωπικό σας, τους προμηθευτές σας και τον ιδιοκτήτη να το δεχτούν." + } + }, + "footer": { + "company": "Εταιρία", + "followUs": "Ακολουθησε μας", + "github": "GitHub", + "linktree": "Linktree", + "jobs": "Linktree", + "support": "Υποστήριξη", + "telegram": "Telegram", + "twitter": "Twitter", + "documentation": "Τεκμηρίωση" + }, + "howItWorks": { + "header": "Πως δουλεύει", + "step1": { + "header": "1. Δημιουργήστε τον λογαριασμό σας", + "description": "Ένα όνομα χρήστη και κωδικός πρόσβασης είναι όλα όσα χρειάζεστε για να ξεκινήσετε." + }, + "step2": { + "header": "2. Ζητήστε πληρωμές", + "description": "Ότι θα κάνατε συνήθως, αλλά με το bitcoin." + }, + "step3": { + "header": "3. Αυτό είναι όλο!", + "description": "Ξοδέψτε το νόμισμά σας αμέσως ή αποθηκεύστε το και δείτε το να αυξάνεται, η επιλογή είναι δική σας." + } + }, + "invoice": { + "addTip": "Προσθήκη φιλοδωρήματος", + "addTipq": "Προσθήκη φιλοδωρήματος;", + "done": "Εγινε", + "paymentSuccessful": "Η πληρωμή ελήφθη!", + "invoice": "Τιμολόγιο", + "total": "Σύνολο", + "tip": "Φιλοδώρημα" + }, + "jobs": { + "header": "Δουλειές", + "subheader": "Τρέχουσες θέσεις εργασίας", + "pitch1": "«Η Coinos αναζητά ενεργά προγραμματιστές για να ενταχθούν στην αναπτυσσόμενη ομάδα μας (πλήρους ή μερικής απασχόλησης, απομακρυσμένη εργασία αποδεκτή) ", + "pitch2": "Είστε ενθουσιασμενοι που εργάζεστε σε bitcoin ανοιχτού κώδικα; Τότε ρίξτε μια ματιά στα αποθετήρια μας στο", + "github": "GitHub", + "pitch3": "Αν πιστεύετε ότι μπορείτε να ασκήσετε επιρροή, θα θέλαμε να σας ακούσουμε. Στείλτε μας το βιογραφικό σας στ" + }, + "landing": { + "header_before_icon": "Ο", + "header_with_icon": "πιο εύκολος", + "header_after_icon": "τρόπος για να ξεκινήσετε με το bitcoin ", + "subheader": "Ένα απλό αλλά ισχυρό πορτοφόλι ιστού για όλους. Δεν απαιτείται software ή hardware", + "info1": { + "title": "Άμεσες πληρωμές μέσω Lightning", + "description": "Διευκολύνουμε τους εμπόρους να λαμβάνουν πληρωμές λιανικής. Χωρίς αναμονή για τραπεζικούς διακανονισμούς ή ανησυχία για αντιστροφές χρεώσεων " + }, + "info2": { + "title": "Απλό στη χρήση", + "description1": "Απλώς πληκτρολογήστε ένα ποσό στο τοπικό σας νόμισμα, μοιραστείτε τον κωδικό QR και τελειώσατε.", + "description2": "Αναλαμβάνουμε τη δύσκολη δουλειά, ώστε να μπορείτε να επικεντρωθείτε στην επιχείρησή σας.", + "description3": "Έχετε πάντα τη δυνατότητα να αναλάβετε τη διαχείριση των χρημάτων σας, κάνοντας ανάληψη άμεσα και ανά πάσα στιγμή " + }, + "info3": { + "title": "Φτιάξτε το με τον δικό σας τρόπο!", + "description": "Προσαρμόστε την εμφάνιση και την αίσθηση της σελίδας του προφίλ σας ή διατηρήστε την ως προεπιλογή - εξαρτάται αποκλειστικά από εσάς " + } + }, + "login": { + "optional": "Προαιρετικό", + "token": "2FA Token", + "username": "Όνομα χρήστη", + "password": "Κωδικός πρόσβασης", + "email": "Διεύθυνση Ηλεκτρονικού Ταχυδρομείου", + "passwordRecommendation": "Συνιστούμε ένα μεγάλο, τυχαίο κωδικό πρόσβασης που δημιουργείται με έναν διαχειριστή κωδικών πρόσβασης ", + "forgotUserOrPassword": "Ξεχάσατε το όνομα χρήστη ή τον κωδικό πρόσβασης;", + "haveAccount": "Εχετε ήδη λογαριασμό;", + "noAccount": "Δεν έχετε λογαριασμό;", + "register": "Έγγραφη", + "signIn": "Σύνδεση", + "failed": "Η σύνδεση απέτυχ" + }, + "nav": { + "profile": "Προφίλ", + "startInSeconds": "Ξεκινήστε σε δευτερόλεπτα", + "account": "Λογαριασμός", + "signIn": "Σύνδεση", + "signOut": "Αποσύνδεση", + "dashboard": "Πίνακας Ελέγχου", + "settings": "Ρυθμίσεις", + "support": "Υποστήριξ" + }, + "news": { + "header": "Νέα", + "subheader": "Τελευταίες ενημερώσεις" + }, + "user": { + "bitcoin": "Bitcoin", + "lightning": "Lightning", + "invoice": "Τιμολόγιο", + "unfollow": "Κατάργηση παρακολούθησης", + "following": "Ακολουθείτε", + "follow": "Ακολουθήστε", + "followers": "Ακόλουθοι", + "remove": "Αφαίρεση", + "noNotes": "Δεν υπάρχουν σημειώσεις ακόμα", + "message": "Μήνυμα", + "messages": "Μηνύματα", + "feed": "Ροή", + "notes": "Σημειώσεις", + "pay": "Πληρωμή", + "scanToPayBTC": "Σάρωση για πληρωμή με bitcoin", + "this": "Αυτό", + "lightningAddress": "διεύθυνση lightning", + "dashboard": { + "go": "Πηγαίνετε", + "dashboard": "Πίνακας Ελέγχου", + "accountBalance": "Υπόλοιπο Λογαριασμού", + "bitcoinPrice": "Τιμή bitcoin:", + "conversionFeeWarning": "Θα χρεωθείτε με χρέωση ανάληψης 1% επί του ποσού ", + "send": "Αποστολή", + "receive": "Λήψη", + "withdraw": "Αποστολή", + "deposit": "Κατάθεση", + "voucher": "Κουπόν" + }, + "receive": { + "lessThan1BTCWarning": "Παρακαλώ εισάγετε ένα ποσό μικρότερο από 1 bitcoin ", + "showQR": "Εμφάνιση Q" + }, + "send": { + "contacts": "Επαφές", + "scan": "Σάρωση", + "paste": "Επικόλληση", + "next": "Επόμενο", + "placeholder": "Επικολλήστε μια διεύθυνση bitcoin, ένα τιμολόγιο lighting ή ένα όνομα χρήστη coino" + }, + "settings": { + "saveSettings": "Αποθήκευση ρυθμίσεων", + "bannerImage": "Εικόνα Banner", + "profileImage": "Εικόνα Προφίλ", + "oneTimeCode": "Εισαγάγετε κωδικό μίας χρήσης", + "accountId": "Μυστικός λογαριασμός", + "header": "Ρυθμίσεις", + "ACCOUNT": "ΛΟΓΑΡΙΑΣΜΟΣ", + "POINT_OF_SALE": "ΠΡΟΤΙΜΗΣΕΙΣ", + "SECURITY": "ΑΣΦΑΛΕΙΑ", + "locale": "Γλώσσα", + "username": "Όνομα χρήστη", + "email": "Ηλεκτρονικό Ταχυδρομεί", + "newPassword": "Αλλαγή κωδικού", + "about": "Σχετικά με", + "aboutPlaceholder": "Λίγα πράγματα για εσάς ή τη διεύθυνση και τις ώρες της επιχείρησής σας", + "emailNotifications": "Ειδοποιήσεις ηλεκτρονικού ταχυδρομείου", + "emailNotificationsDescription": "Λάβετε ειδοποιήσεις μέσω ηλεκτρονικού ταχυδρομείου όταν συμβαίνει μια αλλαγή στο λογαριασμό σας ή όταν οι άνθρωποι σας στέλνουν πληρωμές ", + "smsNotifications": "Ειδοποιήσεις SMS", + "smsNotificationsDescription": "Το Coinos θα σας στείλει ειδοποιήσεις κειμένου όταν αυτές είναι ενεργοποιημένες ", + "theme": "Χρωματικό θέμα", + "tipPrompt": "Προτροπή για φιλοδώρημα", + "tipPromptDescription": "Ρωτήστε τους ανθρώπους εάν θέλουν να προσθέσουν ένα φιλοδώρημα πριν πληρώσουν τα τιμολόγιά τους", + "displayName": "Εμφανιζόμενο όνομα", + "themeDescription": "Επιλέξτε από τα προεπιλεγμένα χρωματικά θέματα.", + "localCurrency": "Νόμισμα", + "btcUnit": "Εμφάνιση bitcoin σε:", + "bitcoin": "bitcoin", + "minutes": "λεπτά", + "hour": "ώρα", + "hours": "ώρες", + "satoshis": "Satoshis", + "securityPIN": "PIN Ασφαλείας", + "securityPINDescription": "Ορίστε μια καρφίτσα ασφαλείας που θα χρησιμοποιείται για το ξεκλείδωμα των ρυθμίσεών σας και για την αποστολή εξερχόμενων πληρωμών ", + "setPIN": "Ορισμός PIN", + "autoLock": "Αυτόματο κλείδωμα χρονοδιακόπτη", + "autoLockDescription": "Ο λογαριασμός σας θα μεταβεί σε ασφαλή λειτουργία, απαιτώντας από εσάς να εισαγάγετε ξανά τα διαπιστευτήριά σας μετά από μια καθορισμένη ώρα:", + "twofa": "Έλεγχος ταυτότητας 2 παραγόντων", + "twofaDescription": "Προσθέστε ένα επιπλέον επίπεδο ασφάλειας στη συσκευή σας ενεργοποιώντας τον έλεγχο ταυτότητας 2 παραγόντων ", + "twofaSetup": "Ρύθμιση 2FA", + "twofaDisable": "Απενεργοποίηση 2FA", + "select": "Επιλογή", + "verifyPIN": "Επαλήθευση PIN", + "enablePIN": "Ενεργοποίηση PIN", + "disablePIN": "Απενεργοποίηση PI" + }, + "support": { + "header": "Υποστήριξη", + "email": "Ηλεκτρονικό Ταχυδρομείο", + "accountName": "Ονομα λογαριασμού", + "message": "Μήνυμα", + "send": "Αποστολ" + } + }, + "payments": { + "amount": "Ποσό", + "date": "Ημερομηνία", + "id": "Ταυτότητα συναλλαγής", + "details": "Λεπτομέρειες Συναλλαγής", + "header": "Πληρωμές", + "preimage": "Απόδειξη πληρωμής", + "type": "Τύπος", + "time": "Χρόνος", + "sent": "Απεστάλει", + "received": "Παραλήφθηκε", + "empty": "Αποστολή", + "send": "Επόμενο", + "next": "Επιλογή αποστολέα", + "sendersChoice": "Αποστολή σε", + "sendTo": "προς", + "to": "προς", + "pay": "Πληρωμή", + "requesting": "απαιτείται", + "sentInvoiceTo": "Εστάλει τιμολόγιο σε", + "sentRequestTo": "Αίτημα εστάλει!", + "pleasePay": "Παρακαλώ πληρώστε", + "day": "Ημέρα", + "week": "Εβδομάδ", + "month": "Μήνας", + "all": "Ολα", + "export": "Εξαγωγή", + "invoice": "Τιμολόγιο", + "readyToPay": "είναι έτοιμο για πληρωμή", + "describeOrder": "Χρησιμοποιήστε αυτό το πεδίο για να περιγράψετε την παραγγελία σας", + "requestAnInvoice": "Αίτημα Τιμολογίου", + "submit": "Υποβολή", + "isRequesting": "απαιτείται", + "invoiceFrom": "Τιμολόγιο από", + "awaiting": "Αναμονή", + "tapAnywhere": "Πατήστε οπουδήποτε για να συνεχίσετε", + "from": "από", + "redeem": "Εξαργύρωση", + "redeemed": "Εξαργυρώθηκε", + "redeemedBy": "Εξαργυρώθηκε από", + "sendMessage": "Γράψτε ένα μήνυμα..." + } } diff --git a/src/locales/en.json b/src/locales/en.json index c0efade3..db25b3fb 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1,48 +1,48 @@ { - "404": { - "404": "404", - "pageNotFound": "Page not found." - }, - "about": { - "header": "About", - "team": { - "header": "The Team", - "adam": { - "name": "Adam Soltys", - "position": "Founder and CTO", - "bio": "Adam is a software engineer who believes that the adoption of sound money will drive global prosperity. He lives in North Vancouver with his wife, daughter, and son." - }, - "cole": { - "name": "Cole", - "position": "Business Development", - "bio": "Cole is a driven individual with a passion for freedom. He came across bitcoin in 2016 and studied it in depth. Now he wants to introduce the lightning network to as many users as possible." - } - }, - "locations": { - "header": "Locations", - "description": "Here are some businesses that are accepting bitcoin with Coinos. We hope that yours will be next!" - } - }, - "error": { - "header": "Error", - "message": "Please try again or contact support.", - "signIn": "Please sign in to continue.", + "404": { + "404": "404", + "pageNotFound": "Page not found." + }, + "about": { + "header": "About", + "team": { + "header": "The Team", + "adam": { + "name": "Adam Soltys", + "position": "Founder and CTO", + "bio": "Adam is a software engineer who believes that the adoption of sound money will drive global prosperity. He lives in North Vancouver with his wife, daughter, and son." + }, + "cole": { + "name": "Cole", + "position": "Business Development", + "bio": "Cole is a driven individual with a passion for freedom. He came across bitcoin in 2016 and studied it in depth. Now he wants to introduce the lightning network to as many users as possible." + } + }, + "locations": { + "header": "Locations", + "description": "Here are some businesses that are accepting bitcoin with Coinos. We hope that yours will be next!" + } + }, + "error": { + "header": "Error", + "message": "Please try again or contact support.", + "signIn": "Please sign in to continue.", "unrecognizedInput": "Unrecognized input" - }, - "faq": { - "header": "FAQ", - "cost": { - "question": "What does it cost?", - "answer": "Receiving payments into your account is free. Withdrawals to external wallets incur a small fee that varies depending on network conditions." - }, - "compatibility": { - "question": "Do I need any special device or software?", - "answer": "No, Coinos works anywhere that has Internet. Merchants might use it on a tablet or put up a QR code for customers to scan with their phone." - }, - "safety": { - "question": "When do my funds settle?", - "answer": "Lightning payments are settled instantly. You can withdraw your balance to another wallet within seconds. On-chain deposits take 5-10 minutes to confirm on average." - }, + }, + "faq": { + "header": "FAQ", + "cost": { + "question": "What does it cost?", + "answer": "Receiving payments into your account is free. Withdrawals to external wallets incur a small fee that varies depending on network conditions." + }, + "compatibility": { + "question": "Do I need any special device or software?", + "answer": "No, Coinos works anywhere that has Internet. Merchants might use it on a tablet or put up a QR code for customers to scan with their phone." + }, + "safety": { + "question": "When do my funds settle?", + "answer": "Lightning payments are settled instantly. You can withdraw your balance to another wallet within seconds. On-chain deposits take 5-10 minutes to confirm on average." + }, "pos": { "question": "Can Coinos integrate with my existing PoS?", "answer": "We recommend entering sales into your existing PoS as cash transactions." @@ -51,218 +51,218 @@ "question": "What can I do with Bitcoin?", "answer": "Spend it at other merchants or pay your expenses with it by getting your staff, suppliers and landlord to take it." } - }, - "footer": { - "company": "Company", - "followUs": "Follow Us", - "github": "GitHub", - "linktree": "Linktree", - "jobs": "Jobs", - "support": "Support", - "telegram": "Telegram", - "twitter": "Twitter", - "documentation": "Documentation" - }, - "howItWorks": { - "header": "How it works", - "step1": { - "header": "1. Create your account", - "description": "A username and password is all you need to get started." - }, - "step2": { - "header": "2. Ask for payments", - "description": "The same as you normally would, but with bitcoin." - }, - "step3": { - "header": "3. That's all!", - "description": "Spend your coin immediately or watch it grow in value." - } - }, - "invoice": { - "addTip": "Add a tip", - "addTipq": "Add a tip?", - "done": "Done", - "paymentSuccessful": "Payment received!", - "invoice": "Invoice", - "total": "Total", - "tip": "Tip" - }, - "jobs": { - "header": "Jobs", - "subheader": "Current Openings", - "pitch1": "Coinos is actively looking for developers to join our growing team (full-time or part-time, remote working OK).", - "pitch2": "Excited about working on open source bitcoin? Then take a look at our repositories on", - "github": "GitHub", - "pitch3": "If you think you can make an impact, we'd love to hear from you. Send us your resume to" - }, - "landing": { - "header_before_icon": "The", - "header_with_icon": "easiest", - "header_after_icon": "way to get started with bitcoin.", - "subheader": "A free web wallet and payment page for everyone.", - "info1": { - "title": "Instant payments via Lightning", - "description": "We make it easy for merchants to receive retail payments. No waiting for bank settlements or worrying about chargebacks. Instant settlement, in seconds. Say goodbye to 3% card fees." - }, - "info2": { - "title": "Simple to use", - "description1": "Just enter an amount in your local currency, share the QR code and you’re done.", - "description2": "We do all the heavy lifting so you can focus on your business.", - "description3": "You always have the option of taking custody of your funds by withdrawing instantly at any time." - }, - "info3": { - "title": "Brand it your way!", - "description": "Customize your profile page look and feel, or keep it on default - it's totally up to you." - } - }, - "login": { - "optional": "Optional", - "token": "2FA Token", - "username": "Username", - "password": "Password", - "email": "Email", - "passwordRecommendation": "We recommend using a long, random password if you intend to keep funds in your account.", - "forgotUserOrPassword": "Forgot Username or Password?", - "haveAccount": "Already have an account?", - "noAccount": "Don't have an account?", - "register": "Register", - "signIn": "Sign In", - "failed": "Login failed" - }, - "nav": { + }, + "footer": { + "company": "Company", + "followUs": "Follow Us", + "github": "GitHub", + "linktree": "Linktree", + "jobs": "Jobs", + "support": "Support", + "telegram": "Telegram", + "twitter": "Twitter", + "documentation": "Documentation" + }, + "howItWorks": { + "header": "How it works", + "step1": { + "header": "1. Create your account", + "description": "A username and password is all you need to get started." + }, + "step2": { + "header": "2. Ask for payments", + "description": "The same as you normally would, but with bitcoin." + }, + "step3": { + "header": "3. That's all!", + "description": "Spend your coin immediately or watch it grow in value." + } + }, + "invoice": { + "addTip": "Add a tip", + "addTipq": "Add a tip?", + "done": "Done", + "paymentSuccessful": "Payment received!", + "invoice": "Invoice", + "total": "Total", + "tip": "Tip" + }, + "jobs": { + "header": "Jobs", + "subheader": "Current Openings", + "pitch1": "Coinos is actively looking for developers to join our growing team (full-time or part-time, remote working OK).", + "pitch2": "Excited about working on open source bitcoin? Then take a look at our repositories on", + "github": "GitHub", + "pitch3": "If you think you can make an impact, we'd love to hear from you. Send us your resume to" + }, + "landing": { + "header_before_icon": "The", + "header_with_icon": "easiest", + "header_after_icon": "way to get started with bitcoin.", + "subheader": "A free web wallet and payment page for everyone.", + "info1": { + "title": "Instant payments via Lightning", + "description": "We make it easy for merchants to receive retail payments. No waiting for bank settlements or worrying about chargebacks. Instant settlement, in seconds. Say goodbye to 3% card fees." + }, + "info2": { + "title": "Simple to use", + "description1": "Just enter an amount in your local currency, share the QR code and you’re done.", + "description2": "We do all the heavy lifting so you can focus on your business.", + "description3": "You always have the option of taking custody of your funds by withdrawing instantly at any time." + }, + "info3": { + "title": "Brand it your way!", + "description": "Customize your profile page look and feel, or keep it on default - it's totally up to you." + } + }, + "login": { + "optional": "Optional", + "token": "2FA Token", + "username": "Username", + "password": "Password", + "email": "Email", + "passwordRecommendation": "We recommend using a long, random password if you intend to keep funds in your account.", + "forgotUserOrPassword": "Forgot Username or Password?", + "haveAccount": "Already have an account?", + "noAccount": "Don't have an account?", + "register": "Register", + "signIn": "Sign In", + "failed": "Login failed" + }, + "nav": { "about": "About", "docs": "Docs", "messages": "Messages", "funds": "Funds", "map": "Map", "merch": "Swag", - "profile": "Profile", - "startInSeconds": "Start in Seconds", - "account": "Account", - "signIn": "Sign In", - "signOut": "Sign out", - "dashboard": "Dashboard", - "settings": "Settings", - "support": "Support" - }, - "news": { - "header": "News", - "subheader": "Latest Updates" - }, - "user": { + "profile": "Profile", + "startInSeconds": "Start in Seconds", + "account": "Account", + "signIn": "Sign In", + "signOut": "Sign out", + "dashboard": "Dashboard", + "settings": "Settings", + "support": "Support" + }, + "news": { + "header": "News", + "subheader": "Latest Updates" + }, + "user": { "profilePage": "Profile page", "nostrPubkey": "Nostr pubkey", "requestPayment": "Request Payment", "welcome": "Welcome! Your account balance is empty. You can start by making a deposit or by asking someone to pay you.", "showBalance": "Show Balance", - "bitcoin": "Bitcoin", - "lightning": "Lightning", - "invoice": "Invoice", - "unfollow": "Unfollow", - "following": "Following", - "follow": "Follow", - "followers": "Followers", - "remove": "Remove", - "noNotes": "No notes yet", - "message": "Message", - "messages": "Messages", - "feed": "Feed", - "notes": "Notes", - "pay": "Pay", - "scanToPayBTC": "Scan to pay with bitcoin", - "this": "This", + "bitcoin": "Bitcoin", + "lightning": "Lightning", + "invoice": "Invoice", + "unfollow": "Unfollow", + "following": "Following", + "follow": "Follow", + "followers": "Followers", + "remove": "Remove", + "noNotes": "No notes yet", + "message": "Message", + "messages": "Messages", + "feed": "Feed", + "notes": "Notes", + "pay": "Pay", + "scanToPayBTC": "Scan to pay with bitcoin", + "this": "This", "lightningAddress": "Lightning address", - "dashboard": { - "go": "Go", - "dashboard": "Dashboard", - "accountBalance": "Account Balance", - "conversionFeeWarning": "You will be charged a 1% withdrawal fee on the amount.", - "fetchingRate": "fetching rate...", - "send": "Send", - "receive": "Receive", - "withdraw": "Send", - "deposit": "Deposit", - "voucher": "Voucher" - }, - "receive": { - "lessThan1BTCWarning": "Please enter an amount less than 1 bitcoin.", - "showQR": "Show QR" - }, - "send": { + "dashboard": { + "go": "Go", + "dashboard": "Dashboard", + "accountBalance": "Account Balance", + "conversionFeeWarning": "You will be charged a 1% withdrawal fee on the amount.", + "fetchingRate": "fetching rate...", + "send": "Send", + "receive": "Receive", + "withdraw": "Send", + "deposit": "Deposit", + "voucher": "Voucher" + }, + "receive": { + "lessThan1BTCWarning": "Please enter an amount less than 1 bitcoin.", + "showQR": "Show QR" + }, + "send": { "makeQR": "QR", - "contacts": "Contacts", - "scan": "Scan", - "paste": "Paste", - "next": "Next", - "placeholder": "Paste a bitcoin address, lightning invoice or coinos username" - }, - "settings": { + "contacts": "Contacts", + "scan": "Scan", + "paste": "Paste", + "next": "Next", + "placeholder": "Paste a bitcoin address, lightning invoice or coinos username" + }, + "settings": { "seedPhrase": "Seed phrase", "nostrKeys": "Nostr key", - "nostrDescription": "Your nostr key is used to sign and decrypt messages, keep it safe or someone may be able to impersonate you.", + "nostrDescription": "Your nostr key is used to sign and decrypt messages, keep it safe or someone may be able to impersonate you.", "seedDescription": "This 12 word seed phrase is used to derive your nostr private key and your self-custodial wallet keys which determine your auto-withdrawal addresses. Back it up and don't let anyone see it.", "emailDescription": "For password resets and notifications", "hideNsec": "Hide nostr secret key", "revealNsec": "Reveal nostr secret key", "hideSeed": "Hide seed phrase", "revealSeed": "Reveal seed phrase", - "saveSettings": "Save Settings", - "bannerImage": "Banner Image", - "profileImage": "Profile Image", - "oneTimeCode": "Enter one-time code", - "accountId": "Account Secret", - "header": "Settings", - "ACCOUNT": "ACCOUNT", - "POINT_OF_SALE": "PREFERENCES", - "SECURITY": "SECURITY", - "locale": "Language", - "username": "Username", - "email": "Email", - "newPassword": "Change Password", - "about": "About", - "aboutPlaceholder": "A little something about you or your business address and hours", - "emailNotifications": "Email Notifications", - "emailNotificationsDescription": "Get notified via email when a change occurs on your account or when people send you payments.", - "smsNotifications": "SMS Notifications", - "smsNotificationsDescription": "Coinos will send you text notifications when this is enabled.", - "theme": "Color Theme", - "tipPrompt": "Prompt for tips", - "tipPromptDescription": "Ask people if they want to add a tip before paying their invoices", - "displayName": "Display name", - "themeDescription": "Select from the default color themes.", - "localCurrency": "Currency", - "btcUnit": "Display bitcoin in:", - "bitcoin": "bitcoin", - "minutes": "minutes", - "hour": "hour", - "hours": "hours", - "satoshis": "Satoshis", - "securityPIN": "Security PIN", - "securityPINDescription": "Set a security pin that will be used to unlock your settings and to send outgoing payments.", - "setPIN": "Set PIN", - "autoLock": "Timeout", - "autoLockDescription": "Time after which your PIN needs to be re-entered:", - "twofa": "2-Factor Authentication", - "twofaDescription": "Add an extra level of security to your device by enabling 2-factor authentication.", - "twofaSetup": "Set up 2FA", - "twofaDisable": "Disable 2FA", - "select": "Select", - "verifyPIN": "Verify PIN", - "enablePIN": "Enable PIN", - "disablePIN": "Disable PIN", + "saveSettings": "Save Settings", + "bannerImage": "Banner Image", + "profileImage": "Profile Image", + "oneTimeCode": "Enter one-time code", + "accountId": "Account Secret", + "header": "Settings", + "ACCOUNT": "ACCOUNT", + "POINT_OF_SALE": "PREFERENCES", + "SECURITY": "SECURITY", + "locale": "Language", + "username": "Username", + "email": "Email", + "newPassword": "Change Password", + "about": "About", + "aboutPlaceholder": "A little something about you or your business address and hours", + "emailNotifications": "Email Notifications", + "emailNotificationsDescription": "Get notified via email when a change occurs on your account or when people send you payments.", + "smsNotifications": "SMS Notifications", + "smsNotificationsDescription": "Coinos will send you text notifications when this is enabled.", + "theme": "Color Theme", + "tipPrompt": "Prompt for tips", + "tipPromptDescription": "Ask people if they want to add a tip before paying their invoices", + "displayName": "Display name", + "themeDescription": "Select from the default color themes.", + "localCurrency": "Currency", + "btcUnit": "Display bitcoin in:", + "bitcoin": "bitcoin", + "minutes": "minutes", + "hour": "hour", + "hours": "hours", + "satoshis": "Satoshis", + "securityPIN": "Security PIN", + "securityPINDescription": "Set a security pin that will be used to unlock your settings and to send outgoing payments.", + "setPIN": "Set PIN", + "autoLock": "Timeout", + "autoLockDescription": "Time after which your PIN needs to be re-entered:", + "twofa": "2-Factor Authentication", + "twofaDescription": "Add an extra level of security to your device by enabling 2-factor authentication.", + "twofaSetup": "Set up 2FA", + "twofaDisable": "Disable 2FA", + "select": "Select", + "verifyPIN": "Verify PIN", + "enablePIN": "Enable PIN", + "disablePIN": "Disable PIN", "publishNip5": "Publish nostr identity", "nip5a": "Use", "nip5b": "as your publicly verified nostr address" - }, - "support": { - "header": "Support", - "email": "Email", - "accountName": "Account Name", - "message": "Message", - "send": "Send" - } - }, - "payments": { + }, + "support": { + "header": "Support", + "email": "Email", + "accountName": "Account Name", + "message": "Message", + "send": "Send" + } + }, + "payments": { "create": "Create", "fund": "Fund", "addFunds": "Add Funds", @@ -277,46 +277,46 @@ "total": "Total", "gainLoss": "Gain/Loss", "presentValue": "Present Value", - "amount": "Amount", - "date": "Date", - "id": "Transaction ID", - "details": "Transaction Details", - "header": "Payments", - "preimage": "Proof of payment", - "type": "Type", - "time": "Time", - "sent": "Sent", - "received": "Received", - "empty": "No transactions yet.", - "send": "Send", - "next": "Next", - "sendTo": "Send to", - "to": "to", - "pay": "Pay", - "requesting": "is requesting", - "sentInvoiceTo": "Sent invoice to", - "sentRequestTo": "Request sent!", - "pleasePay": "Please pay", - "day": "Day", - "week": "Week", - "month": "Month", - "all": "All", - "export": "Export", - "invoice": "Invoice", - "readyToPay": "is ready to pay", - "describeOrder": "Use this field to describe your order", - "requestAnInvoice": "Request Invoice", - "submit": "Submit", - "isRequesting": "is requesting", - "invoiceFrom": "Invoice from", - "awaiting": "Awaiting", - "tapAnywhere": "Tap anywhere to continue", - "from": "from", - "redeem": "Redeem", - "redeemed": "Redeemed", - "redeemedBy": "Redeemed by", - "sendMessage": "Write a message...", + "amount": "Amount", + "date": "Date", + "id": "Transaction ID", + "details": "Transaction Details", + "header": "Payments", + "preimage": "Proof of payment", + "type": "Type", + "time": "Time", + "sent": "Sent", + "received": "Received", + "empty": "No transactions yet.", + "send": "Send", + "next": "Next", + "sendTo": "Send to", + "to": "to", + "pay": "Pay", + "requesting": "is requesting", + "sentInvoiceTo": "Sent invoice to", + "sentRequestTo": "Request sent!", + "pleasePay": "Please pay", + "day": "Day", + "week": "Week", + "month": "Month", + "all": "All", + "export": "Export", + "invoice": "Invoice", + "readyToPay": "is ready to pay", + "describeOrder": "Use this field to describe your order", + "requestAnInvoice": "Request Invoice", + "submit": "Submit", + "isRequesting": "is requesting", + "invoiceFrom": "Invoice from", + "awaiting": "Awaiting", + "tapAnywhere": "Tap anywhere to continue", + "from": "from", + "redeem": "Redeem", + "redeemed": "Redeemed", + "redeemedBy": "Redeemed by", + "sendMessage": "Write a message...", "fundsClaimed": "Funds claimed!", "print": "Print" - } + } } diff --git a/src/locales/es.json b/src/locales/es.json index e506696c..29508b27 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -1,36 +1,36 @@ { - "404": { - "404": "404", - "pageNotFound": "Pagina no encontrada." - }, - "about": { - "header": "Sobre Nosotros", - "p1": "Coinos es una aplicación de pagos en bitcoin de código abierto. Este desarrollo comenzó en septiembre de 2012 en Vancouver como una forma de proporcionar a los comerciantes locales una forma conveniente de aceptar pagos con bitcoin.", - "p2": "Descarga el código fuente para ejecutar una instancia y conectarla a tu propio nodo bitcoin y lightning. Te ayudaremos a convertirte en un excelente custodio y proveedor de pagos para tu comunidad.", - "p3": "Las transferencias de bitcoin entre usuarios en nuestro servidor se realizan off-chain, por lo que son completamente gratuitas, instantáneas e ilimitadas.", - "team": { - "header": "Nuestro Equipo", - "adam": { - "name": "Adam Soltys", - "position": "Director de Tecnología", - "bio": "Adam es un desarrollador web y un entusiasta de bitcoin que cree que el dinero sólido es clave para aumentar la prosperidad global. Vive en la ciudad de North Vancouver con su esposa Chelsea y sus dos hijos." - }, - "cole": { - "name": "Cole", - "position": "Profesional de Bitcoin", - "bio": "Como profesional certificado de Bitcoin, Cole es una persona motivada y apasionada por la libertad. Conoció el bitcoin en 2016 y lo estudió en profundidad. Ahora quiere presentarle la Red Lightning a tantos usuarios como sea posible." - } - }, - "locations": { - "header": "Locaciones", - "description": "Aquí algunas empresas que aceptan bitcoin con Coinos. ¡Esperamos que el tuyo sea el próximo!" - } - }, - "error": { - "header": "Error", - "message": "Vuelva a intentarlo o póngase en contacto con el soporte.", - "signIn": "Por favor inicie sesión para continuar." - }, + "404": { + "404": "404", + "pageNotFound": "Pagina no encontrada." + }, + "about": { + "header": "Sobre Nosotros", + "p1": "Coinos es una aplicación de pagos en bitcoin de código abierto. Este desarrollo comenzó en septiembre de 2012 en Vancouver como una forma de proporcionar a los comerciantes locales una forma conveniente de aceptar pagos con bitcoin.", + "p2": "Descarga el código fuente para ejecutar una instancia y conectarla a tu propio nodo bitcoin y lightning. Te ayudaremos a convertirte en un excelente custodio y proveedor de pagos para tu comunidad.", + "p3": "Las transferencias de bitcoin entre usuarios en nuestro servidor se realizan off-chain, por lo que son completamente gratuitas, instantáneas e ilimitadas.", + "team": { + "header": "Nuestro Equipo", + "adam": { + "name": "Adam Soltys", + "position": "Director de Tecnología", + "bio": "Adam es un desarrollador web y un entusiasta de bitcoin que cree que el dinero sólido es clave para aumentar la prosperidad global. Vive en la ciudad de North Vancouver con su esposa Chelsea y sus dos hijos." + }, + "cole": { + "name": "Cole", + "position": "Profesional de Bitcoin", + "bio": "Como profesional certificado de Bitcoin, Cole es una persona motivada y apasionada por la libertad. Conoció el bitcoin en 2016 y lo estudió en profundidad. Ahora quiere presentarle la Red Lightning a tantos usuarios como sea posible." + } + }, + "locations": { + "header": "Locaciones", + "description": "Aquí algunas empresas que aceptan bitcoin con Coinos. ¡Esperamos que el tuyo sea el próximo!" + } + }, + "error": { + "header": "Error", + "message": "Vuelva a intentarlo o póngase en contacto con el soporte.", + "signIn": "Por favor inicie sesión para continuar." + }, "faq": { "header": "FAQ", "why": { @@ -70,222 +70,222 @@ "answer": "Gástalo en otros comerciantes o paga tus gastos con él al conseguir que tu personal, proveedores y arrendador lo acepten." } }, - "footer": { - "company": "Empresa", - "followUs": "Síguenos", - "github": "GitHub", - "jobs": "Trabaje con nosotros", - "support": "Soporte", - "telegram": "Telegram", - "twitter": "Twitter", - "news": "Noticias" - }, - "howItWorks": { - "header": "Cómo funciona", - "step1": { - "header": "1. Crea tu cuenta", - "description": "Un nombre de usuario y una contraseña es todo lo que necesita para comenzar." - }, - "step2": { - "header": "2. Cobra tus pagos", - "description": "Lo mismo que harías normalmente, pero con bitcoin." - }, - "step3": { - "header": "3. ¡Eso es todo!", - "description": "Haz pagos con otros comerciantes o guarda tu bitcoin para días difíciles, la elección es tuya." - } - }, - "invoice": { - "addTip": "Añadir propina", - "addTipq": "Añadir propina?", - "done": "Hecho", - "paymentSuccessful": "¡Pago recibido!", - "invoice": "Factura", - "total": "Total", - "tip": "Propina" - }, - "jobs": { - "header": "Trabaje con nosotros", - "subheader": "Ofertas de Trabajo", - "pitch1": "Coinos está activamente buscando desarrolladores para unirse a nuestro equipo en crecimiento (tiempo completo o medio tiempo, trabajo remoto OK).", - "pitch2": "¿Emocionado por trabajar en bitcoin de código abierto? Entonces eche un vistazo a nuestros repositorios en", - "github": "GitHub", - "pitch3": "Si crees que puedes generar impacto, nos encantaría saber de ti. Envíanos tu currículum a" - }, - "landing": { - "header_before_icon": "La", - "header_with_icon": "manera", - "header_after_icon": "más fácil de aceptar bitcoins en tu tienda.", - "subheader": "Aumenta tu número de clientes e ingresos al aceptar bitcoin con una aplicación web fácil de usar. No requiere software ni hardware.", - "info1": { - "title": "Pagos instantáneos a través de Lightning", - "description": "El momento en que su cliente envía un pago, este llega inmediatamente. Sin esperar liquidaciones bancarias ni preocuparse por cargos extras. Liquidación instantánea, en segundos. Diga adiós a los cargos del 3% de las tarifas en tarjetas de crédito." - }, - "info2": { - "title": "Fácil de usar", - "description1": "Solo ingrese una cantidad en su moneda local, comparta el código QR y listo.", - "description2": "Hacemos todo el trabajo pesado para que pueda concentrarse en su negocio.", - "description3": "Siempre tiene la opción de tomar la custodia de sus fondos al retirarlos instantáneamente en cualquier momento." - }, - "info3": { - "title": "Maneja tu marca a tu manera!", - "description": "Personalice la apariencia y el estilo del punto de venta de tu negocio a tu gusto: depende totalmente de ti." - } - }, - "login": { - "optional": "Opcional", - "token": "Token 2FA", - "username": "Usuario", - "password": "Contraseña", - "email": "Correo", - "passwordRecommendation": "Recomendamos una contraseña larga y aleatoria generada con un administrador de contraseñas.", - "forgotUserOrPassword": "¿Olvidaste tu nombre de usuario o contraseña?", - "haveAccount": "¿Ya tienes una cuenta?", - "noAccount": "¿No tienes una cuenta?", - "register": "Registro", - "signIn": "Registrarse", - "failed": "Fallo en Inicio" - }, - "nav": { - "profile": "Perfil", - "startInSeconds": "Comience en segundos", - "account": "Cuenta", - "signIn": "Registrarse", - "signOut": "Desconectar", - "dashboard": "Panel", - "settings": "Ajustes", - "support": "Soporte" - }, - "newSiteHeader": "Acompañanos en nuestra <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">launch party</a> en Vancouver el 26 de Octubre! Buscando la version anterior de coinos? Visita <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", - "news": { - "header": "Noticias", - "subheader": "Últimas Actualizaciones", - "update1": "" - }, - "user": { + "footer": { + "company": "Empresa", + "followUs": "Síguenos", + "github": "GitHub", + "jobs": "Trabaje con nosotros", + "support": "Soporte", + "telegram": "Telegram", + "twitter": "Twitter", + "news": "Noticias" + }, + "howItWorks": { + "header": "Cómo funciona", + "step1": { + "header": "1. Crea tu cuenta", + "description": "Un nombre de usuario y una contraseña es todo lo que necesita para comenzar." + }, + "step2": { + "header": "2. Cobra tus pagos", + "description": "Lo mismo que harías normalmente, pero con bitcoin." + }, + "step3": { + "header": "3. ¡Eso es todo!", + "description": "Haz pagos con otros comerciantes o guarda tu bitcoin para días difíciles, la elección es tuya." + } + }, + "invoice": { + "addTip": "Añadir propina", + "addTipq": "Añadir propina?", + "done": "Hecho", + "paymentSuccessful": "¡Pago recibido!", + "invoice": "Factura", + "total": "Total", + "tip": "Propina" + }, + "jobs": { + "header": "Trabaje con nosotros", + "subheader": "Ofertas de Trabajo", + "pitch1": "Coinos está activamente buscando desarrolladores para unirse a nuestro equipo en crecimiento (tiempo completo o medio tiempo, trabajo remoto OK).", + "pitch2": "¿Emocionado por trabajar en bitcoin de código abierto? Entonces eche un vistazo a nuestros repositorios en", + "github": "GitHub", + "pitch3": "Si crees que puedes generar impacto, nos encantaría saber de ti. Envíanos tu currículum a" + }, + "landing": { + "header_before_icon": "La", + "header_with_icon": "manera", + "header_after_icon": "más fácil de aceptar bitcoins en tu tienda.", + "subheader": "Aumenta tu número de clientes e ingresos al aceptar bitcoin con una aplicación web fácil de usar. No requiere software ni hardware.", + "info1": { + "title": "Pagos instantáneos a través de Lightning", + "description": "El momento en que su cliente envía un pago, este llega inmediatamente. Sin esperar liquidaciones bancarias ni preocuparse por cargos extras. Liquidación instantánea, en segundos. Diga adiós a los cargos del 3% de las tarifas en tarjetas de crédito." + }, + "info2": { + "title": "Fácil de usar", + "description1": "Solo ingrese una cantidad en su moneda local, comparta el código QR y listo.", + "description2": "Hacemos todo el trabajo pesado para que pueda concentrarse en su negocio.", + "description3": "Siempre tiene la opción de tomar la custodia de sus fondos al retirarlos instantáneamente en cualquier momento." + }, + "info3": { + "title": "Maneja tu marca a tu manera!", + "description": "Personalice la apariencia y el estilo del punto de venta de tu negocio a tu gusto: depende totalmente de ti." + } + }, + "login": { + "optional": "Opcional", + "token": "Token 2FA", + "username": "Usuario", + "password": "Contraseña", + "email": "Correo", + "passwordRecommendation": "Recomendamos una contraseña larga y aleatoria generada con un administrador de contraseñas.", + "forgotUserOrPassword": "¿Olvidaste tu nombre de usuario o contraseña?", + "haveAccount": "¿Ya tienes una cuenta?", + "noAccount": "¿No tienes una cuenta?", + "register": "Registro", + "signIn": "Registrarse", + "failed": "Fallo en Inicio" + }, + "nav": { + "profile": "Perfil", + "startInSeconds": "Comience en segundos", + "account": "Cuenta", + "signIn": "Registrarse", + "signOut": "Desconectar", + "dashboard": "Panel", + "settings": "Ajustes", + "support": "Soporte" + }, + "newSiteHeader": "Acompañanos en nuestra <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">launch party</a> en Vancouver el 26 de Octubre! Buscando la version anterior de coinos? Visita <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", + "news": { + "header": "Noticias", + "subheader": "Últimas Actualizaciones", + "update1": "" + }, + "user": { "requestPayment": "Solicitar pago", "welcome": "¡Bienvenido! El saldo de su cuenta está vacío. Puede comenzar haciendo un depósito o pidiéndole a alguien que le pague.", "showBalance": "Mostrar saldo", - "bitcoin": "Bitcoin", - "lightning": "Lightning", - "invoice": "Factura", - "unfollow": "Dejar de seguir", - "following": "Siguiendo", - "followers": "Seguidores", - "follow": "Seguir", - "remove": "Eliminar", - "scanToPayBTC": "Escanear para pagar con bitcoin", - "this": "Esta", - "lightningAddress": "Dirección lightning", - "dashboard": { - "go": "Ir", - "dashboard": "Panel", - "accountBalance": "Saldo de la cuenta", - "bitcoinPrice": "Precio de Bitcoin:", - "conversionFeeWarning": "Se le cobrará una tarifa de retiro del 1% sobre el monto.", - "dataFromBinance": "Datos de <span class=\"text-black\">Binance</span>.", - "fetchingRate": "calculando tasa...", - "optionalNote": "Nota (Opcional)", - "send": "Enviar", - "receive": "Recibir", - "withdraw": "Retirar", - "deposit": "Depositar", - "voucher": "Comprobante" - }, - "receive": { - "lessThan1BTCWarning": "Introduzca una cantidad inferior a 1 bitcoin.", - "showQR": "Mostrar código QR" - }, - "send": { - "contacts": "Contactos", - "scan": "Escanear", - "paste": "Pegar", - "next": "Siguiente", - "placeholder": "Asigne una dirección de bitcoin, una factura lightning o un usuario de coinos" - }, - "settings": { - "saveSettings": "Guardar ajustes", - "tipPrompt": "Solicitar propinas", - "tipPromptDescription": "Pregunta a las personas si quieren agregar una propina antes de pagar sus facturas", - "bannerImage": "Imagen del Banner", - "profileImage": "Imagen de perfil", - "oneTimeCode": "Ingrese el código de un solo uso", - "accountId": "Id Cuenta", - "header": "Ajustes", - "ACCOUNT": "CUENTA", - "POINT_OF_SALE": "PUNTO DE VENTA", - "SECURITY": "SEGURIDAD", - "locale": "Idioma", - "username": "Usuario", - "email": "Correo", - "newPassword": "Nueva contraseña", - "businessAddress": "Dirección Negocio", - "emailNotifications": "Notificaciones de Correo Electrónico", - "emailNotificationsDescription": "Reciba notificaciones por correo electrónico cuando se produzca un cambio en su cuenta o cuando las personas le envíen pagos.", - "smsNotifications": "Notificaciones SMS", - "smsNotificationsDescription": "Coinos enviará notificaciones de texto cuando esto esté habilitado.", - "theme": "Color Tema", - "themeDescription": "Seleccione entre los temas de color predeterminados.", - "localCurrency": "Moneda Local", - "btcUnit": "Ver bitcoin en:", - "bitcoin": "bitcoin", - "satoshis": "Satoshis", - "securityPIN": "PIN de Seguridad", - "securityPINDescription": "Establezca un pin de seguridad para desbloquear la configuración y enviar pagos.", - "setPIN": "Establecer PIN", - "autoLock": "Temporizador de bloqueo automático", - "autoLockDescription": "Su cuenta entrará en modo seguro automáticamente después del tiempo especificado:", - "twofa": "Autenticación de 2 factores", - "twofaDescription": "Agregue una capa extra de seguridad a su dispositivo habilitando la autenticación de 2 factores.", - "twofaSetup": "Configurar 2FA", - "twofaDisable": "Deshabilitar 2FA", - "select": "Seleccionar", - "verifyPIN": "Verificar PIN", - "enablePIN": "Habilitar PIN", - "disablePIN": "Deshabilitar PIN" - }, - "support": { - "header": "Soporte", - "email": "Correo", - "accountName": "Nombre Cuenta", - "message": "Mensaje", - "send": "Enviar" - } - }, - "payments": { - "header": "Transacciones", - "AMOUNT": "CANTIDAD", - "TYPE": "TIPO", - "TIME": "HORA", - "sent": "Enviado", - "received": "Recibido", - "empty": "Aún no hay transacciones.", - "send": "Enviado", - "next": "Siguiente", - "sendersChoice": "Elección del remitente", - "sendTo": "Enviar a", - "to": "para", - "pay": "Pagar", - "requesting": "está solicitando", - "sentInvoiceTo": "Enviar factura a", - "sentRequestTo": "Enviar solicitud a", - "pleasePay": "Por favor pague", - "day": "Día", - "week": "Semana", - "month": "Mes", - "all": "Todo", - "export": "Exportar", - "invoice": "Factura", - "readyToPay": "está listo para pagar", - "describeOrder": "Utilice este campo para describir su orden", - "requestAnInvoice": "Solicitar factura", - "submit": "Enviar", - "isRequesting": "está solicitando", - "awaiting": "Esperando", - "tapAnywhere": "Toca en cualquier lugar", - "from": "de", - "redeem": "Redimir", - "redeemed": "Redimido", - "redeemedBy": "Redimido por" - } + "bitcoin": "Bitcoin", + "lightning": "Lightning", + "invoice": "Factura", + "unfollow": "Dejar de seguir", + "following": "Siguiendo", + "followers": "Seguidores", + "follow": "Seguir", + "remove": "Eliminar", + "scanToPayBTC": "Escanear para pagar con bitcoin", + "this": "Esta", + "lightningAddress": "Dirección lightning", + "dashboard": { + "go": "Ir", + "dashboard": "Panel", + "accountBalance": "Saldo de la cuenta", + "bitcoinPrice": "Precio de Bitcoin:", + "conversionFeeWarning": "Se le cobrará una tarifa de retiro del 1% sobre el monto.", + "dataFromBinance": "Datos de <span class=\"text-black\">Binance</span>.", + "fetchingRate": "calculando tasa...", + "optionalNote": "Nota (Opcional)", + "send": "Enviar", + "receive": "Recibir", + "withdraw": "Retirar", + "deposit": "Depositar", + "voucher": "Comprobante" + }, + "receive": { + "lessThan1BTCWarning": "Introduzca una cantidad inferior a 1 bitcoin.", + "showQR": "Mostrar código QR" + }, + "send": { + "contacts": "Contactos", + "scan": "Escanear", + "paste": "Pegar", + "next": "Siguiente", + "placeholder": "Asigne una dirección de bitcoin, una factura lightning o un usuario de coinos" + }, + "settings": { + "saveSettings": "Guardar ajustes", + "tipPrompt": "Solicitar propinas", + "tipPromptDescription": "Pregunta a las personas si quieren agregar una propina antes de pagar sus facturas", + "bannerImage": "Imagen del Banner", + "profileImage": "Imagen de perfil", + "oneTimeCode": "Ingrese el código de un solo uso", + "accountId": "Id Cuenta", + "header": "Ajustes", + "ACCOUNT": "CUENTA", + "POINT_OF_SALE": "PUNTO DE VENTA", + "SECURITY": "SEGURIDAD", + "locale": "Idioma", + "username": "Usuario", + "email": "Correo", + "newPassword": "Nueva contraseña", + "businessAddress": "Dirección Negocio", + "emailNotifications": "Notificaciones de Correo Electrónico", + "emailNotificationsDescription": "Reciba notificaciones por correo electrónico cuando se produzca un cambio en su cuenta o cuando las personas le envíen pagos.", + "smsNotifications": "Notificaciones SMS", + "smsNotificationsDescription": "Coinos enviará notificaciones de texto cuando esto esté habilitado.", + "theme": "Color Tema", + "themeDescription": "Seleccione entre los temas de color predeterminados.", + "localCurrency": "Moneda Local", + "btcUnit": "Ver bitcoin en:", + "bitcoin": "bitcoin", + "satoshis": "Satoshis", + "securityPIN": "PIN de Seguridad", + "securityPINDescription": "Establezca un pin de seguridad para desbloquear la configuración y enviar pagos.", + "setPIN": "Establecer PIN", + "autoLock": "Temporizador de bloqueo automático", + "autoLockDescription": "Su cuenta entrará en modo seguro automáticamente después del tiempo especificado:", + "twofa": "Autenticación de 2 factores", + "twofaDescription": "Agregue una capa extra de seguridad a su dispositivo habilitando la autenticación de 2 factores.", + "twofaSetup": "Configurar 2FA", + "twofaDisable": "Deshabilitar 2FA", + "select": "Seleccionar", + "verifyPIN": "Verificar PIN", + "enablePIN": "Habilitar PIN", + "disablePIN": "Deshabilitar PIN" + }, + "support": { + "header": "Soporte", + "email": "Correo", + "accountName": "Nombre Cuenta", + "message": "Mensaje", + "send": "Enviar" + } + }, + "payments": { + "header": "Transacciones", + "AMOUNT": "CANTIDAD", + "TYPE": "TIPO", + "TIME": "HORA", + "sent": "Enviado", + "received": "Recibido", + "empty": "Aún no hay transacciones.", + "send": "Enviado", + "next": "Siguiente", + "sendersChoice": "Elección del remitente", + "sendTo": "Enviar a", + "to": "para", + "pay": "Pagar", + "requesting": "está solicitando", + "sentInvoiceTo": "Enviar factura a", + "sentRequestTo": "Enviar solicitud a", + "pleasePay": "Por favor pague", + "day": "Día", + "week": "Semana", + "month": "Mes", + "all": "Todo", + "export": "Exportar", + "invoice": "Factura", + "readyToPay": "está listo para pagar", + "describeOrder": "Utilice este campo para describir su orden", + "requestAnInvoice": "Solicitar factura", + "submit": "Enviar", + "isRequesting": "está solicitando", + "awaiting": "Esperando", + "tapAnywhere": "Toca en cualquier lugar", + "from": "de", + "redeem": "Redimir", + "redeemed": "Redimido", + "redeemedBy": "Redimido por" + } } diff --git a/src/locales/fa.json b/src/locales/fa.json index fb5a4418..22a506d7 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -1,282 +1,282 @@ { - "404": { - "404": "404", - "pageNotFound": "Page not found." - }, - "about": { - "header": "About", - "p1": "Coinos is an open source bitcoin payment app. Development started in Vancouver in September 2012 as a way to provide local merchants with a convenient way to accept bitcoin payments.", - "p2": "You can download the source code to run an instance and connect it to your own bitcoin and lightning nodes. We'll help you get started being a benevolent custodian and payments provider for your community.", - "p3": "Account transfers amongst users on our server are done off-chain so they're completely free, instant, and unlimited.", - "team": { - "header": "The Team", - "adam": { - "name": "Adam Soltys", - "position": "Chief Technical Officer", - "bio": "Adam is a web developer and bitcoin enthusiast who believes sound money is key to increasing global prosperity. He lives in North Vancouver with his wife Chelsea and two children." - }, - "cole": { - "name": "Cole", - "position": "Certified Bitcoin Professional", - "bio": "Cole is a driven individual with a passion for freedom. He came across bitcoin in 2016 and studied it in depth. Now he wants to introduce the lightning network to as many users as possible." - } - }, - "locations": { - "header": "Locations", - "description": "Here are some businesses that are accepting bitcoin with Coinos. We hope that yours will be next!" - } - }, - "error": { - "header": "Error", - "message": "Please try again or contact support.", - "signIn": "Please sign in to continue." - }, - "faq": { - "header": "FAQ", - "cost": { - "question": "What does it cost?", - "answer": "It's free to register an account and receive payments. We charge a 0.1% conversion fee if you withdraw regular bitcoin to lightning or vice versa." - }, - "compatibility": { - "question": "Do I need any special device or software?", - "answer": "No. Coinos is web based, so as long as you have an internet connection and a web browser, it works anywhere. Retail merchants might use it on a phone or a tablet." - }, - "safety": { - "question": "When do my funds settle?", - "answer": "Lightning payments are settled instantly. You can withdraw your balance to another wallet or exchange immediately once funds are received." - } - }, - "footer": { - "company": "Company", - "followUs": "Follow Us", - "github": "GitHub", - "linktree": "Linktree", - "jobs": "Jobs", - "support": "Support", - "telegram": "Telegram", - "twitter": "Twitter", - "documentation": "Documentation" - }, - "howItWorks": { - "header": "How it works", - "step1": { - "header": "1. Create your account", - "description": "A username and password is all you need to get started." - }, - "step2": { - "header": "2. Ask for payments", - "description": "The same as you normally would, but with bitcoin." - }, - "step3": { - "header": "3. That's all!", - "description": "Spend your coin immediately or save it and watch it grow, the choice is yours." - } - }, - "invoice": { - "addTip": "Add a tip", - "addTipq": "Add a tip?", - "done": "Done", - "paymentSuccessful": "Payment received!", - "invoice": "Invoice", - "total": "Total", - "tip": "Tip" - }, - "jobs": { - "header": "Jobs", - "subheader": "Current Openings", - "pitch1": "Coinos is actively looking for developers to join our growing team (full-time or part-time, remote working OK).", - "pitch2": "Excited about working on open source bitcoin? Then take a look at our repositories on", - "github": "GitHub", - "pitch3": "If you think you can make an impact, we'd love to hear from you. Send us your resume to" - }, - "landing": { - "header_before_icon": "The", - "header_with_icon": "easiest", - "header_after_icon": "way to get started with bitcoin.", - "subheader": "A simple but powerful web wallet for everyone. No software or hardware required.", - "info1": { - "title": "Instant payments via Lightning", - "description": "We make it easy for merchants to receive retail payments. No waiting for bank settlements or worrying about chargebacks. Instant settlement, in seconds. Say goodbye to 3% credit card processing fees." - }, - "info2": { - "title": "Simple to use", - "description1": "Just enter an amount in your local currency, share the QR code and you’re done.", - "description2": "We do all the heavy lifting so you can focus on your business.", - "description3": "You always have the option of taking custody of your funds by withdrawing instantly at any time." - }, - "info3": { - "title": "Brand it your way!", - "description": "Customize your profile page look and feel, or keep it on default - it's totally up to you." - } - }, - "login": { - "optional": "Optional", - "token": "2FA Token", - "username": "Username", - "password": "Password", - "email": "Email", - "passwordRecommendation": "We recommend a long, random password generated with a password manager.", - "forgotUserOrPassword": "Forgot Username or Password?", - "haveAccount": "Already have an account?", - "noAccount": "Don't have an account?", - "register": "Register", - "signIn": "Sign In", - "failed": "Login failed" - }, - "nav": { - "profile": "Profile", - "startInSeconds": "Start in Seconds", - "account": "Account", - "signIn": "Sign In", - "signOut": "Sign Out", - "dashboard": "Dashboard", - "settings": "Settings", - "support": "Support" - }, - "newSiteHeader": "Join us at our <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">launch party</a> in Vancouver on October 26! Looking for the old coinos? Visit <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", - "news": { - "header": "News", - "subheader": "Latest Updates", - "update1": "" - }, - "user": { - "bitcoin": "Bitcoin", - "lightning": "Lightning", - "invoice": "Invoice", - "unfollow": "Unfollow", - "following": "Following", - "follow": "Follow", - "followers": "Followers", - "remove": "Remove", - "noNotes": "No notes yet", - "message": "Message", - "messages": "Messages", - "feed": "Feed", - "notes": "Notes", - "pay": "Pay", - "scanToPayBTC": "Scan to pay with bitcoin", - "this": "This", - "lightningAddress": "Lightning address", - "dashboard": { - "go": "Go", - "dashboard": "Dashboard", - "accountBalance": "Account Balance", - "bitcoinPrice": "bitcoin Price:", - "conversionFeeWarning": "You will be charged a 1% withdrawal fee on the amount.", - "dataFromBinance": "Data from <span class=\"text-black\">Binance</span>.", - "fetchingRate": "fetching rate...", - "optionalNote": "Note (Optional)", - "send": "Send", - "receive": "Receive", - "withdraw": "Send", - "deposit": "Deposit", - "voucher": "Voucher" - }, - "receive": { - "lessThan1BTCWarning": "Please enter an amount less than 1 bitcoin.", - "showQR": "Show QR" - }, - "send": { - "contacts": "Contacts", - "scan": "Scan", - "paste": "Paste", - "next": "Next", - "placeholder": "Paste a bitcoin address, lightning invoice or coinos username" - }, - "settings": { - "saveSettings": "Save Settings", - "bannerImage": "Banner Image", - "profileImage": "Profile Image", - "oneTimeCode": "Enter one-time code", - "accountId": "Account Secret", - "header": "Settings", - "ACCOUNT": "ACCOUNT", - "POINT_OF_SALE": "PREFERENCES", - "SECURITY": "SECURITY", - "locale": "Language", - "username": "Username", - "email": "Email", - "newPassword": "Change Password", - "about": "About", - "aboutPlaceholder": "A little something about you or your business address and hours", - "emailNotifications": "Email Notifications", - "emailNotificationsDescription": "Get notified via email when a change occurs on your account or when people send you payments.", - "smsNotifications": "SMS Notifications", - "smsNotificationsDescription": "Coinos will send you text notifications when this is enabled.", - "theme": "Color Theme", - "tipPrompt": "Prompt for tips", - "tipPromptDescription": "Ask people if they want to add a tip before paying their invoices", - "displayName": "Display name", - "themeDescription": "Select from the default color themes.", - "localCurrency": "Currency", - "btcUnit": "Display bitcoin in:", - "bitcoin": "bitcoin", - "minutes": "minutes", - "hour": "hour", - "hours": "hours", - "satoshis": "Satoshis", - "securityPIN": "Security PIN", - "securityPINDescription": "Set a security pin that will be used to unlock your settings and to send outgoing payments.", - "setPIN": "Set PIN", - "autoLock": "Auto-Lock Timer", - "autoLockDescription": "Your account will go into a secure mode, requiring you to re-enter your credentials after a specified time:", - "twofa": "2-Factor Authentication", - "twofaDescription": "Add an extra level of security to your device by enabling 2-factor authentication.", - "twofaSetup": "Set up 2FA", - "twofaDisable": "Disable 2FA", - "select": "Select", - "verifyPIN": "Verify PIN", - "enablePIN": "Enable PIN", - "disablePIN": "Disable PIN" - }, - "support": { - "header": "Support", - "email": "Email", - "accountName": "Account Name", - "message": "Message", - "send": "Send" - } - }, - "payments": { - "amount": "Amount", - "date": "Date", - "id": "Transaction ID", - "details": "Transaction Details", - "header": "Payments", - "preimage": "Proof of payment", - "type": "Type", - "time": "Time", - "sent": "Sent", - "received": "Received", - "empty": "No transactions yet.", - "send": "Send", - "next": "Next", - "sendersChoice": "Sender's choice", - "sendTo": "Send to", - "to": "to", - "pay": "Pay", - "requesting": "is requesting", - "sentInvoiceTo": "Sent invoice to", - "sentRequestTo": "Request sent!", - "pleasePay": "Please pay", - "day": "Day", - "week": "Week", - "month": "Month", - "all": "All", - "export": "Export", - "invoice": "Invoice", - "readyToPay": "is ready to pay", - "describeOrder": "Use this field to describe your order", - "requestAnInvoice": "Request Invoice", - "submit": "Submit", - "isRequesting": "is requesting", - "invoiceFrom": "Invoice from", - "awaiting": "Awaiting", - "tapAnywhere": "Tap anywhere to continue", - "from": "from", - "redeem": "Redeem", - "redeemed": "Redeemed", - "redeemedBy": "Redeemed by", - "sendMessage": "Write a message..." - } + "404": { + "404": "404", + "pageNotFound": "Page not found." + }, + "about": { + "header": "About", + "p1": "Coinos is an open source bitcoin payment app. Development started in Vancouver in September 2012 as a way to provide local merchants with a convenient way to accept bitcoin payments.", + "p2": "You can download the source code to run an instance and connect it to your own bitcoin and lightning nodes. We'll help you get started being a benevolent custodian and payments provider for your community.", + "p3": "Account transfers amongst users on our server are done off-chain so they're completely free, instant, and unlimited.", + "team": { + "header": "The Team", + "adam": { + "name": "Adam Soltys", + "position": "Chief Technical Officer", + "bio": "Adam is a web developer and bitcoin enthusiast who believes sound money is key to increasing global prosperity. He lives in North Vancouver with his wife Chelsea and two children." + }, + "cole": { + "name": "Cole", + "position": "Certified Bitcoin Professional", + "bio": "Cole is a driven individual with a passion for freedom. He came across bitcoin in 2016 and studied it in depth. Now he wants to introduce the lightning network to as many users as possible." + } + }, + "locations": { + "header": "Locations", + "description": "Here are some businesses that are accepting bitcoin with Coinos. We hope that yours will be next!" + } + }, + "error": { + "header": "Error", + "message": "Please try again or contact support.", + "signIn": "Please sign in to continue." + }, + "faq": { + "header": "FAQ", + "cost": { + "question": "What does it cost?", + "answer": "It's free to register an account and receive payments. We charge a 0.1% conversion fee if you withdraw regular bitcoin to lightning or vice versa." + }, + "compatibility": { + "question": "Do I need any special device or software?", + "answer": "No. Coinos is web based, so as long as you have an internet connection and a web browser, it works anywhere. Retail merchants might use it on a phone or a tablet." + }, + "safety": { + "question": "When do my funds settle?", + "answer": "Lightning payments are settled instantly. You can withdraw your balance to another wallet or exchange immediately once funds are received." + } + }, + "footer": { + "company": "Company", + "followUs": "Follow Us", + "github": "GitHub", + "linktree": "Linktree", + "jobs": "Jobs", + "support": "Support", + "telegram": "Telegram", + "twitter": "Twitter", + "documentation": "Documentation" + }, + "howItWorks": { + "header": "How it works", + "step1": { + "header": "1. Create your account", + "description": "A username and password is all you need to get started." + }, + "step2": { + "header": "2. Ask for payments", + "description": "The same as you normally would, but with bitcoin." + }, + "step3": { + "header": "3. That's all!", + "description": "Spend your coin immediately or save it and watch it grow, the choice is yours." + } + }, + "invoice": { + "addTip": "Add a tip", + "addTipq": "Add a tip?", + "done": "Done", + "paymentSuccessful": "Payment received!", + "invoice": "Invoice", + "total": "Total", + "tip": "Tip" + }, + "jobs": { + "header": "Jobs", + "subheader": "Current Openings", + "pitch1": "Coinos is actively looking for developers to join our growing team (full-time or part-time, remote working OK).", + "pitch2": "Excited about working on open source bitcoin? Then take a look at our repositories on", + "github": "GitHub", + "pitch3": "If you think you can make an impact, we'd love to hear from you. Send us your resume to" + }, + "landing": { + "header_before_icon": "The", + "header_with_icon": "easiest", + "header_after_icon": "way to get started with bitcoin.", + "subheader": "A simple but powerful web wallet for everyone. No software or hardware required.", + "info1": { + "title": "Instant payments via Lightning", + "description": "We make it easy for merchants to receive retail payments. No waiting for bank settlements or worrying about chargebacks. Instant settlement, in seconds. Say goodbye to 3% credit card processing fees." + }, + "info2": { + "title": "Simple to use", + "description1": "Just enter an amount in your local currency, share the QR code and you’re done.", + "description2": "We do all the heavy lifting so you can focus on your business.", + "description3": "You always have the option of taking custody of your funds by withdrawing instantly at any time." + }, + "info3": { + "title": "Brand it your way!", + "description": "Customize your profile page look and feel, or keep it on default - it's totally up to you." + } + }, + "login": { + "optional": "Optional", + "token": "2FA Token", + "username": "Username", + "password": "Password", + "email": "Email", + "passwordRecommendation": "We recommend a long, random password generated with a password manager.", + "forgotUserOrPassword": "Forgot Username or Password?", + "haveAccount": "Already have an account?", + "noAccount": "Don't have an account?", + "register": "Register", + "signIn": "Sign In", + "failed": "Login failed" + }, + "nav": { + "profile": "Profile", + "startInSeconds": "Start in Seconds", + "account": "Account", + "signIn": "Sign In", + "signOut": "Sign Out", + "dashboard": "Dashboard", + "settings": "Settings", + "support": "Support" + }, + "newSiteHeader": "Join us at our <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">launch party</a> in Vancouver on October 26! Looking for the old coinos? Visit <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", + "news": { + "header": "News", + "subheader": "Latest Updates", + "update1": "" + }, + "user": { + "bitcoin": "Bitcoin", + "lightning": "Lightning", + "invoice": "Invoice", + "unfollow": "Unfollow", + "following": "Following", + "follow": "Follow", + "followers": "Followers", + "remove": "Remove", + "noNotes": "No notes yet", + "message": "Message", + "messages": "Messages", + "feed": "Feed", + "notes": "Notes", + "pay": "Pay", + "scanToPayBTC": "Scan to pay with bitcoin", + "this": "This", + "lightningAddress": "Lightning address", + "dashboard": { + "go": "Go", + "dashboard": "Dashboard", + "accountBalance": "Account Balance", + "bitcoinPrice": "bitcoin Price:", + "conversionFeeWarning": "You will be charged a 1% withdrawal fee on the amount.", + "dataFromBinance": "Data from <span class=\"text-black\">Binance</span>.", + "fetchingRate": "fetching rate...", + "optionalNote": "Note (Optional)", + "send": "Send", + "receive": "Receive", + "withdraw": "Send", + "deposit": "Deposit", + "voucher": "Voucher" + }, + "receive": { + "lessThan1BTCWarning": "Please enter an amount less than 1 bitcoin.", + "showQR": "Show QR" + }, + "send": { + "contacts": "Contacts", + "scan": "Scan", + "paste": "Paste", + "next": "Next", + "placeholder": "Paste a bitcoin address, lightning invoice or coinos username" + }, + "settings": { + "saveSettings": "Save Settings", + "bannerImage": "Banner Image", + "profileImage": "Profile Image", + "oneTimeCode": "Enter one-time code", + "accountId": "Account Secret", + "header": "Settings", + "ACCOUNT": "ACCOUNT", + "POINT_OF_SALE": "PREFERENCES", + "SECURITY": "SECURITY", + "locale": "Language", + "username": "Username", + "email": "Email", + "newPassword": "Change Password", + "about": "About", + "aboutPlaceholder": "A little something about you or your business address and hours", + "emailNotifications": "Email Notifications", + "emailNotificationsDescription": "Get notified via email when a change occurs on your account or when people send you payments.", + "smsNotifications": "SMS Notifications", + "smsNotificationsDescription": "Coinos will send you text notifications when this is enabled.", + "theme": "Color Theme", + "tipPrompt": "Prompt for tips", + "tipPromptDescription": "Ask people if they want to add a tip before paying their invoices", + "displayName": "Display name", + "themeDescription": "Select from the default color themes.", + "localCurrency": "Currency", + "btcUnit": "Display bitcoin in:", + "bitcoin": "bitcoin", + "minutes": "minutes", + "hour": "hour", + "hours": "hours", + "satoshis": "Satoshis", + "securityPIN": "Security PIN", + "securityPINDescription": "Set a security pin that will be used to unlock your settings and to send outgoing payments.", + "setPIN": "Set PIN", + "autoLock": "Auto-Lock Timer", + "autoLockDescription": "Your account will go into a secure mode, requiring you to re-enter your credentials after a specified time:", + "twofa": "2-Factor Authentication", + "twofaDescription": "Add an extra level of security to your device by enabling 2-factor authentication.", + "twofaSetup": "Set up 2FA", + "twofaDisable": "Disable 2FA", + "select": "Select", + "verifyPIN": "Verify PIN", + "enablePIN": "Enable PIN", + "disablePIN": "Disable PIN" + }, + "support": { + "header": "Support", + "email": "Email", + "accountName": "Account Name", + "message": "Message", + "send": "Send" + } + }, + "payments": { + "amount": "Amount", + "date": "Date", + "id": "Transaction ID", + "details": "Transaction Details", + "header": "Payments", + "preimage": "Proof of payment", + "type": "Type", + "time": "Time", + "sent": "Sent", + "received": "Received", + "empty": "No transactions yet.", + "send": "Send", + "next": "Next", + "sendersChoice": "Sender's choice", + "sendTo": "Send to", + "to": "to", + "pay": "Pay", + "requesting": "is requesting", + "sentInvoiceTo": "Sent invoice to", + "sentRequestTo": "Request sent!", + "pleasePay": "Please pay", + "day": "Day", + "week": "Week", + "month": "Month", + "all": "All", + "export": "Export", + "invoice": "Invoice", + "readyToPay": "is ready to pay", + "describeOrder": "Use this field to describe your order", + "requestAnInvoice": "Request Invoice", + "submit": "Submit", + "isRequesting": "is requesting", + "invoiceFrom": "Invoice from", + "awaiting": "Awaiting", + "tapAnywhere": "Tap anywhere to continue", + "from": "from", + "redeem": "Redeem", + "redeemed": "Redeemed", + "redeemedBy": "Redeemed by", + "sendMessage": "Write a message..." + } } diff --git a/src/locales/fr.json b/src/locales/fr.json index b79f5d54..ad5478f6 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -1,312 +1,312 @@ { - "404": { - "404": "404", - "pageNotFound": "Page non trouvée." - }, - "about": { - "header": "À Propos", - "locations": { - "header": "Établissements", - "description": "Il y a des entreprises qui acceptent bitcoin utilisant le point de vente coinos. Si vous utilisez notre appli, s'il vous plait <a href=\"mailto:[email protected]\" class=\"underline underline-offset-4\">fais-le-nous savoir!</a>" - }, - "team": { - "header": "L'Équipe", - "adam": { - "name": "Adam Soltys", - "position": "Officier Technique Principal", - "bio": "Adam est développeur web et passionné de bitcoin. Il croit que l'argent sûr est la clé pour augmenter la prospérité planétaire. Il habite en Vancouver de Nord avec sa femme Chelsea et deux enfants." - }, - "cole": { - "name": "Cole Goodwin", - "position": "Professionel de Bitcoin", - "bio": "Cole est un professionel de bitcoin diplômé et un homme motivé avec passion pour la liberté. Il a découvert bitcoin en 2016 et l'a etudié en détail. Maintenant, il veux introduire le réseau Lightning à autant d'utilisateurs que possible." - } - } - }, - "error": { - "header": "Erreur", - "message": "S'il vous plait réessayez ou contactez l'assistance.", - "signIn": "Vous devez connecter pour continuer.", - "unrecognizedInput": "Donnée non reconnu" - }, - "faq": { - "header": "FAQ", - "cost": { - "question": "Combien cela coûte-t-il?", - "answer": "Recevoir des paiements sur votre compte est gratuit. Les retraits vers des portefeuilles externes entraînent des frais minimes qui varient en fonction des conditions du réseau. Nous facturons 0,1% pour convertir entre le Bitcoin lightning et le Bitcoin on-chain. Envoyer à d'autres comptes Coinos est gratuit." - }, - "compatibility": { - "question": "Ai-je besoin d'un dispositif ou d'un logiciel spécial?", - "answer": "Non, Coinos est une application web qui fonctionne partout où il y a Internet. Les commerçants pourraient l'utiliser sur une tablette ou afficher un code QR pour que les clients puissent le scanner avec leur téléphone." - }, - "safety": { - "question": "Quand mes fonds sont-ils réglés?", - "answer": "Les paiements lightning sont réglés instantanément. Vous pouvez retirer votre solde vers un autre portefeuille en quelques secondes. Les dépôts on-chain prennent en moyenne 5-10 minutes pour être confirmés." - }, - "pos": { - "question": "Coinos peut-il s'intégrer à mon PoS existant?", - "answer": "Nous recommandons d'entrer les ventes dans votre PoS existant comme des transactions en espèces." - }, - "bitcoin": { - "question": "Que puis-je faire avec le Bitcoin?", - "answer": "Dépensez-le chez d'autres commerçants ou payez vos dépenses avec en incitant votre personnel, vos fournisseurs et votre propriétaire à l'accepter." - } - }, - "footer": { - "followUs": "Suivez-Nous", - "twitter": "Twitter", - "github": "GitHub", - "company": "Entreprise", - "telegram": "Telegram", - "support": "Assistance", - "jobs": "Jobs", - "linktree": "Linktree", - "documentation": "Documentation" - }, - "howItWorks": { - "header": "Comment cela Fonctionne", - "step1": { - "header": "1. Créez votre compte", - "description": "Un nom d'utilateur et mot de passe est tout ce dont vous avez besoin pour commencer." - }, - "step2": { - "header": "2. Demande pour des paiements", - "description": "Comme vous faisiez normalement, mais avec bitcoin." - }, - "step3": { - "header": "3. C'est tout!", - "description": "Vous pouvez convertir en monnaie local n'importe quand." - } - }, - "landing": { - "header_before_icon": "Le", - "header_with_icon": "façon", - "header_after_icon": "le plus façile d'accepter bitcoin dans votre magasin.", - "subheader": "Agrandir votre clientèle et chiffre d'affaires en acceptant bitcoin avec un point de vente façile à utiliser et géré. Logiciel et matériel informatique non obligatoire.", - "info1": { - "title": "Paiements instantanés avec Lightning", - "description": "Le moment où votre client envoie un paiment, c'est là. Vous ne devez pas attendre les décisions des banques ou vous inquiéter pour des rejets de débit. Décisions instantanées, en secondes. Dites au revoir aux frais de transactions de 3% des cartes de crédit." - }, - "info2": { - "title": "Simple à utiliser, en garde par défaut", - "description1": "Juste entrer une quantité, partager le code QR et c'est tout!", - "description2": "Tes comptes sont en garde par défaut, qui élimine le nécessité de comprendre comment bitcoin/lightning fonctionnent. Nous réalisons le plus gros du travail pour que vous puissiez vous concentrer sur votre entreprise.", - "description3": "Vous avez toujours l'option de <span class=\"underline undeline-offset-4\">gérer</span> vos clés." - }, - "info3": { - "title": "Personalisez-le comme vous voulez", - "description": "Personalisez votre point de vente, ou utilisez les réglages par défaut - c'est votre chois." - } - }, - "login": { - "username": "Nom d'utilisateur", - "password": "Mot de passe", - "email": "Courriel", - "passwordRecommendation": "Nous recommendons un mot de passe long et aléatoire, généré par un gestionnaire de mots de passe.", - "forgotUserOrPassword": "Oublié votre nom d'utilisateur ou mot de passe ?", - "haveAccount": "Avez-vous déjà un compte ?", - "noAccount": "Pas encore de compte ?", - "register": "Enregistrer", - "signIn": "Connexion", - "failed": "Echec de Connexion", - "optional": "Optionnel", - "token": "Jeton 2FA" - }, - "nav": { - "startInSeconds": "Commence en Secondes", - "account": "Compte", - "signIn": "Connexion", - "signOut": "Déconnexion", - "dashboard": "Tableau de bord", - "settings": "Préférences", - "support": "Assistance", - "profile": "Profil", - "map": "Carte", - "merch": "Marchandise", - "about": "À Propos", - "docs": "Documents", - "messages": "Messages", - "funds": "Fonds" - }, - "invoice": { - "addTip": "Ajoutez un pourboire", - "addTipq": "Ajouter un pourboire?", - "done": "Fini", - "paymentSuccessful": "Paiement réussi!", - "invoice": "Facture", - "total": "Total", - "tip": "Pourboire" - }, - "jobs": { - "header": "Emplois", - "subheader": "Postes disponibles actuels", - "pitch1": "coinos recherche pour des développeurs pour joindre notre équipe croissant (temps plein ou partiel, télétravail OK)", - "pitch2": "Avez-vous hâte de travailler sur bitcoin open source? Regardez sur nos dépôts sur", - "github": "GitHub", - "pitch3": "Si vous pensez que vous pouvez exercer une influence positive, nous voulons que vous contribuez. Envoyer votre CV à" - }, - "user": { - "follow": "Suivre", - "remove": "Enlève", - "scanToPayBTC": "Scanner pour payer avec bitcoin", - "dashboard": { - "dashboard": "Tableau de bord", - "accountBalance": "Solde", - "conversionFeeWarning": "Il y a des frais de 1% pour convertir entre bitcoin, Liquid et Lightning.", - "fetchingRate": "cherchant taut de change...", - "send": "Envoie", - "withdraw": "Retire", - "go": "Allez", - "receive": "Reçois un Paiement", - "deposit": "Déposer", - "voucher": "Bon" - }, - "receive": { - "lessThan1BTCWarning": "S'il vous plaît utilisez moins d'un bitcoin.", - "showQR": "Voire le QR code" - }, - "settings": { - "header": "Préférences", - "ACCOUNT": "COMPTE", - "POINT_OF_SALE": "POINT DE VENTE", - "SECURITY": "SÉCURITÉ", - "locale": "Langue", - "username": "Nom d'Utilisateur", - "email": "Courriel", - "newPassword": "Nouveau Mot de Passe", - "emailNotifications": "Notifications par Courriel", - "emailNotificationsDescription": "Nous pouvons vous notifier par courriel quand votre compte change ou quand vous recevez des paiements", - "smsNotifications": "Notifications par SMS", - "smsNotificationsDescription": "Nous vous envoyons des notifications par texte quand c'est activé", - "theme": "Thème", - "themeDescription": "Sélectionnez des thèmes dèfauts.", - "localCurrency": "Monnaie Local", - "btcUnit": "Voire bitcoin en:", - "bitcoin": "bitcoin", - "satoshis": "Satoshis", - "securityPIN": "NIP de Sécurité", - "securityPINDescription": "Choisir un NIP de sécurité qui sera necessaire pour ouvrir tes préférences et pour envoyer des paiements sortants.", - "setPIN": "Choisir Votre NIP", - "autoLock": "Auto-Verouillage", - "autoLockDescription": "Ta conte activera une mode sécuritaire après une durée spécifiée:", - "twofa": "Authentification à 2 Facteurs", - "twofaDescription": "Ajoutez un autre niveau de sécurité par activez l'authentication à deux facteurs.", - "twofaSetup": "Activer A2F", - "bannerImage": "Image de Bandeau", - "profileImage": "Image de Profil", - "oneTimeCode": "Entrez mot de passe à usage unique", - "accountId": "Secret de conte", - "twofaDisable": "Désactivez A2F", - "about": "À Propos", - "aboutPlaceholder": "Un description court de vous ou votre entreprise (adresse, bio, etc.)", - "select": "Choisir", - "verifyPIN": "Verifiez votre NIP", - "enablePIN": "Activer NIP", - "disablePIN": "Désactiver NIP", - "saveSettings": "Sauvegarder Paramètres", - "tipPrompt": "Demande pour Pourboires", - "tipPromptDescription": "Demander les gens s'ils voulent ajouter un pourboire avant payer leurs factures", - "displayName": "Nom d'Utilisateur", - "minutes": "minutes", - "hour": "heure", - "hours": "heures", - "hideSeed": "Cache phrase mnémotechnique", - "revealSeed": "Montre phrase mnémotechnique" - }, - "support": { - "header": "Assistance", - "email": "Courriel", - "accountName": "Nom d'utilisateur", - "message": "Message", - "send": "Envoie" - }, - "bitcoin": "Bitcoin", - "lightning": "Lightning", - "invoice": "Facture", - "unfollow": "Ne plus suivre", - "following": "Vous suivez", - "this": "Cette", - "lightningAddress": "Adresse de lightning", - "send": { - "contacts": "Contacts", - "scan": "Scannez", - "paste": "Collez", - "next": "Prochain", - "placeholder": "Collez une adresse de bitcoin, facture de lightning ou nom d'utilisateur coinos", - "makeQR": "QR" - }, - "requestPayment": "Demander un Paiement", - "welcome": "Bienvenue! Le solde de votre conte est vide. Vous pouvez commencer par déposer ou recevoir un paiement.", - "showBalance": "Montrer Solde", - "followers": "Abonnés", - "noNotes": "Pas de notes", - "message": "Envoyer", - "messages": "Messages", - "feed": "Flux", - "notes": "Notes", - "pay": "Payer", - "profilePage": "Page de profil", - "nostrPubkey": "Clé publique nostr" - }, - "news": { - "header": "Nouvelles", - "subheader": "Dernières Informations" - }, - "payments": { - "addFunds": "Ajouter d'argent", - "takeFunds": "Prendre d'argent", - "show": "Démontrer", - "hide": "Cacher", - "qr": "QR", - "copyLink": "Copier le Lien", - "copyText": "Copier le Texte", - "openLink": "Ouvrer le Portefeuille", - "moreOptions": "Plus d'options", - "total": "Total", - "gainLoss": "Hausse/Perte", - "presentValue": "Valuer au présent", - "amount": "Quantité", - "date": "Date", - "id": "ID de Transaction", - "details": "Détails de Transaction", - "header": "Paiements", - "preimage": "Preuve de paiement", - "type": "Type", - "time": "Heure", - "sent": "Envoyé", - "received": "Reçu", - "empty": "Vous n'avez pas de transactions.", - "send": "Envoyer", - "next": "Prochain", - "sendTo": "Envoyer à", - "to": "à", - "pay": "Payer", - "requesting": "demande", - "sentInvoiceTo": "Envoyé une facture à", - "sentRequestTo": "Demande envoyé!", - "pleasePay": "S'il vous plait payez", - "day": "Jour", - "week": "Semaine", - "month": "Mois", - "all": "Toutes", - "export": "Exporter", - "invoice": "Facture", - "readyToPay": "est prêt a payer", - "describeOrder": "Utiliser ce champ pour décrire votre commande", - "requestAnInvoice": "Demander une facture", - "submit": "Soumettre", - "isRequesting": "demande", - "invoiceFrom": "Facture de", - "awaiting": "Attendant", - "tapAnywhere": "Taper n'importe où pour continuer", - "from": "de", - "redeem": "Racheter", - "redeemed": "Racheté", - "redeemedBy": "Racheté par", - "sendMessage": "Écrire une message...", - "fundsClaimed": "Les fonds sont réclamés!", - "print": "Imprime", - "create": "Crée", - "fund": "Finance" - } + "404": { + "404": "404", + "pageNotFound": "Page non trouvée." + }, + "about": { + "header": "À Propos", + "locations": { + "header": "Établissements", + "description": "Il y a des entreprises qui acceptent bitcoin utilisant le point de vente coinos. Si vous utilisez notre appli, s'il vous plait <a href=\"mailto:[email protected]\" class=\"underline underline-offset-4\">fais-le-nous savoir!</a>" + }, + "team": { + "header": "L'Équipe", + "adam": { + "name": "Adam Soltys", + "position": "Officier Technique Principal", + "bio": "Adam est développeur web et passionné de bitcoin. Il croit que l'argent sûr est la clé pour augmenter la prospérité planétaire. Il habite en Vancouver de Nord avec sa femme Chelsea et deux enfants." + }, + "cole": { + "name": "Cole Goodwin", + "position": "Professionel de Bitcoin", + "bio": "Cole est un professionel de bitcoin diplômé et un homme motivé avec passion pour la liberté. Il a découvert bitcoin en 2016 et l'a etudié en détail. Maintenant, il veux introduire le réseau Lightning à autant d'utilisateurs que possible." + } + } + }, + "error": { + "header": "Erreur", + "message": "S'il vous plait réessayez ou contactez l'assistance.", + "signIn": "Vous devez connecter pour continuer.", + "unrecognizedInput": "Donnée non reconnu" + }, + "faq": { + "header": "FAQ", + "cost": { + "question": "Combien cela coûte-t-il?", + "answer": "Recevoir des paiements sur votre compte est gratuit. Les retraits vers des portefeuilles externes entraînent des frais minimes qui varient en fonction des conditions du réseau. Nous facturons 0,1% pour convertir entre le Bitcoin lightning et le Bitcoin on-chain. Envoyer à d'autres comptes Coinos est gratuit." + }, + "compatibility": { + "question": "Ai-je besoin d'un dispositif ou d'un logiciel spécial?", + "answer": "Non, Coinos est une application web qui fonctionne partout où il y a Internet. Les commerçants pourraient l'utiliser sur une tablette ou afficher un code QR pour que les clients puissent le scanner avec leur téléphone." + }, + "safety": { + "question": "Quand mes fonds sont-ils réglés?", + "answer": "Les paiements lightning sont réglés instantanément. Vous pouvez retirer votre solde vers un autre portefeuille en quelques secondes. Les dépôts on-chain prennent en moyenne 5-10 minutes pour être confirmés." + }, + "pos": { + "question": "Coinos peut-il s'intégrer à mon PoS existant?", + "answer": "Nous recommandons d'entrer les ventes dans votre PoS existant comme des transactions en espèces." + }, + "bitcoin": { + "question": "Que puis-je faire avec le Bitcoin?", + "answer": "Dépensez-le chez d'autres commerçants ou payez vos dépenses avec en incitant votre personnel, vos fournisseurs et votre propriétaire à l'accepter." + } + }, + "footer": { + "followUs": "Suivez-Nous", + "twitter": "Twitter", + "github": "GitHub", + "company": "Entreprise", + "telegram": "Telegram", + "support": "Assistance", + "jobs": "Jobs", + "linktree": "Linktree", + "documentation": "Documentation" + }, + "howItWorks": { + "header": "Comment cela Fonctionne", + "step1": { + "header": "1. Créez votre compte", + "description": "Un nom d'utilateur et mot de passe est tout ce dont vous avez besoin pour commencer." + }, + "step2": { + "header": "2. Demande pour des paiements", + "description": "Comme vous faisiez normalement, mais avec bitcoin." + }, + "step3": { + "header": "3. C'est tout!", + "description": "Vous pouvez convertir en monnaie local n'importe quand." + } + }, + "landing": { + "header_before_icon": "Le", + "header_with_icon": "façon", + "header_after_icon": "le plus façile d'accepter bitcoin dans votre magasin.", + "subheader": "Agrandir votre clientèle et chiffre d'affaires en acceptant bitcoin avec un point de vente façile à utiliser et géré. Logiciel et matériel informatique non obligatoire.", + "info1": { + "title": "Paiements instantanés avec Lightning", + "description": "Le moment où votre client envoie un paiment, c'est là. Vous ne devez pas attendre les décisions des banques ou vous inquiéter pour des rejets de débit. Décisions instantanées, en secondes. Dites au revoir aux frais de transactions de 3% des cartes de crédit." + }, + "info2": { + "title": "Simple à utiliser, en garde par défaut", + "description1": "Juste entrer une quantité, partager le code QR et c'est tout!", + "description2": "Tes comptes sont en garde par défaut, qui élimine le nécessité de comprendre comment bitcoin/lightning fonctionnent. Nous réalisons le plus gros du travail pour que vous puissiez vous concentrer sur votre entreprise.", + "description3": "Vous avez toujours l'option de <span class=\"underline undeline-offset-4\">gérer</span> vos clés." + }, + "info3": { + "title": "Personalisez-le comme vous voulez", + "description": "Personalisez votre point de vente, ou utilisez les réglages par défaut - c'est votre chois." + } + }, + "login": { + "username": "Nom d'utilisateur", + "password": "Mot de passe", + "email": "Courriel", + "passwordRecommendation": "Nous recommendons un mot de passe long et aléatoire, généré par un gestionnaire de mots de passe.", + "forgotUserOrPassword": "Oublié votre nom d'utilisateur ou mot de passe ?", + "haveAccount": "Avez-vous déjà un compte ?", + "noAccount": "Pas encore de compte ?", + "register": "Enregistrer", + "signIn": "Connexion", + "failed": "Echec de Connexion", + "optional": "Optionnel", + "token": "Jeton 2FA" + }, + "nav": { + "startInSeconds": "Commence en Secondes", + "account": "Compte", + "signIn": "Connexion", + "signOut": "Déconnexion", + "dashboard": "Tableau de bord", + "settings": "Préférences", + "support": "Assistance", + "profile": "Profil", + "map": "Carte", + "merch": "Marchandise", + "about": "À Propos", + "docs": "Documents", + "messages": "Messages", + "funds": "Fonds" + }, + "invoice": { + "addTip": "Ajoutez un pourboire", + "addTipq": "Ajouter un pourboire?", + "done": "Fini", + "paymentSuccessful": "Paiement réussi!", + "invoice": "Facture", + "total": "Total", + "tip": "Pourboire" + }, + "jobs": { + "header": "Emplois", + "subheader": "Postes disponibles actuels", + "pitch1": "coinos recherche pour des développeurs pour joindre notre équipe croissant (temps plein ou partiel, télétravail OK)", + "pitch2": "Avez-vous hâte de travailler sur bitcoin open source? Regardez sur nos dépôts sur", + "github": "GitHub", + "pitch3": "Si vous pensez que vous pouvez exercer une influence positive, nous voulons que vous contribuez. Envoyer votre CV à" + }, + "user": { + "follow": "Suivre", + "remove": "Enlève", + "scanToPayBTC": "Scanner pour payer avec bitcoin", + "dashboard": { + "dashboard": "Tableau de bord", + "accountBalance": "Solde", + "conversionFeeWarning": "Il y a des frais de 1% pour convertir entre bitcoin, Liquid et Lightning.", + "fetchingRate": "cherchant taut de change...", + "send": "Envoie", + "withdraw": "Retire", + "go": "Allez", + "receive": "Reçois un Paiement", + "deposit": "Déposer", + "voucher": "Bon" + }, + "receive": { + "lessThan1BTCWarning": "S'il vous plaît utilisez moins d'un bitcoin.", + "showQR": "Voire le QR code" + }, + "settings": { + "header": "Préférences", + "ACCOUNT": "COMPTE", + "POINT_OF_SALE": "POINT DE VENTE", + "SECURITY": "SÉCURITÉ", + "locale": "Langue", + "username": "Nom d'Utilisateur", + "email": "Courriel", + "newPassword": "Nouveau Mot de Passe", + "emailNotifications": "Notifications par Courriel", + "emailNotificationsDescription": "Nous pouvons vous notifier par courriel quand votre compte change ou quand vous recevez des paiements", + "smsNotifications": "Notifications par SMS", + "smsNotificationsDescription": "Nous vous envoyons des notifications par texte quand c'est activé", + "theme": "Thème", + "themeDescription": "Sélectionnez des thèmes dèfauts.", + "localCurrency": "Monnaie Local", + "btcUnit": "Voire bitcoin en:", + "bitcoin": "bitcoin", + "satoshis": "Satoshis", + "securityPIN": "NIP de Sécurité", + "securityPINDescription": "Choisir un NIP de sécurité qui sera necessaire pour ouvrir tes préférences et pour envoyer des paiements sortants.", + "setPIN": "Choisir Votre NIP", + "autoLock": "Auto-Verouillage", + "autoLockDescription": "Ta conte activera une mode sécuritaire après une durée spécifiée:", + "twofa": "Authentification à 2 Facteurs", + "twofaDescription": "Ajoutez un autre niveau de sécurité par activez l'authentication à deux facteurs.", + "twofaSetup": "Activer A2F", + "bannerImage": "Image de Bandeau", + "profileImage": "Image de Profil", + "oneTimeCode": "Entrez mot de passe à usage unique", + "accountId": "Secret de conte", + "twofaDisable": "Désactivez A2F", + "about": "À Propos", + "aboutPlaceholder": "Un description court de vous ou votre entreprise (adresse, bio, etc.)", + "select": "Choisir", + "verifyPIN": "Verifiez votre NIP", + "enablePIN": "Activer NIP", + "disablePIN": "Désactiver NIP", + "saveSettings": "Sauvegarder Paramètres", + "tipPrompt": "Demande pour Pourboires", + "tipPromptDescription": "Demander les gens s'ils voulent ajouter un pourboire avant payer leurs factures", + "displayName": "Nom d'Utilisateur", + "minutes": "minutes", + "hour": "heure", + "hours": "heures", + "hideSeed": "Cache phrase mnémotechnique", + "revealSeed": "Montre phrase mnémotechnique" + }, + "support": { + "header": "Assistance", + "email": "Courriel", + "accountName": "Nom d'utilisateur", + "message": "Message", + "send": "Envoie" + }, + "bitcoin": "Bitcoin", + "lightning": "Lightning", + "invoice": "Facture", + "unfollow": "Ne plus suivre", + "following": "Vous suivez", + "this": "Cette", + "lightningAddress": "Adresse de lightning", + "send": { + "contacts": "Contacts", + "scan": "Scannez", + "paste": "Collez", + "next": "Prochain", + "placeholder": "Collez une adresse de bitcoin, facture de lightning ou nom d'utilisateur coinos", + "makeQR": "QR" + }, + "requestPayment": "Demander un Paiement", + "welcome": "Bienvenue! Le solde de votre conte est vide. Vous pouvez commencer par déposer ou recevoir un paiement.", + "showBalance": "Montrer Solde", + "followers": "Abonnés", + "noNotes": "Pas de notes", + "message": "Envoyer", + "messages": "Messages", + "feed": "Flux", + "notes": "Notes", + "pay": "Payer", + "profilePage": "Page de profil", + "nostrPubkey": "Clé publique nostr" + }, + "news": { + "header": "Nouvelles", + "subheader": "Dernières Informations" + }, + "payments": { + "addFunds": "Ajouter d'argent", + "takeFunds": "Prendre d'argent", + "show": "Démontrer", + "hide": "Cacher", + "qr": "QR", + "copyLink": "Copier le Lien", + "copyText": "Copier le Texte", + "openLink": "Ouvrer le Portefeuille", + "moreOptions": "Plus d'options", + "total": "Total", + "gainLoss": "Hausse/Perte", + "presentValue": "Valuer au présent", + "amount": "Quantité", + "date": "Date", + "id": "ID de Transaction", + "details": "Détails de Transaction", + "header": "Paiements", + "preimage": "Preuve de paiement", + "type": "Type", + "time": "Heure", + "sent": "Envoyé", + "received": "Reçu", + "empty": "Vous n'avez pas de transactions.", + "send": "Envoyer", + "next": "Prochain", + "sendTo": "Envoyer à", + "to": "à", + "pay": "Payer", + "requesting": "demande", + "sentInvoiceTo": "Envoyé une facture à", + "sentRequestTo": "Demande envoyé!", + "pleasePay": "S'il vous plait payez", + "day": "Jour", + "week": "Semaine", + "month": "Mois", + "all": "Toutes", + "export": "Exporter", + "invoice": "Facture", + "readyToPay": "est prêt a payer", + "describeOrder": "Utiliser ce champ pour décrire votre commande", + "requestAnInvoice": "Demander une facture", + "submit": "Soumettre", + "isRequesting": "demande", + "invoiceFrom": "Facture de", + "awaiting": "Attendant", + "tapAnywhere": "Taper n'importe où pour continuer", + "from": "de", + "redeem": "Racheter", + "redeemed": "Racheté", + "redeemedBy": "Racheté par", + "sendMessage": "Écrire une message...", + "fundsClaimed": "Les fonds sont réclamés!", + "print": "Imprime", + "create": "Crée", + "fund": "Finance" + } } diff --git a/src/locales/pt.json b/src/locales/pt.json index f4fb5408..dcf6ecdb 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -1,36 +1,36 @@ { - "404": { - "404": "404", - "pageNotFound": "Página não encontrada." - }, - "about": { - "header": "Sobre", - "p1": "O Coinos é uma aplicação de pagamento de bitcoin de código aberto. O desenvolvimento começou em Vancouver em Setembro de 2012 como forma de fornecer aos comerciantes locais uma forma conveniente de aceitar pagamentos de bitcoin.", - "p2": "Você pode baixar o código fonte para executar uma instância e ligá-lo aos seus próprios nós de bitcoin e da Lightning Network. Vamos ajudá-lo a começar a ser um depositário benevolente e fornecedor de pagamentos para a sua comunidade..", - "p3": "As transferências de conta entre utilizadores no nosso servidor são feitas off-chain, por isso são completamente gratuitas, instantâneas e ilimitadas.", - "team": { - "header": "O time", - "adam": { - "name": "Adam Soltys", - "position": "Responsável Técnico Principal", - "bio": "Adam é um desenvolvedor web e um entusiasta do bitcoin que acredita que o dinheiro sólido é a chave para aumentar a prosperidade global. Ele vive em North Vancouver com a sua esposa Chelsea e dois filhos." - }, - "cole": { - "name": "Cole", - "position": "Profissional Certificado de Bitcoin", - "bio": "Cole é um indivíduo motivado com uma paixão pela liberdade. Encontrou-se com o bitcoin em 2016 e estudou-o em profundidade. Agora quer introduzir a rede lightning ao maior número de utilizadores possível." - } - }, - "locations": { - "header": "Locais", - "description": "Aqui estão algumas empresas que estão aceitando bitcoin com a Coinos. Esperamos que o seu seja o próximo!" - } - }, - "error": { - "header": "Erro", - "message": "Por favor tente novamente ou contate o suporte.", - "signIn": "Por favor, faça o login para continuar." - }, + "404": { + "404": "404", + "pageNotFound": "Página não encontrada." + }, + "about": { + "header": "Sobre", + "p1": "O Coinos é uma aplicação de pagamento de bitcoin de código aberto. O desenvolvimento começou em Vancouver em Setembro de 2012 como forma de fornecer aos comerciantes locais uma forma conveniente de aceitar pagamentos de bitcoin.", + "p2": "Você pode baixar o código fonte para executar uma instância e ligá-lo aos seus próprios nós de bitcoin e da Lightning Network. Vamos ajudá-lo a começar a ser um depositário benevolente e fornecedor de pagamentos para a sua comunidade..", + "p3": "As transferências de conta entre utilizadores no nosso servidor são feitas off-chain, por isso são completamente gratuitas, instantâneas e ilimitadas.", + "team": { + "header": "O time", + "adam": { + "name": "Adam Soltys", + "position": "Responsável Técnico Principal", + "bio": "Adam é um desenvolvedor web e um entusiasta do bitcoin que acredita que o dinheiro sólido é a chave para aumentar a prosperidade global. Ele vive em North Vancouver com a sua esposa Chelsea e dois filhos." + }, + "cole": { + "name": "Cole", + "position": "Profissional Certificado de Bitcoin", + "bio": "Cole é um indivíduo motivado com uma paixão pela liberdade. Encontrou-se com o bitcoin em 2016 e estudou-o em profundidade. Agora quer introduzir a rede lightning ao maior número de utilizadores possível." + } + }, + "locations": { + "header": "Locais", + "description": "Aqui estão algumas empresas que estão aceitando bitcoin com a Coinos. Esperamos que o seu seja o próximo!" + } + }, + "error": { + "header": "Erro", + "message": "Por favor tente novamente ou contate o suporte.", + "signIn": "Por favor, faça o login para continuar." + }, "faq": { "header": "FAQ", "why": { @@ -70,240 +70,240 @@ "answer": "Gástalo en otros comerciantes o paga tus gastos con él al conseguir que tu personal, proveedores y arrendador lo acepten." } }, - "footer": { - "company": "Empresa", - "followUs": "Siga-nos", - "github": "GitHub", - "linktree": "Linktree", - "jobs": "Empregos", - "support": "Suporte", - "telegram": "Telegram", - "twitter": "Twitter", - "documentation": "Documentação" - }, - "howItWorks": { - "header": "Como funciona", - "step1": { - "header": "1. Crie sua conta", - "description": "Um nome de usuário e senha é tudo o que você precisa para começar." - }, - "step2": { - "header": "2. Solicite pagamentos", - "description": "O mesmo que você normalmente faria, mas com bitcoin." - }, - "step3": { - "header": "3. Pronto!", - "description": "Gaste sua moeda imediatamente ou guarde-a e veja-a crescer, a escolha é sua." - } - }, - "invoice": { - "addTip": "Adicionar gorjeta", - "addTipq": "Adicionar gorjeta?", - "done": "Feito", - "paymentSuccessful": "Pagamento recebido!", - "invoice": "Fatura", - "total": "Total", - "tip": "Gorjeta" - }, - "jobs": { - "header": "Vagas de emprego", - "subheader": "Atualmente abertas", - "pitch1": "Coinos está procurando ativamente desenvolvedores para se juntar à nossa crescente equipe (tempo integral ou meio-período, trabalho remoto OK).", - "pitch2": "Entusiasmado com o trabalho em bitcoin open source? Então dê uma olhada em nossos repositórios em", - "github": "GitHub", - "pitch3": "Se você acha que pode causar um impacto, gostaríamos muito de ouvir de você. Envie-nos seu currículo para" - }, - "landing": { - "header_before_icon": "O", - "header_with_icon": "jeito mais fácil", - "header_after_icon": "de iniciar no Bitcoin.", - "subheader": "Uma carteira web simples, mas poderosa para todos. Não requer software nem hardware.", - "info1": { - "title": "Pagamentos instantâneos via Lightning", - "description": "Facilitamos para os comerciantes o recebimento de pagamentos de varejo. Nada de esperar por liquidações bancárias ou se preocupar com chargebacks. Liquidação instantânea, em segundos. Diga adeus a 3% de taxas de processamento de cartão de crédito.." - }, - "info2": { - "title": "Simples de usar", - "description1": "Basta inserir a quantia em sua moeda local, compartilhar o QR code e pronto..", - "description2": "Nós fazemos todo o trabalho pesado para que você possa se concentrar no seu negócio.", - "description3": "Você sempre tem a opção de tomar para sí a custódia de seus fundos, retirando-se imediatamente a qualquer momento." - }, - "info3": { - "title": "Personalise do seu jeito!", - "description": "Personalize a aparência de sua página de perfil, ou mantenha o padrão - você escolhe." - } - }, - "login": { - "optional": "Opcional", - "token": "2FA Token", - "username": "Usuário", - "password": "Senha", - "email": "Email", - "passwordRecommendation": "Recomendamos uma senha longa e aleatória gerada com um gerenciador de senhas.", - "forgotUserOrPassword": "Esqueceu o usuário ou senha?", - "haveAccount": "Já tem uma conta?", - "noAccount": "Não tem uma conta?", - "register": "Registre", - "signIn": "Entrar", - "failed": "Falha no login" - }, - "nav": { - "profile": "Perfil", - "startInSeconds": "Inicie em segundos", - "account": "Conta", - "signIn": "Entrar", - "signOut": "Sair", - "dashboard": "Painel de controle", - "settings": "Ajustes", - "support": "Suporte" - }, - "newSiteHeader": "Join us at our <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">launch party</a> in Vancouver on October 26! Looking for the old coinos? Visit <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", - "news": { - "header": "Notícias", - "subheader": "Últimos Updates", - "update1": "" - }, - "user": { + "footer": { + "company": "Empresa", + "followUs": "Siga-nos", + "github": "GitHub", + "linktree": "Linktree", + "jobs": "Empregos", + "support": "Suporte", + "telegram": "Telegram", + "twitter": "Twitter", + "documentation": "Documentação" + }, + "howItWorks": { + "header": "Como funciona", + "step1": { + "header": "1. Crie sua conta", + "description": "Um nome de usuário e senha é tudo o que você precisa para começar." + }, + "step2": { + "header": "2. Solicite pagamentos", + "description": "O mesmo que você normalmente faria, mas com bitcoin." + }, + "step3": { + "header": "3. Pronto!", + "description": "Gaste sua moeda imediatamente ou guarde-a e veja-a crescer, a escolha é sua." + } + }, + "invoice": { + "addTip": "Adicionar gorjeta", + "addTipq": "Adicionar gorjeta?", + "done": "Feito", + "paymentSuccessful": "Pagamento recebido!", + "invoice": "Fatura", + "total": "Total", + "tip": "Gorjeta" + }, + "jobs": { + "header": "Vagas de emprego", + "subheader": "Atualmente abertas", + "pitch1": "Coinos está procurando ativamente desenvolvedores para se juntar à nossa crescente equipe (tempo integral ou meio-período, trabalho remoto OK).", + "pitch2": "Entusiasmado com o trabalho em bitcoin open source? Então dê uma olhada em nossos repositórios em", + "github": "GitHub", + "pitch3": "Se você acha que pode causar um impacto, gostaríamos muito de ouvir de você. Envie-nos seu currículo para" + }, + "landing": { + "header_before_icon": "O", + "header_with_icon": "jeito mais fácil", + "header_after_icon": "de iniciar no Bitcoin.", + "subheader": "Uma carteira web simples, mas poderosa para todos. Não requer software nem hardware.", + "info1": { + "title": "Pagamentos instantâneos via Lightning", + "description": "Facilitamos para os comerciantes o recebimento de pagamentos de varejo. Nada de esperar por liquidações bancárias ou se preocupar com chargebacks. Liquidação instantânea, em segundos. Diga adeus a 3% de taxas de processamento de cartão de crédito.." + }, + "info2": { + "title": "Simples de usar", + "description1": "Basta inserir a quantia em sua moeda local, compartilhar o QR code e pronto..", + "description2": "Nós fazemos todo o trabalho pesado para que você possa se concentrar no seu negócio.", + "description3": "Você sempre tem a opção de tomar para sí a custódia de seus fundos, retirando-se imediatamente a qualquer momento." + }, + "info3": { + "title": "Personalise do seu jeito!", + "description": "Personalize a aparência de sua página de perfil, ou mantenha o padrão - você escolhe." + } + }, + "login": { + "optional": "Opcional", + "token": "2FA Token", + "username": "Usuário", + "password": "Senha", + "email": "Email", + "passwordRecommendation": "Recomendamos uma senha longa e aleatória gerada com um gerenciador de senhas.", + "forgotUserOrPassword": "Esqueceu o usuário ou senha?", + "haveAccount": "Já tem uma conta?", + "noAccount": "Não tem uma conta?", + "register": "Registre", + "signIn": "Entrar", + "failed": "Falha no login" + }, + "nav": { + "profile": "Perfil", + "startInSeconds": "Inicie em segundos", + "account": "Conta", + "signIn": "Entrar", + "signOut": "Sair", + "dashboard": "Painel de controle", + "settings": "Ajustes", + "support": "Suporte" + }, + "newSiteHeader": "Join us at our <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">launch party</a> in Vancouver on October 26! Looking for the old coinos? Visit <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", + "news": { + "header": "Notícias", + "subheader": "Últimos Updates", + "update1": "" + }, + "user": { "requestPayment": "Gerar fatura", "welcome": "Bem-vindo! O saldo da sua conta está vazio. Você pode começar fazendo um depósito ou recebendo um pagamento.", "showBalance": "Mostrar Saldo", - "bitcoin": "Bitcoin", - "lightning": "Lightning", - "invoice": "Fatura", - "unfollow": "Deixar de seguir", - "following": "Seguindo", - "follow": "Seguir", - "followers": "Seguidores", - "remove": "Remover", - "noNotes": "Nenhuma nota ainda", - "message": "Mensagem", - "messages": "Mensagens", - "feed": "Feed", - "notes": "Notas", - "pay": "Pagar", - "scanToPayBTC": "Escaneie para pagar com bitcoin", - "this": "Este", - "lightningAddress": "Endereço lightning", - "dashboard": { - "go": "Ir", - "dashboard": "Painel de controle", - "accountBalance": "Saldo na conta", - "bitcoinPrice": "Preço do bitcoin:", - "conversionFeeWarning": "Será cobrada uma taxa de retirada de 1% sobre o valor.", - "dataFromBinance": "Data from <span class=\"text-black\">Binance</span>.", - "fetchingRate": "buscando taxa...", - "optionalNote": "Nota (Opcional)", - "send": "Enviar", - "receive": "Receber", - "withdraw": "Enviar", - "deposit": "Deposit", - "voucher": "Voucher" - }, - "receive": { - "lessThan1BTCWarning": "Favor inserir uma quantidade inferior a 1 bitcoin.", - "showQR": "Mostrar QR code" - }, - "send": { - "contacts": "Contatos", - "scan": "Escanear", - "paste": "Colar", - "next": "Continuar", - "placeholder": "Colar um endereço de bitcoin, fatura lightning ou nome de usuário na coinos" - }, - "settings": { - "saveSettings": "Salvar ajustes", - "bannerImage": "Imagem do banner", - "profileImage": "Imagem do perfil", - "oneTimeCode": "Entre com o código único", - "accountId": "ID da conta", - "header": "Ajustes", - "ACCOUNT": "CONTA", - "POINT_OF_SALE": "PREFERÊNCIAS", - "SECURITY": "SEGURANÇA", - "locale": "Idioma", - "username": "Usuário", - "email": "Email", - "newPassword": "Alterar senhar", - "about": "Sobre", - "aboutPlaceholder": "Um pouco sobre você ou seu endereço comercial e horários", - "emailNotifications": "Notificações por email", - "emailNotificationsDescription": "Ser notificado por e-mail quando ocorrer uma mudança em sua conta ou quando as pessoas lhe enviarem pagamentos.", - "smsNotifications": "Notificações por SMS", - "smsNotificationsDescription": "A Coinos lhe enviará notificações por SMS quando isto estiver habilitado.", - "theme": "Cor do tema", - "tipPrompt": "Mostrar opção gorjetas", - "tipPromptDescription": "Pergunte às pessoas se elas querem acrescentar uma gorjeta antes de pagar sua fatura", - "displayName": "Mostrar nome", - "themeDescription": "Selecionar entre os temas de cor padrão.", - "localCurrency": "Moeda", - "btcUnit": "Mostrar bitcoin em:", - "bitcoin": "bitcoin", - "minutes": "minutos", - "hour": "hora", - "hours": "horas", - "satoshis": "Satoshis", - "securityPIN": "PIN de segurança", - "securityPINDescription": "Estabeleça um PIN de segurança que será usado para desbloquear suas configurações e para enviar pagamentos.", - "setPIN": "Insira o PIN", - "autoLock": "Bloqueio automático", - "autoLockDescription": "Sua conta entrará em um modo seguro, exigindo que você reentre suas credenciais após um tempo especificado:", - "twofa": "Eutenticação de 2 fatores", - "twofaDescription": "Adicione um nível extra de segurança ao seu dispositivo, permitindo autenticação de 2 fatores.", - "twofaSetup": "Habilitar o 2FA", - "twofaDisable": "Desabilitar o 2FA", - "select": "Selecionar", - "verifyPIN": "Verificar PIN", - "enablePIN": "Habilitar PIN", - "disablePIN": "Desabilitar PIN" - }, - "support": { - "header": "Suporte", - "email": "Email", - "accountName": "Usuário", - "message": "Mensagem", - "send": "Enviar" - } - }, - "payments": { - "amount": "Quantidade", - "date": "Data", - "id": "ID de transação", - "details": "Details da transação", - "header": "Pagamentos", - "preimage": "Prova de pagamento", - "type": "Tipo", - "time": "Hora", - "sent": "Enviado", - "received": "Recebido", - "empty": "Nenhuma transação ainda.", - "send": "Enviar", - "next": "Continuar", - "sendersChoice": "A escolha do remetente", - "sendTo": "Enviar para", - "to": "para", - "pay": "Pagar", - "requesting": "solicitando", - "sentInvoiceTo": "Fatura enviada para", - "sentRequestTo": "Solicitação enviada!", - "pleasePay": "Por favor pague", - "day": "Dia", - "week": "Semana", - "month": "Mês", - "all": "Todos", - "export": "Exportar", - "invoice": "Fatura", - "readyToPay": "está pronta para pagar", - "describeOrder": "Use este campo para descrever seu pedido", - "requestAnInvoice": "Solicitar Fatura", - "submit": "O envio", - "isRequesting": "foi solicitado", - "invoiceFrom": "Fatura de", - "awaiting": "Aguardando", - "tapAnywhere": "Toque em qualquer lugar para continuar", - "from": "de", - "redeem": "Resgatar", - "redeemed": "Resgatado", - "redeemedBy": "Resgatado por", - "sendMessage": "Escreva uma mensagem..." - } + "bitcoin": "Bitcoin", + "lightning": "Lightning", + "invoice": "Fatura", + "unfollow": "Deixar de seguir", + "following": "Seguindo", + "follow": "Seguir", + "followers": "Seguidores", + "remove": "Remover", + "noNotes": "Nenhuma nota ainda", + "message": "Mensagem", + "messages": "Mensagens", + "feed": "Feed", + "notes": "Notas", + "pay": "Pagar", + "scanToPayBTC": "Escaneie para pagar com bitcoin", + "this": "Este", + "lightningAddress": "Endereço lightning", + "dashboard": { + "go": "Ir", + "dashboard": "Painel de controle", + "accountBalance": "Saldo na conta", + "bitcoinPrice": "Preço do bitcoin:", + "conversionFeeWarning": "Será cobrada uma taxa de retirada de 1% sobre o valor.", + "dataFromBinance": "Data from <span class=\"text-black\">Binance</span>.", + "fetchingRate": "buscando taxa...", + "optionalNote": "Nota (Opcional)", + "send": "Enviar", + "receive": "Receber", + "withdraw": "Enviar", + "deposit": "Deposit", + "voucher": "Voucher" + }, + "receive": { + "lessThan1BTCWarning": "Favor inserir uma quantidade inferior a 1 bitcoin.", + "showQR": "Mostrar QR code" + }, + "send": { + "contacts": "Contatos", + "scan": "Escanear", + "paste": "Colar", + "next": "Continuar", + "placeholder": "Colar um endereço de bitcoin, fatura lightning ou nome de usuário na coinos" + }, + "settings": { + "saveSettings": "Salvar ajustes", + "bannerImage": "Imagem do banner", + "profileImage": "Imagem do perfil", + "oneTimeCode": "Entre com o código único", + "accountId": "ID da conta", + "header": "Ajustes", + "ACCOUNT": "CONTA", + "POINT_OF_SALE": "PREFERÊNCIAS", + "SECURITY": "SEGURANÇA", + "locale": "Idioma", + "username": "Usuário", + "email": "Email", + "newPassword": "Alterar senhar", + "about": "Sobre", + "aboutPlaceholder": "Um pouco sobre você ou seu endereço comercial e horários", + "emailNotifications": "Notificações por email", + "emailNotificationsDescription": "Ser notificado por e-mail quando ocorrer uma mudança em sua conta ou quando as pessoas lhe enviarem pagamentos.", + "smsNotifications": "Notificações por SMS", + "smsNotificationsDescription": "A Coinos lhe enviará notificações por SMS quando isto estiver habilitado.", + "theme": "Cor do tema", + "tipPrompt": "Mostrar opção gorjetas", + "tipPromptDescription": "Pergunte às pessoas se elas querem acrescentar uma gorjeta antes de pagar sua fatura", + "displayName": "Mostrar nome", + "themeDescription": "Selecionar entre os temas de cor padrão.", + "localCurrency": "Moeda", + "btcUnit": "Mostrar bitcoin em:", + "bitcoin": "bitcoin", + "minutes": "minutos", + "hour": "hora", + "hours": "horas", + "satoshis": "Satoshis", + "securityPIN": "PIN de segurança", + "securityPINDescription": "Estabeleça um PIN de segurança que será usado para desbloquear suas configurações e para enviar pagamentos.", + "setPIN": "Insira o PIN", + "autoLock": "Bloqueio automático", + "autoLockDescription": "Sua conta entrará em um modo seguro, exigindo que você reentre suas credenciais após um tempo especificado:", + "twofa": "Eutenticação de 2 fatores", + "twofaDescription": "Adicione um nível extra de segurança ao seu dispositivo, permitindo autenticação de 2 fatores.", + "twofaSetup": "Habilitar o 2FA", + "twofaDisable": "Desabilitar o 2FA", + "select": "Selecionar", + "verifyPIN": "Verificar PIN", + "enablePIN": "Habilitar PIN", + "disablePIN": "Desabilitar PIN" + }, + "support": { + "header": "Suporte", + "email": "Email", + "accountName": "Usuário", + "message": "Mensagem", + "send": "Enviar" + } + }, + "payments": { + "amount": "Quantidade", + "date": "Data", + "id": "ID de transação", + "details": "Details da transação", + "header": "Pagamentos", + "preimage": "Prova de pagamento", + "type": "Tipo", + "time": "Hora", + "sent": "Enviado", + "received": "Recebido", + "empty": "Nenhuma transação ainda.", + "send": "Enviar", + "next": "Continuar", + "sendersChoice": "A escolha do remetente", + "sendTo": "Enviar para", + "to": "para", + "pay": "Pagar", + "requesting": "solicitando", + "sentInvoiceTo": "Fatura enviada para", + "sentRequestTo": "Solicitação enviada!", + "pleasePay": "Por favor pague", + "day": "Dia", + "week": "Semana", + "month": "Mês", + "all": "Todos", + "export": "Exportar", + "invoice": "Fatura", + "readyToPay": "está pronta para pagar", + "describeOrder": "Use este campo para descrever seu pedido", + "requestAnInvoice": "Solicitar Fatura", + "submit": "O envio", + "isRequesting": "foi solicitado", + "invoiceFrom": "Fatura de", + "awaiting": "Aguardando", + "tapAnywhere": "Toque em qualquer lugar para continuar", + "from": "de", + "redeem": "Resgatar", + "redeemed": "Resgatado", + "redeemedBy": "Resgatado por", + "sendMessage": "Escreva uma mensagem..." + } } diff --git a/src/locales/zh.json b/src/locales/zh.json index 8fc41c38..cd606216 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1,290 +1,290 @@ { - "404": { - "404": "404", - "pageNotFound": "Page not found." - }, - "about": { - "header": "About", - "p1": "Coinos is an open source bitcoin payment app. Development started in Vancouver in September 2012 as a way to provide local merchants with a convenient way to accept bitcoin payments.", - "p2": "You can download the source code to run an instance and connect it to your own bitcoin and lightning nodes. We'll help you get started being a benevolent custodian and payments provider for your community.", - "p3": "Account transfers amongst users on our server are done off-chain so they're completely free, instant, and unlimited.", - "team": { - "header": "The Team", - "adam": { - "name": "Adam Soltys", - "position": "Chief Technical Officer", - "bio": "Adam is a web developer and bitcoin enthusiast who believes sound money is key to increasing global prosperity. He lives in North Vancouver with his wife Chelsea and two children." - }, - "shauna": { - "name": "Shauna", - "position": "Operations & Marketing", - "bio": "Strategic & Project Management. Jack of all Trades. Can always find time to meet for coffee." - }, - "cole": { - "name": "Cole", - "position": "Certified Bitcoin Professional", - "bio": "Cole is a driven individual with a passion for freedom. He came across bitcoin in 2016 and studied it in depth. Now he wants to introduce the lightning network to as many users as possible." - }, - "john": { - "name": "John Barratt", - "position": "Full Stack Web Developer", - "bio": "When not coding you can find John out in the woods hiking all the great trails that BC has to offer." - } - }, - "locations": { - "header": "Locations", - "description": "Here are some businesses that are accepting bitcoin with Coinos. We hope that yours will be next!" - } - }, - "error": { - "header": "Error", - "message": "Please try again or contact support.", - "signIn": "Please sign in to continue." - }, - "faq": { - "header": "FAQ", - "cost": { - "question": "What does it cost?", - "answer": "It's free to register an account and receive payments over the lightning network. We charge a 0.1% fee to convert from lightning to bitcoin." - }, - "compatibility": { - "question": "Do I need any special device or software?", - "answer": "No. Coinos is web-based, so as long as you have an internet connection, you can use it on any device. Retail merchants might use it on a phone or a tablet." - }, - "safety": { - "question": "When do my funds settle?", - "answer": "Lightning payments are settled instantly. You can withdraw your balance to another wallet or exchange right after payments are received." - } - }, - "footer": { - "company": "Company", - "followUs": "Follow Us", - "github": "GitHub", - "jobs": "Jobs", - "support": "Support", - "telegram": "Telegram", - "twitter": "Twitter", - "documentation": "Documentation" - }, - "howItWorks": { - "header": "How it works", - "step1": { - "header": "1. Create your account", - "description": "A username and password is all you need to get started." - }, - "step2": { - "header": "2. Ask for payments", - "description": "The same as you normally would, but with bitcoin." - }, - "step3": { - "header": "3. That's all!", - "description": "Spend your coin at other merchants or save it for a rainy day, the choice is yours." - } - }, - "invoice": { - "addTip": "Add a tip", - "addTipq": "Add a tip?", - "done": "Done", - "paymentSuccessful": "Payment received!", - "invoice": "Invoice", - "total": "Total", - "tip": "Tip" - }, - "jobs": { - "header": "Jobs", - "subheader": "Current Openings", - "pitch1": "Coinos is actively looking for developers to join our growing team (full-time or part-time, remote working OK).", - "pitch2": "Excited about working on open source bitcoin? Then take a look at our repositories on", - "github": "GitHub", - "pitch3": "If you think you can make an impact, we'd love to hear from you. Send us your resume to" - }, - "landing": { - "header_before_icon": "The", - "header_with_icon": "easiest", - "header_after_icon": "way to accept bitcoin in your store.", - "subheader": "Grow your customer base and revenue by accepting bitcoin with an easy-to-use web app. No software or hardware required.", - "info1": { - "title": "Instant payments via Lightning", - "description": "The moment your customer sends a payment, it’s there. No waiting for bank settlements or worrying about chargebacks. Instant settlement, in seconds. Say goodbye to 3% credit card processing fees." - }, - "info2": { - "title": "Simple to use", - "description1": "Just enter an amount in your local currency, share the QR code and you’re done.", - "description2": "We do all the heavy lifting so you can focus on your business.", - "description3": "You always have the option of taking custody of your funds by withdrawing instantly at any time." - }, - "info3": { - "title": "Brand it your way!", - "description": "Customize your point of sale look and feel, or keep it on default - it's totally up to you." - } - }, - "login": { - "optional": "Optional", - "token": "2FA Token", - "username": "Username", - "password": "Password", - "email": "Email", - "passwordRecommendation": "We recommend a long, random password generated with a password manager.", - "forgotUserOrPassword": "Forgot Username or Password?", - "haveAccount": "Already have an account?", - "noAccount": "Don't have an account?", - "register": "Register", - "signIn": "Sign In", - "failed": "Login failed" - }, - "nav": { - "profile": "Profile", - "startInSeconds": "Start in Seconds", - "account": "Account", - "signIn": "Sign In", - "signOut": "Sign Out", - "dashboard": "Dashboard", - "settings": "Settings", - "support": "Support" - }, - "newSiteHeader": "Join us at our <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">launch party</a> in Vancouver on October 26! Looking for the old coinos? Visit <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", - "news": { - "header": "News", - "subheader": "Latest Updates", - "update1": "" - }, - "user": { - "bitcoin": "Bitcoin", - "lightning": "Lightning", - "invoice": "Invoice", - "unfollow": "Unfollow", - "following": "Following", - "follow": "Follow", - "followers": "Followers", - "remove": "Remove", - "noNotes": "No notes yet", - "message": "Message", - "messages": "Messages", - "feed": "Feed", - "notes": "Notes", - "pay": "Pay", - "scanToPayBTC": "Scan to pay with bitcoin", - "this": "This", - "lightningAddress": "Lightning address", - "dashboard": { - "go": "Go", - "dashboard": "Dashboard", - "accountBalance": "Account Balance", - "bitcoinPrice": "bitcoin Price:", - "conversionFeeWarning": "You will be charged a 1% withdrawal fee on the amount.", - "dataFromBinance": "Data from <span class=\"text-black\">Binance</span>.", - "fetchingRate": "fetching rate...", - "optionalNote": "Note (Optional)", - "send": "Send", - "receive": "Receive", - "withdraw": "Send", - "deposit": "Deposit", - "voucher": "Voucher" - }, - "receive": { - "lessThan1BTCWarning": "Please enter an amount less than 1 bitcoin.", - "showQR": "Show QR" - }, - "send": { - "contacts": "Contacts", - "scan": "Scan", - "paste": "Paste", - "next": "Next", - "placeholder": "Paste a bitcoin address, lightning invoice or coinos username" - }, - "settings": { - "bannerImage": "Banner Image", - "profileImage": "Profile Image", - "oneTimeCode": "Enter one-time code", - "accountId": "Account Secret", - "header": "Settings", - "ACCOUNT": "ACCOUNT", - "POINT_OF_SALE": "PREFERENCES", - "SECURITY": "SECURITY", - "locale": "Language", - "username": "Username", - "email": "Email", - "newPassword": "Change Password", - "about": "About", - "aboutPlaceholder": "A little something about you or your business address and hours", - "emailNotifications": "Email Notifications", - "emailNotificationsDescription": "Get notified via email when a change occurs on your account or when people send you payments.", - "smsNotifications": "SMS Notifications", - "smsNotificationsDescription": "Coinos will send you text notifications when this is enabled.", - "theme": "Color Theme", - "tipPrompt": "Prompt for tips", - "tipPromptDescription": "Ask people if they want to add a tip before paying their invoices", - "displayName": "Display name", - "themeDescription": "Select from the default color themes.", - "localCurrency": "Currency", - "btcUnit": "Display bitcoin in:", - "bitcoin": "bitcoin", - "minutes": "minutes", - "hour": "hour", - "hours": "hours", - "satoshis": "Satoshis", - "securityPIN": "Security PIN", - "securityPINDescription": "Set a security pin that will be used to unlock your settings and to send outgoing payments.", - "setPIN": "Set PIN", - "autoLock": "Auto-Lock Timer", - "autoLockDescription": "Your account will go into a secure mode, requiring you to re-enter your credentials after a specified time:", - "twofa": "2-Factor Authentication", - "twofaDescription": "Add an extra level of security to your device by enabling 2-factor authentication.", - "twofaSetup": "Set up 2FA", - "twofaDisable": "Disable 2FA", - "select": "Select", - "verifyPIN": "Verify PIN", - "enablePIN": "Enable PIN", - "disablePIN": "Disable PIN" - }, - "support": { - "header": "Support", - "email": "Email", - "accountName": "Account Name", - "message": "Message", - "send": "Send" - } - }, - "payments": { - "amount": "Amount", - "date": "Date", - "id": "Transaction ID", - "details": "Transaction Details", - "header": "Payments", - "preimage": "Proof of payment", - "type": "Type", - "time": "Time", - "sent": "Sent", - "received": "Received", - "empty": "No transactions yet.", - "send": "Send", - "next": "Next", - "sendersChoice": "Sender's choice", - "sendTo": "Send to", - "to": "to", - "pay": "Pay", - "requesting": "is requesting", - "sentInvoiceTo": "Sent invoice to", - "sentRequestTo": "Request sent!", - "pleasePay": "Please pay", - "day": "Day", - "week": "Week", - "month": "Month", - "all": "All", - "export": "Export", - "invoice": "Invoice", - "readyToPay": "is ready to pay", - "describeOrder": "Use this field to describe your order", - "requestAnInvoice": "Request Invoice", - "submit": "Submit", - "isRequesting": "is requesting", - "invoiceFrom": "Invoice from", - "awaiting": "Awaiting", - "tapAnywhere": "Tap anywhere to continue", - "from": "from", - "redeem": "Redeem", - "redeemed": "Redeemed", - "redeemedBy": "Redeemed by", - "sendMessage": "Write a message..." - } + "404": { + "404": "404", + "pageNotFound": "Page not found." + }, + "about": { + "header": "About", + "p1": "Coinos is an open source bitcoin payment app. Development started in Vancouver in September 2012 as a way to provide local merchants with a convenient way to accept bitcoin payments.", + "p2": "You can download the source code to run an instance and connect it to your own bitcoin and lightning nodes. We'll help you get started being a benevolent custodian and payments provider for your community.", + "p3": "Account transfers amongst users on our server are done off-chain so they're completely free, instant, and unlimited.", + "team": { + "header": "The Team", + "adam": { + "name": "Adam Soltys", + "position": "Chief Technical Officer", + "bio": "Adam is a web developer and bitcoin enthusiast who believes sound money is key to increasing global prosperity. He lives in North Vancouver with his wife Chelsea and two children." + }, + "shauna": { + "name": "Shauna", + "position": "Operations & Marketing", + "bio": "Strategic & Project Management. Jack of all Trades. Can always find time to meet for coffee." + }, + "cole": { + "name": "Cole", + "position": "Certified Bitcoin Professional", + "bio": "Cole is a driven individual with a passion for freedom. He came across bitcoin in 2016 and studied it in depth. Now he wants to introduce the lightning network to as many users as possible." + }, + "john": { + "name": "John Barratt", + "position": "Full Stack Web Developer", + "bio": "When not coding you can find John out in the woods hiking all the great trails that BC has to offer." + } + }, + "locations": { + "header": "Locations", + "description": "Here are some businesses that are accepting bitcoin with Coinos. We hope that yours will be next!" + } + }, + "error": { + "header": "Error", + "message": "Please try again or contact support.", + "signIn": "Please sign in to continue." + }, + "faq": { + "header": "FAQ", + "cost": { + "question": "What does it cost?", + "answer": "It's free to register an account and receive payments over the lightning network. We charge a 0.1% fee to convert from lightning to bitcoin." + }, + "compatibility": { + "question": "Do I need any special device or software?", + "answer": "No. Coinos is web-based, so as long as you have an internet connection, you can use it on any device. Retail merchants might use it on a phone or a tablet." + }, + "safety": { + "question": "When do my funds settle?", + "answer": "Lightning payments are settled instantly. You can withdraw your balance to another wallet or exchange right after payments are received." + } + }, + "footer": { + "company": "Company", + "followUs": "Follow Us", + "github": "GitHub", + "jobs": "Jobs", + "support": "Support", + "telegram": "Telegram", + "twitter": "Twitter", + "documentation": "Documentation" + }, + "howItWorks": { + "header": "How it works", + "step1": { + "header": "1. Create your account", + "description": "A username and password is all you need to get started." + }, + "step2": { + "header": "2. Ask for payments", + "description": "The same as you normally would, but with bitcoin." + }, + "step3": { + "header": "3. That's all!", + "description": "Spend your coin at other merchants or save it for a rainy day, the choice is yours." + } + }, + "invoice": { + "addTip": "Add a tip", + "addTipq": "Add a tip?", + "done": "Done", + "paymentSuccessful": "Payment received!", + "invoice": "Invoice", + "total": "Total", + "tip": "Tip" + }, + "jobs": { + "header": "Jobs", + "subheader": "Current Openings", + "pitch1": "Coinos is actively looking for developers to join our growing team (full-time or part-time, remote working OK).", + "pitch2": "Excited about working on open source bitcoin? Then take a look at our repositories on", + "github": "GitHub", + "pitch3": "If you think you can make an impact, we'd love to hear from you. Send us your resume to" + }, + "landing": { + "header_before_icon": "The", + "header_with_icon": "easiest", + "header_after_icon": "way to accept bitcoin in your store.", + "subheader": "Grow your customer base and revenue by accepting bitcoin with an easy-to-use web app. No software or hardware required.", + "info1": { + "title": "Instant payments via Lightning", + "description": "The moment your customer sends a payment, it’s there. No waiting for bank settlements or worrying about chargebacks. Instant settlement, in seconds. Say goodbye to 3% credit card processing fees." + }, + "info2": { + "title": "Simple to use", + "description1": "Just enter an amount in your local currency, share the QR code and you’re done.", + "description2": "We do all the heavy lifting so you can focus on your business.", + "description3": "You always have the option of taking custody of your funds by withdrawing instantly at any time." + }, + "info3": { + "title": "Brand it your way!", + "description": "Customize your point of sale look and feel, or keep it on default - it's totally up to you." + } + }, + "login": { + "optional": "Optional", + "token": "2FA Token", + "username": "Username", + "password": "Password", + "email": "Email", + "passwordRecommendation": "We recommend a long, random password generated with a password manager.", + "forgotUserOrPassword": "Forgot Username or Password?", + "haveAccount": "Already have an account?", + "noAccount": "Don't have an account?", + "register": "Register", + "signIn": "Sign In", + "failed": "Login failed" + }, + "nav": { + "profile": "Profile", + "startInSeconds": "Start in Seconds", + "account": "Account", + "signIn": "Sign In", + "signOut": "Sign Out", + "dashboard": "Dashboard", + "settings": "Settings", + "support": "Support" + }, + "newSiteHeader": "Join us at our <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">launch party</a> in Vancouver on October 26! Looking for the old coinos? Visit <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", + "news": { + "header": "News", + "subheader": "Latest Updates", + "update1": "" + }, + "user": { + "bitcoin": "Bitcoin", + "lightning": "Lightning", + "invoice": "Invoice", + "unfollow": "Unfollow", + "following": "Following", + "follow": "Follow", + "followers": "Followers", + "remove": "Remove", + "noNotes": "No notes yet", + "message": "Message", + "messages": "Messages", + "feed": "Feed", + "notes": "Notes", + "pay": "Pay", + "scanToPayBTC": "Scan to pay with bitcoin", + "this": "This", + "lightningAddress": "Lightning address", + "dashboard": { + "go": "Go", + "dashboard": "Dashboard", + "accountBalance": "Account Balance", + "bitcoinPrice": "bitcoin Price:", + "conversionFeeWarning": "You will be charged a 1% withdrawal fee on the amount.", + "dataFromBinance": "Data from <span class=\"text-black\">Binance</span>.", + "fetchingRate": "fetching rate...", + "optionalNote": "Note (Optional)", + "send": "Send", + "receive": "Receive", + "withdraw": "Send", + "deposit": "Deposit", + "voucher": "Voucher" + }, + "receive": { + "lessThan1BTCWarning": "Please enter an amount less than 1 bitcoin.", + "showQR": "Show QR" + }, + "send": { + "contacts": "Contacts", + "scan": "Scan", + "paste": "Paste", + "next": "Next", + "placeholder": "Paste a bitcoin address, lightning invoice or coinos username" + }, + "settings": { + "bannerImage": "Banner Image", + "profileImage": "Profile Image", + "oneTimeCode": "Enter one-time code", + "accountId": "Account Secret", + "header": "Settings", + "ACCOUNT": "ACCOUNT", + "POINT_OF_SALE": "PREFERENCES", + "SECURITY": "SECURITY", + "locale": "Language", + "username": "Username", + "email": "Email", + "newPassword": "Change Password", + "about": "About", + "aboutPlaceholder": "A little something about you or your business address and hours", + "emailNotifications": "Email Notifications", + "emailNotificationsDescription": "Get notified via email when a change occurs on your account or when people send you payments.", + "smsNotifications": "SMS Notifications", + "smsNotificationsDescription": "Coinos will send you text notifications when this is enabled.", + "theme": "Color Theme", + "tipPrompt": "Prompt for tips", + "tipPromptDescription": "Ask people if they want to add a tip before paying their invoices", + "displayName": "Display name", + "themeDescription": "Select from the default color themes.", + "localCurrency": "Currency", + "btcUnit": "Display bitcoin in:", + "bitcoin": "bitcoin", + "minutes": "minutes", + "hour": "hour", + "hours": "hours", + "satoshis": "Satoshis", + "securityPIN": "Security PIN", + "securityPINDescription": "Set a security pin that will be used to unlock your settings and to send outgoing payments.", + "setPIN": "Set PIN", + "autoLock": "Auto-Lock Timer", + "autoLockDescription": "Your account will go into a secure mode, requiring you to re-enter your credentials after a specified time:", + "twofa": "2-Factor Authentication", + "twofaDescription": "Add an extra level of security to your device by enabling 2-factor authentication.", + "twofaSetup": "Set up 2FA", + "twofaDisable": "Disable 2FA", + "select": "Select", + "verifyPIN": "Verify PIN", + "enablePIN": "Enable PIN", + "disablePIN": "Disable PIN" + }, + "support": { + "header": "Support", + "email": "Email", + "accountName": "Account Name", + "message": "Message", + "send": "Send" + } + }, + "payments": { + "amount": "Amount", + "date": "Date", + "id": "Transaction ID", + "details": "Transaction Details", + "header": "Payments", + "preimage": "Proof of payment", + "type": "Type", + "time": "Time", + "sent": "Sent", + "received": "Received", + "empty": "No transactions yet.", + "send": "Send", + "next": "Next", + "sendersChoice": "Sender's choice", + "sendTo": "Send to", + "to": "to", + "pay": "Pay", + "requesting": "is requesting", + "sentInvoiceTo": "Sent invoice to", + "sentRequestTo": "Request sent!", + "pleasePay": "Please pay", + "day": "Day", + "week": "Week", + "month": "Month", + "all": "All", + "export": "Export", + "invoice": "Invoice", + "readyToPay": "is ready to pay", + "describeOrder": "Use this field to describe your order", + "requestAnInvoice": "Request Invoice", + "submit": "Submit", + "isRequesting": "is requesting", + "invoiceFrom": "Invoice from", + "awaiting": "Awaiting", + "tapAnywhere": "Tap anywhere to continue", + "from": "from", + "redeem": "Redeem", + "redeemed": "Redeemed", + "redeemedBy": "Redeemed by", + "sendMessage": "Write a message..." + } } diff --git a/src/params/uuid.js b/src/params/uuid.js index 1278e59e..bd32b496 100644 --- a/src/params/uuid.js +++ b/src/params/uuid.js @@ -1 +1 @@ -export let match = (p) => p.split('-').length > 4; +export let match = (p) => p.split("-").length > 4; diff --git a/src/routes/(app)/+error.svelte b/src/routes/(app)/+error.svelte index 36726e26..ed0bec3e 100644 --- a/src/routes/(app)/+error.svelte +++ b/src/routes/(app)/+error.svelte @@ -1,38 +1,38 @@ <script> - import { back } from '$lib/utils'; - import { Icon } from '$comp'; - import { page } from '$app/stores'; + import { back } from "$lib/utils"; + import { Icon } from "$comp"; + import { page } from "$app/stores"; - export let data; - let { user } = data; + export let data; + let { user } = data; - let link = user ? `/${user.username}` : '/'; + let link = user ? `/${user.username}` : "/"; </script> <div class="container px-4 max-w-xl mx-auto mt-10 space-y-5"> - <div class="w-full flex"> - <a href="/" class="mx-auto"> - <Icon icon="logo" /> - </a> - </div> + <div class="w-full flex"> + <a href="/" class="mx-auto"> + <Icon icon="logo" /> + </a> + </div> - <div class="flex justify-center items-center"> - <div class="shadow-xl rounded-3xl px-10 pt-5 pb-10 space-y-5 w-full mx-5"> - <div class="relative"> - <h1 class="text-2xl md:text-3xl font-semibold text-center">Error</h1> - </div> - <p class="text-secondary text-center">Something went wrong</p> - <p class="text-secondary text-center">{$page.error.message}</p> + <div class="flex justify-center items-center"> + <div class="shadow-xl rounded-3xl px-10 pt-5 pb-10 space-y-5 w-full mx-5"> + <div class="relative"> + <h1 class="text-2xl md:text-3xl font-semibold text-center">Error</h1> + </div> + <p class="text-secondary text-center">Something went wrong</p> + <p class="text-secondary text-center">{$page.error.message}</p> - <div class="flex justify-center py-24"> - <a href={link}> - <button - class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-xl" - > - Back - </button> - </a> - </div> - </div> - </div> + <div class="flex justify-center py-24"> + <a href={link}> + <button + class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-xl" + > + Back + </button> + </a> + </div> + </div> + </div> </div> diff --git a/src/routes/(app)/+layout.server.js b/src/routes/(app)/+layout.server.js index e80e1e64..9fcf6411 100644 --- a/src/routes/(app)/+layout.server.js +++ b/src/routes/(app)/+layout.server.js @@ -1,44 +1,48 @@ -import { error, redirect } from '@sveltejs/kit'; -import { get, post, auth, protectedRoutes } from '$lib/utils'; -import { PUBLIC_BTC as btc } from '$env/static/public'; +import { error, redirect } from "@sveltejs/kit"; +import { get, post, auth, protectedRoutes } from "$lib/utils"; +import { PUBLIC_BTC as btc } from "$env/static/public"; export async function load({ cookies, request, url, params }) { - let { pathname } = url; - let token = cookies.get('token'); - let rate, - rates = { USD: 1 }; + let { pathname } = url; + let token = cookies.get("token"); + let rate, + rates = { USD: 1 }; - let user; - if (token) { - try { - user = await get('/me', auth(cookies)); - } catch (e) {} - } + let user; + if (token) { + try { + user = await get("/me", auth(cookies)); + } catch (e) {} + } - if (user?.needsMigration) { - throw redirect(307, `/migrate`); - } + if (user?.needsMigration) { + throw redirect(307, `/migrate`); + } - if (user && ['/login', '/register'].includes(pathname) && request.method === 'GET') { - throw redirect(307, `/${user.username}`); - } + if ( + user && + ["/login", "/register"].includes(pathname) && + request.method === "GET" + ) { + throw redirect(307, `/${user.username}`); + } - if ( - protectedRoutes.find((p) => pathname.match(p)) && - (!user || user.username !== params.username) - ) { - throw redirect(307, '/login'); - } + if ( + protectedRoutes.find((p) => pathname.match(p)) && + (!user || user.username !== params.username) + ) { + throw redirect(307, "/login"); + } - if (user && !user.pubkey && !pathname.includes('generate')) - throw redirect(307, `/${user.username}/generate`); + if (user && !user.pubkey && !pathname.includes("generate")) + throw redirect(307, `/${user.username}/generate`); - try { - rate = await get('/rate'); - rates = await get('/rates'); - } catch (e) { - console.log(e); - } + try { + rate = await get("/rate"); + rates = await get("/rates"); + } catch (e) { + console.log(e); + } - return { user, token, rate, rates }; + return { user, token, rate, rates }; } diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 51ade9c7..c5a312e1 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,167 +1,174 @@ <script> - import { SvelteToast } from '@zerodevx/svelte-toast'; - import { onDestroy, onMount } from 'svelte'; - import { close, connect, send } from '$lib/socket'; - import { last, invoice, request, ndef, passwordPrompt, pin } from '$lib/store'; - import { page } from '$app/stores'; - import { browser } from '$app/environment'; - import { goto } from '$app/navigation'; - import { LoadingSplash, Invoice, Password, Request } from '$comp'; - import { warning, protectedRoutes } from '$lib/utils'; - import { t, locale } from '$lib/translations'; - import { onNavigate } from '$app/navigation'; + import { SvelteToast } from "@zerodevx/svelte-toast"; + import { onDestroy, onMount } from "svelte"; + import { close, connect, send } from "$lib/socket"; + import { + last, + invoice, + request, + ndef, + passwordPrompt, + pin, + } from "$lib/store"; + import { page } from "$app/stores"; + import { browser } from "$app/environment"; + import { goto } from "$app/navigation"; + import { LoadingSplash, Invoice, Password, Request } from "$comp"; + import { warning, protectedRoutes } from "$lib/utils"; + import { t, locale } from "$lib/translations"; + import { onNavigate } from "$app/navigation"; - export let data; + export let data; - onNavigate((navigation) => { - if (!document.startViewTransition) return; + onNavigate((navigation) => { + if (!document.startViewTransition) return; - return new Promise((resolve) => { - document.startViewTransition(async () => { - resolve(); - await navigation.complete; - }); - }); - }); + return new Promise((resolve) => { + document.startViewTransition(async () => { + resolve(); + await navigation.complete; + }); + }); + }); - let { rate, user, subject, token, rates } = data; + let { rate, user, subject, token, rates } = data; - $: update(data); - let update = (data) => { - ({ rate, user, subject, token, rates } = data); - }; + $: update(data); + let update = (data) => { + ({ rate, user, subject, token, rates } = data); + }; - onMount(async () => { - let localeLocalStorageKey = 'sveltekit-i18n-locale'; - let localStorageLocale = localStorage.getItem(localeLocalStorageKey); - if (localStorageLocale) locale.set(localStorageLocale); + onMount(async () => { + let localeLocalStorageKey = "sveltekit-i18n-locale"; + let localStorageLocale = localStorage.getItem(localeLocalStorageKey); + if (localStorageLocale) locale.set(localStorageLocale); - locale.subscribe((lng) => { - if (lng) localStorage.setItem(localeLocalStorageKey, lng); - }); + locale.subscribe((lng) => { + if (lng) localStorage.setItem(localeLocalStorageKey, lng); + }); - if (browser) { - checkSocket(); - let locktime = user && user.locktime ? user.locktime : 300; - expireTimer = setTimeout(expirePin, locktime * 1000); + if (browser) { + checkSocket(); + let locktime = user && user.locktime ? user.locktime : 300; + expireTimer = setTimeout(expirePin, locktime * 1000); - // if (window.NDEFReader) { - // try { - // $ndef = new NDEFReader(); - // await $ndef.scan(); - // - // $ndef.addEventListener('readingerror', (e) => { - // console.log('nfc error', e); - // }); - // - // $ndef.addEventListener('reading', ({ message, serialNumber }) => { - // goto(`/${user.username}/card/${serialNumber.replace(/:/g, '-')}`); - // }); - // } catch (error) { - // console.log('Argh! ' + error); - // } - // } - } - }); + // if (window.NDEFReader) { + // try { + // $ndef = new NDEFReader(); + // await $ndef.scan(); + // + // $ndef.addEventListener('readingerror', (e) => { + // console.log('nfc error', e); + // }); + // + // $ndef.addEventListener('reading', ({ message, serialNumber }) => { + // goto(`/${user.username}/card/${serialNumber.replace(/:/g, '-')}`); + // }); + // } catch (error) { + // console.log('Argh! ' + error); + // } + // } + } + }); - $: browser && connect(token); + $: browser && connect(token); - let lost, - checkTimer, - expireTimer, - counter = 0; + let lost, + checkTimer, + expireTimer, + counter = 0; - let checkSocket = () => { - counter++; - lost = Date.now() - $last > 30000; - if (lost) connect(token); - if (counter > 5) { - send('heartbeat', token); - counter = 0; - } + let checkSocket = () => { + counter++; + lost = Date.now() - $last > 30000; + if (lost) connect(token); + if (counter > 5) { + send("heartbeat", token); + counter = 0; + } - checkTimer = setTimeout(checkSocket, 1000); - }; + checkTimer = setTimeout(checkSocket, 1000); + }; - let expirePin = () => { - $pin = null; - expireTimer = setTimeout(expirePin, user.locktime * 1000 || 300000); - }; + let expirePin = () => { + $pin = null; + expireTimer = setTimeout(expirePin, user.locktime * 1000 || 300000); + }; - onDestroy(() => { - if (browser) { - close(); - clearTimeout(checkTimer); - clearTimeout(expireTimer); - } - }); + onDestroy(() => { + if (browser) { + close(); + clearTimeout(checkTimer); + clearTimeout(expireTimer); + } + }); </script> {#if browser && $passwordPrompt} - <Password {user} /> + <Password {user} /> {/if} <svelte:head> - {#if subject} - <title>coinos - {subject.username}</title> - <meta name="lightning" content={`lnurlp:${subject.username}@coinos.io`} /> - {:else} - <title>coinos</title> - {/if} + {#if subject} + <title>coinos - {subject.username}</title> + <meta name="lightning" content={`lnurlp:${subject.username}@coinos.io`} /> + {:else} + <title>coinos</title> + {/if} - {#if subject?.profile} - <meta name="og:image" content={`/api/public/${subject.profile}.webp`} /> - {:else} - <meta property="og:image" content="/icons/logo.svg" /> - {/if} + {#if subject?.profile} + <meta name="og:image" content={`/api/public/${subject.profile}.webp`} /> + {:else} + <meta property="og:image" content="/icons/logo.svg" /> + {/if} </svelte:head> <SvelteToast options={{ reversed: true, intro: { y: 192 } }} /> <main class="pb-20"> - <slot /> + <slot /> </main> {#if $invoice} - <Invoice {user} /> + <Invoice {user} /> {/if} {#if $request} - <Request {user} /> + <Request {user} /> {/if} <style> - @keyframes fade-in { - from { - opacity: 0; - } - } + @keyframes fade-in { + from { + opacity: 0; + } + } - @keyframes fade-out { - to { - opacity: 0; - } - } + @keyframes fade-out { + to { + opacity: 0; + } + } - @keyframes slide-from-right { - from { - transform: translateX(60px); - } - } + @keyframes slide-from-right { + from { + transform: translateX(60px); + } + } - @keyframes slide-to-left { - to { - transform: translateX(-60px); - } - } + @keyframes slide-to-left { + to { + transform: translateX(-60px); + } + } - :root::view-transition-old(root) { - animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out, - 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; - } + :root::view-transition-old(root) { + animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out, + 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; + } - :root::view-transition-new(root) { - animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in, - 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; - } + :root::view-transition-new(root) { + animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in, + 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; + } </style> diff --git a/src/routes/(app)/[username]/(profile)/+layout.server.js b/src/routes/(app)/[username]/(profile)/+layout.server.js index f4f2ca0a..a508de92 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.server.js +++ b/src/routes/(app)/[username]/(profile)/+layout.server.js @@ -1,26 +1,26 @@ -import { get } from '$lib/utils'; +import { get } from "$lib/utils"; export async function load({ parent }) { - let { subject, user } = await parent(); + let { subject, user } = await parent(); - subject.follows = []; - subject.followers = []; + subject.follows = []; + subject.followers = []; - try { - subject.follows = await get(`/${subject.pubkey}/follows`); - } catch (e) { - console.log('problem fetching follows', e); - } + try { + subject.follows = await get(`/${subject.pubkey}/follows`); + } catch (e) { + console.log("problem fetching follows", e); + } - try { - subject.followers = await get(`/${subject.pubkey}/followers`); - } catch (e) { - console.log('problem fetching followers', e); - } + try { + subject.followers = await get(`/${subject.pubkey}/followers`); + } catch (e) { + console.log("problem fetching followers", e); + } - if (user) { - user.follows = await get(`/${user.pubkey}/follows?tagsonly=true`); - } + if (user) { + user.follows = await get(`/${user.pubkey}/follows?tagsonly=true`); + } - return { subject, user }; + return { subject, user }; } diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte index 55c22bdb..6b166e2c 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.svelte +++ b/src/routes/(app)/[username]/(profile)/+layout.svelte @@ -1,236 +1,246 @@ <script> - import { hexToUint8Array } from 'uint8array-extras'; - import { copy, f, s, sats } from '$lib/utils'; - import { animatedRate } from '$lib/store'; - import { Icon } from '$comp'; - import { t } from '$lib/translations'; - import { sign, send } from '$lib/nostr'; - import { bech32m } from '@scure/base'; - import { page } from '$app/stores'; + import { hexToUint8Array } from "uint8array-extras"; + import { copy, f, s, sats } from "$lib/utils"; + import { animatedRate } from "$lib/store"; + import { Icon } from "$comp"; + import { t } from "$lib/translations"; + import { sign, send } from "$lib/nostr"; + import { bech32m } from "@scure/base"; + import { page } from "$app/stores"; - export let data; + export let data; - let { encode, toWords } = bech32m; - let events, user, subject, rates, src, text; + let { encode, toWords } = bech32m; + let events, user, subject, rates, src, text; - $: ({ events, user, rates, subject, src, text } = data); - $: ({ currency, username: n, display } = subject); + $: ({ events, user, rates, subject, src, text } = data); + $: ({ currency, username: n, display } = subject); - $: username = n.length > 60 ? n.substr(0, 6) : display || n; - $: npub = bech32m.encode('npub', toWords(hexToUint8Array(subject.pubkey)), 180); - $: lnaddr = `${n}@${$page.url.host}`; - $: profile = `${$page.url.host}/${n}`; + $: username = n.length > 60 ? n.substr(0, 6) : display || n; + $: npub = bech32m.encode( + "npub", + toWords(hexToUint8Array(subject.pubkey)), + 180 + ); + $: lnaddr = `${n}@${$page.url.host}`; + $: profile = `${$page.url.host}/${n}`; - let follow = async () => { - user.follows.push(['p', subject.pubkey, 'wss://nostr.coinos.io', subject.username]); - update(); - }; + let follow = async () => { + user.follows.push([ + "p", + subject.pubkey, + "wss://nostr.coinos.io", + subject.username, + ]); + update(); + }; - let unfollow = async () => { - user.follows.splice( - user.follows.findIndex((t) => t[1] === subject.pubkey), - 1 - ); - update(); - }; + let unfollow = async () => { + user.follows.splice( + user.follows.findIndex((t) => t[1] === subject.pubkey), + 1 + ); + update(); + }; - let update = async () => { - let event = { - pubkey: user.pubkey, - created_at: Math.floor(Date.now() / 1000), - kind: 3, - content: '', - tags: user.follows - }; + let update = async () => { + let event = { + pubkey: user.pubkey, + created_at: Math.floor(Date.now() / 1000), + kind: 3, + content: "", + tags: user.follows, + }; - await sign({ event, user }); - await send(event); + await sign({ event, user }); + await send(event); - user.follows = user.follows; - }; + user.follows = user.follows; + }; - $: following = !!user?.follows.find((t) => t.includes(subject.pubkey)); + $: following = !!user?.follows.find((t) => t.includes(subject.pubkey)); - let showBio; - let toggleBio = () => (showBio = !showBio); + let showBio; + let toggleBio = () => (showBio = !showBio); - let showDetails; - let toggleDetails = () => (showDetails = !showDetails); + let showDetails; + let toggleDetails = () => (showDetails = !showDetails); </script> <div class="container mx-auto w-full px-4 flex flex-wrap lg:flex-nowrap"> - <div class="hidden lg:block lg:w-[300px]" /> - <div class="lg:w-[300px] lg:absolute space-y-5 left-20 mx-auto max-w-sm"> - <div class="flex text-3xl font-bold text-center mx-auto justify-center gap-2"> - <div class="my-auto">{display || username}</div> - <button on:click={toggleDetails}> - <Icon icon="stats" style="w-12" /> - </button> - </div> + <div class="hidden lg:block lg:w-[300px]" /> + <div class="lg:w-[300px] lg:absolute space-y-5 left-20 mx-auto max-w-sm"> + <div + class="flex text-3xl font-bold text-center mx-auto justify-center gap-2" + > + <div class="my-auto">{display || username}</div> + <button on:click={toggleDetails}> + <Icon icon="stats" style="w-12" /> + </button> + </div> - {#if showDetails} - <div> - <div class="font-bold">{$t('user.profilePage')}</div> - <div class="flex gap-4"> - <div class="break-all grow text-secondary text-lg"> - {profile} - </div> - <div class="flex mb-auto gap-1"> - <button class="my-auto" on:click={() => copy(profile)} - ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button - > - <a - href={`/qr/${encodeURIComponent(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.lightningAddress')}</div> - <div class="flex gap-4"> - <div class="break-all grow text-secondary text-lg"> - {lnaddr} - </div> - <div class="flex mb-auto gap-1"> - <button class="my-auto" on:click={() => copy(lnaddr)} - ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button - > - <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.nostrPubkey')}</div> - <div class="flex gap-4"> - <div class="break-all grow text-secondary text-lg"> - {npub} - </div> - <div class="flex my-auto gap-1"> - <button class="my-auto" on:click={() => copy(npub)} - ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button - > - <a - href={`/qr/${encodeURIComponent(npub)}`} - class="my-auto" - > - <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> - </a> - </div> - </div> - </div> - {/if} + {#if showDetails} + <div> + <div class="font-bold">{$t("user.profilePage")}</div> + <div class="flex gap-4"> + <div class="break-all grow text-secondary text-lg"> + {profile} + </div> + <div class="flex mb-auto gap-1"> + <button class="my-auto" on:click={() => copy(profile)} + ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button + > + <a href={`/qr/${encodeURIComponent(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.lightningAddress")}</div> + <div class="flex gap-4"> + <div class="break-all grow text-secondary text-lg"> + {lnaddr} + </div> + <div class="flex mb-auto gap-1"> + <button class="my-auto" on:click={() => copy(lnaddr)} + ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button + > + <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.nostrPubkey")}</div> + <div class="flex gap-4"> + <div class="break-all grow text-secondary text-lg"> + {npub} + </div> + <div class="flex my-auto gap-1"> + <button class="my-auto" on:click={() => copy(npub)} + ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button + > + <a href={`/qr/${encodeURIComponent(npub)}`} class="my-auto"> + <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> + </a> + </div> + </div> + </div> + {/if} - {#if subject.address && subject.id !== user?.id} - <div - class="text-secondary mx-auto text-center lg:text-left lg:mx-0" - class:line-clamp-3={showBio} - on:click={toggleBio} - on:keydown={toggleBio} - > - {subject.address} - </div> - {/if} + {#if subject.address && subject.id !== user?.id} + <div + class="text-secondary mx-auto text-center lg:text-left lg:mx-0" + class:line-clamp-3={showBio} + on:click={toggleBio} + on:keydown={toggleBio} + > + {subject.address} + </div> + {/if} - <div class="flex justify-center gap-2"> - <a href={`/${subject.pubkey}/follows`} - ><b>{subject.follows.length}</b> - <span class="text-secondary">{$t('user.following')}</span></a - > - <a href={`/${subject.pubkey}/followers`} - ><b>{subject.followers.length}</b> - <span class="text-secondary">{$t('user.followers')}</span></a - > - </div> + <div class="flex justify-center gap-2"> + <a href={`/${subject.pubkey}/follows`} + ><b>{subject.follows.length}</b> + <span class="text-secondary">{$t("user.following")}</span></a + > + <a href={`/${subject.pubkey}/followers`} + ><b>{subject.followers.length}</b> + <span class="text-secondary">{$t("user.followers")}</span></a + > + </div> - <div class="flex flex-wrap gap-2 w-full"> - {#if user && user.username !== subject.username && subject.pubkey} - {#if following} - <div class="w-full flex"> - <button - class="mx-auto rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white" - on:click={unfollow} - > - <div class="mx-auto flex"> - <Icon icon={'profile'} style="my-auto h-6 mr-2 invert" /> - <div class="my-auto">{$t('user.following')}</div> - </div> - </button> - </div> - {:else} - <div class="w-full flex"> - <button - class="mx-auto rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60" - on:click={follow} - > - <div class="mx-auto flex"> - <Icon icon={'profile'} style="my-auto h-6 mr-2" /> - <div class="my-auto">{$t('user.follow')}</div> - </div> - </button> - </div> - {/if} - {/if} + <div class="flex flex-wrap gap-2 w-full"> + {#if user && user.username !== subject.username && subject.pubkey} + {#if following} + <div class="w-full flex"> + <button + class="mx-auto rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white" + on:click={unfollow} + > + <div class="mx-auto flex"> + <Icon icon={"profile"} style="my-auto h-6 mr-2 invert" /> + <div class="my-auto">{$t("user.following")}</div> + </div> + </button> + </div> + {:else} + <div class="w-full flex"> + <button + class="mx-auto rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60" + on:click={follow} + > + <div class="mx-auto flex"> + <Icon icon={"profile"} style="my-auto h-6 mr-2" /> + <div class="my-auto">{$t("user.follow")}</div> + </div> + </button> + </div> + {/if} + {/if} - {#if user && user.username !== subject.username && subject.pubkey} - <div class="w-full flex"> - <a href={`/${subject.username}/messages`} class="mx-auto"> - <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60"> - <div class="mx-auto flex"> - <Icon icon="message" style="mr-2 my-auto" /> - <div class="mt-1 my-auto">{$t('user.message')}</div> - </div> - </button> - </a> - </div> - {/if} + {#if user && user.username !== subject.username && subject.pubkey} + <div class="w-full flex"> + <a href={`/${subject.username}/messages`} class="mx-auto"> + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60" + > + <div class="mx-auto flex"> + <Icon icon="message" style="mr-2 my-auto" /> + <div class="mt-1 my-auto">{$t("user.message")}</div> + </div> + </button> + </a> + </div> + {/if} - {#if !subject.anon && subject.username !== user?.username} - <div class="w-full flex"> - <a - href={user ? `/send/${subject.username}` : `/${subject.username}/receive`} - class="mx-auto" - > - <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60"> - <div class="mx-auto flex"> - <Icon icon="send" style="mr-2" /> - <div class="mt-1">{$t('user.pay')}</div> - </div> - </button> - </a> - </div> - {/if} - </div> - </div> + {#if !subject.anon && subject.username !== user?.username} + <div class="w-full flex"> + <a + href={user + ? `/send/${subject.username}` + : `/${subject.username}/receive`} + class="mx-auto" + > + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60" + > + <div class="mx-auto flex"> + <Icon icon="send" style="mr-2" /> + <div class="mt-1">{$t("user.pay")}</div> + </div> + </button> + </a> + </div> + {/if} + </div> + </div> - <div class="w-full"> - <div class="mx-auto space-y-5 lg:max-w-xl lg:pl-10 xl:max-w-2xl lg:pl-10"> - <slot /> - </div> - </div> + <div class="w-full"> + <div class="mx-auto space-y-5 lg:max-w-xl lg:pl-10 xl:max-w-2xl lg:pl-10"> + <slot /> + </div> + </div> </div> {#if currency && !isNaN($animatedRate)} - <div class="flex fixed w-full px-4 bg-white py-2 pb-3 bottom-0 bg-opacity-90 tabular-nums"> - <div class="text-secondary flex mr-auto"> - <div class="flex mr-1"> - <div class="my-auto mr-1">1</div> - <img src="/images/bitcoin.svg" class="w-5 my-auto" alt="Bitcoin" /> - </div> - <div>= {f($animatedRate, currency)}</div> - </div> - <div class="text-secondary flex ml-auto"> - <div class="flex"> - <div class="mr-1">⚡️{s((1 * sats) / $animatedRate)} =</div> - <div>{f(1, currency)}</div> - </div> - </div> - </div> + <div + class="flex fixed w-full px-4 bg-white py-2 pb-3 bottom-0 bg-opacity-90 tabular-nums" + > + <div class="text-secondary flex mr-auto"> + <div class="flex mr-1"> + <div class="my-auto mr-1">1</div> + <img src="/images/bitcoin.svg" class="w-5 my-auto" alt="Bitcoin" /> + </div> + <div>= {f($animatedRate, currency)}</div> + </div> + <div class="text-secondary flex ml-auto"> + <div class="flex"> + <div class="mr-1">⚡️{s((1 * sats) / $animatedRate)} =</div> + <div>{f(1, currency)}</div> + </div> + </div> + </div> {/if} diff --git a/src/routes/(app)/[username]/(profile)/+page.server.js b/src/routes/(app)/[username]/(profile)/+page.server.js index e37db1fe..3110ec40 100644 --- a/src/routes/(app)/[username]/(profile)/+page.server.js +++ b/src/routes/(app)/[username]/(profile)/+page.server.js @@ -1,35 +1,35 @@ -import { auth, get } from '$lib/utils'; +import { auth, get } from "$lib/utils"; export async function load({ cookies, params, parent, url }) { - let { user, subject } = await parent(); - let { pubkey } = subject; - let { since = 0 } = params; + let { user, subject } = await parent(); + let { pubkey } = subject; + let { since = 0 } = params; - let messages, invoices, sent, received, notes; - messages = invoices = sent = received = notes = []; + let messages, invoices, sent, received, notes; + messages = invoices = sent = received = notes = []; - if (pubkey) { - if (user) { - try { - messages = await get(`/${user.pubkey}/${since}/messages`); - messages = messages.sort((a, b) => b.created_at - a.created_at); - } catch (e) { - console.log(`failed to fetch nostr messages`, e); - } + if (pubkey) { + if (user) { + try { + messages = await get(`/${user.pubkey}/${since}/messages`); + messages = messages.sort((a, b) => b.created_at - a.created_at); + } catch (e) { + console.log(`failed to fetch nostr messages`, e); + } - ({ invoices, sent, received } = await get('/requests', auth(cookies))); - } + ({ invoices, sent, received } = await get("/requests", auth(cookies))); + } - try { - notes = await get(`/${pubkey}/notes`); - } catch (e) { - console.log(`failed to fetch nostr notes for ${pubkey}`, e); - } + try { + notes = await get(`/${pubkey}/notes`); + } catch (e) { + console.log(`failed to fetch nostr notes for ${pubkey}`, e); + } - notes.map((e) => { - e.seen = e.created_at; - }); - } + notes.map((e) => { + e.seen = e.created_at; + }); + } - return { invoices, messages, notes, sent, received }; + return { invoices, messages, notes, sent, received }; } diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index 32a1f2cb..b0e36090 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -1,227 +1,245 @@ <script> - import { onMount } from 'svelte'; - import { Icon, Avatar, Balance, Feed } from '$comp'; - import { t } from '$lib/translations'; - import { ease, f, sat, sats } from '$lib/utils'; - import { sign, send, encrypt, decrypt } from '$lib/nostr'; - import { event as e, password, rate, animatedRate } from '$lib/store'; - import { browser } from '$app/environment'; + import { onMount } from "svelte"; + import { Icon, Avatar, Balance, Feed } from "$comp"; + import { t } from "$lib/translations"; + import { ease, f, sat, sats } from "$lib/utils"; + import { sign, send, encrypt, decrypt } from "$lib/nostr"; + import { event as e, password, rate, animatedRate } from "$lib/store"; + import { browser } from "$app/environment"; - export let data; - let { messages, notes, invoices, sent, received, subject, user, rates } = data; - $: refresh(data); - let refresh = (d) => ({ messages, notes, invoices, sent, received, subject, user, rates } = d); + export let data; + let { messages, notes, invoices, sent, received, subject, user, rates } = + data; + $: refresh(data); + let refresh = (d) => + ({ messages, notes, invoices, sent, received, subject, user, rates } = d); - let keys = new Set(); - let latest = []; - let ready; + let keys = new Set(); + let latest = []; + let ready; - e.subscribe(async (event) => { - if (!(event && ready)) return; - if (event.recipient?.id === user.id && !~latest.findIndex((m) => m.id === event.id)) { - event.content = await decrypt({ event, user }); + e.subscribe(async (event) => { + if (!(event && ready)) return; + if ( + event.recipient?.id === user.id && + !~latest.findIndex((m) => m.id === event.id) + ) { + event.content = await decrypt({ event, user }); - let i = latest.findIndex((m) => m.pubkey === event.pubkey); - let popped; - if (~i) popped = latest.splice(i, 1); - else popped = latest.pop(); + let i = latest.findIndex((m) => m.pubkey === event.pubkey); + let popped; + if (~i) popped = latest.splice(i, 1); + else popped = latest.pop(); - latest.unshift(event); - latest = latest; - } - }); + latest.unshift(event); + latest = latest; + } + }); - $: initialize($password); - let initialize = async (p) => { - let i = 0; - ready = false; - if (!p || !user) return; - while (i < messages.length) { - let event = messages[i]; - i++; + $: initialize($password); + let initialize = async (p) => { + let i = 0; + ready = false; + if (!p || !user) return; + while (i < messages.length) { + let event = messages[i]; + i++; - if (!(event.author && event.recipient)) continue; + if (!(event.author && event.recipient)) continue; - let k = event.author.id === user.id ? event.recipient?.pubkey : event.author.pubkey; - if (!keys.has(k)) { - keys.add(k); - event.content = await decrypt({ event, user }); - if (event.content) latest.push(event); - } - } + let k = + event.author.id === user.id + ? event.recipient?.pubkey + : event.author.pubkey; + if (!keys.has(k)) { + keys.add(k); + event.content = await decrypt({ event, user }); + if (event.content) latest.push(event); + } + } - latest = latest; - ready = true; - }; + latest = latest; + ready = true; + }; - let i; - let o; + let i; + let o; - rate.subscribe((n) => { - if (!user?.currency) return; - clearInterval(i); + rate.subscribe((n) => { + if (!user?.currency) return; + clearInterval(i); - let r = ((rates[user.currency] / rates['USD']) * n).toFixed(2); + let r = ((rates[user.currency] / rates["USD"]) * n).toFixed(2); - if (!r || isNaN(r)) return; - if (!o) o = r; + if (!r || isNaN(r)) return; + if (!o) o = r; - let t = 0; - let d = o - r; + let t = 0; + let d = o - r; - i = setInterval(() => { - if (!r) return; - $animatedRate = (o - d * ease(t / 100)).toFixed(2); - if (t > 80) (o = r) && clearInterval(i); - t++; - }, 10); - }); + i = setInterval(() => { + if (!r) return; + $animatedRate = (o - d * ease(t / 100)).toFixed(2); + if (t > 80) (o = r) && clearInterval(i); + t++; + }, 10); + }); </script> <div class="space-y-5"> - {#if user?.id === subject.id} - <div class="space-y-5"> - <div class="flex justify-center lg:justify-start"> - <Balance {user} /> - </div> + {#if user?.id === subject.id} + <div class="space-y-5"> + <div class="flex justify-center lg:justify-start"> + <Balance {user} /> + </div> - {#if !user.balance} - <div class="mb-8"> - <p class="text-secondary text-lg"> - {$t('user.welcome')} - </p> - </div> - {/if} + {#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-2 justify-center w-full"> - <a href={`/${user.username}/receive`} class="w-full md:w-60"> - <button - class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full" - > - <div class="mx-auto flex"> - <Icon icon="numpad" style="my-auto h-6 mr-2" /> - <div class="my-auto">{$t('user.dashboard.receive')}</div> - </div> - </button> - </a> - <a href={`/send`} class="w-full md:w-60"> - <button - class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full" - > - <div class="mx-auto flex"> - <Icon icon="send" style="my-auto h-6 mr-2" /> - <div class="my-auto">{$t('user.dashboard.send')}</div> - </div> - </button> - </a> + <div class="flex flex-wrap gap-2 justify-center w-full"> + <a href={`/${user.username}/receive`} class="w-full md:w-60"> + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full" + > + <div class="mx-auto flex"> + <Icon icon="numpad" style="my-auto h-6 mr-2" /> + <div class="my-auto">{$t("user.dashboard.receive")}</div> + </div> + </button> + </a> + <a href={`/send`} class="w-full md:w-60"> + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full" + > + <div class="mx-auto flex"> + <Icon icon="send" style="my-auto h-6 mr-2" /> + <div class="my-auto">{$t("user.dashboard.send")}</div> + </div> + </button> + </a> - {#if user.eligible} - <a href={`/buy`}> - <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60"> - <div class="mx-auto flex"> - <!-- <Icon icon="plus" style="my-auto h-6 mr-2" /> --> + {#if user.eligible} + <a href={`/buy`}> + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60" + > + <div class="mx-auto flex"> + <!-- <Icon icon="plus" style="my-auto h-6 mr-2" /> --> - <img - src="/images/bitcoin.svg" - class="w-8 border-4 border-transparent mr-2" - alt="Bitcoin" - /> - <div class="my-auto mt-1">Buy Bitcoin</div> - </div> - </button> - </a> - {/if} - </div> - </div> - {/if} + <img + src="/images/bitcoin.svg" + class="w-8 border-4 border-transparent mr-2" + alt="Bitcoin" + /> + <div class="my-auto mt-1">Buy Bitcoin</div> + </div> + </button> + </a> + {/if} + </div> + </div> + {/if} - {#if invoices.length} - <div> - <h1 class="text-secondary text-xl">Invoices</h1> - {#each invoices as { amount, currency, rate, request: { requester, recipient }, hash }} - {@const requested = user.username === requester?.username} - <a href={`/${recipient.username}/invoice/${hash}` + (requested ? '/tip' : '')}> - <div class="border-b p-2 last:border-b-0 hover:bg-gray-100"> - <div class="flex gap-2"> - <div> - {#if requested} - <div class="flex"> - <Avatar user={recipient} size={20} /> - <div class="my-auto text-left"> - <p class="text-secondary">{$t('payments.invoiceFrom')}</p> - <p class="ml-1 text-lg break-words">{recipient.username}</p> - </div> - </div> - {:else} - <div class="flex"> - <Avatar user={requester} size={20} /> - <div class="my-auto text-left"> - <p class="text-secondary">{$t('payments.awaiting')}</p> - <p class="ml-1 text-lg break-words">{requester.username}</p> - </div> - </div> - {/if} - </div> - <div class="whitespace-nowrap my-auto ml-auto flex gap-2"> - <div class="font-bold"> - {f(amount * (rate / sats), currency)} - </div> + {#if invoices.length} + <div> + <h1 class="text-secondary text-xl">Invoices</h1> + {#each invoices as { amount, currency, rate, request: { requester, recipient }, hash }} + {@const requested = user.username === requester?.username} + <a + href={`/${recipient.username}/invoice/${hash}` + + (requested ? "/tip" : "")} + > + <div class="border-b p-2 last:border-b-0 hover:bg-gray-100"> + <div class="flex gap-2"> + <div> + {#if requested} + <div class="flex"> + <Avatar user={recipient} size={20} /> + <div class="my-auto text-left"> + <p class="text-secondary">{$t("payments.invoiceFrom")}</p> + <p class="ml-1 text-lg break-words"> + {recipient.username} + </p> + </div> + </div> + {:else} + <div class="flex"> + <Avatar user={requester} size={20} /> + <div class="my-auto text-left"> + <p class="text-secondary">{$t("payments.awaiting")}</p> + <p class="ml-1 text-lg break-words"> + {requester.username} + </p> + </div> + </div> + {/if} + </div> + <div class="whitespace-nowrap my-auto ml-auto flex gap-2"> + <div class="font-bold"> + {f(amount * (rate / sats), currency)} + </div> - <div class="text-secondary">{sat(amount)}</div> - </div> - </div> - </div> - </a> - {/each} - </div> - {/if} - {#if received.length} - <div> - <h1 class="text-secondary text-xl">Requests</h1> + <div class="text-secondary">{sat(amount)}</div> + </div> + </div> + </div> + </a> + {/each} + </div> + {/if} + {#if received.length} + <div> + <h1 class="text-secondary text-xl">Requests</h1> - {#each received as { id, invoice, memo, requester: r }} - <a href={`/${user.username}/receive/${id}`}> - <div class="border-b p-2 last:border-b-0 hover:bg-gray-100"> - <div class="flex"> - <div> - <div class="flex"> - <Avatar user={r} size={20} /> - <div class="my-auto text-left"> - <p class="ml-1 text-lg break-words">{r.username}</p> - <p class="ml-1 text-secondary">{memo}</p> - </div> - </div> - </div> - <div class="whitespace-nowrap my-auto ml-auto flex gap-2"> - <button class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full" - >Invoice</button - > - </div> - </div> - </div> - </a> - {/each} - </div> - {/if} + {#each received as { id, invoice, memo, requester: r }} + <a href={`/${user.username}/receive/${id}`}> + <div class="border-b p-2 last:border-b-0 hover:bg-gray-100"> + <div class="flex"> + <div> + <div class="flex"> + <Avatar user={r} size={20} /> + <div class="my-auto text-left"> + <p class="ml-1 text-lg break-words">{r.username}</p> + <p class="ml-1 text-secondary">{memo}</p> + </div> + </div> + </div> + <div class="whitespace-nowrap my-auto ml-auto flex gap-2"> + <button + class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full" + >Invoice</button + > + </div> + </div> + </div> + </a> + {/each} + </div> + {/if} - {#if latest.length && user?.id === subject.id} - <div class="relative"> - {#each latest as { content, pubkey, author, recipient }} - {@const user = author.id === user.id ? recipient : author} - <a href={`/${user.username}/messages`}> - <div class="flex hover:bg-gray-100 p-4 rounded-2xl"> - <div class="my-auto"> - <Avatar {user} size={'20'} disabled={true} /> - </div> - <div class="my-auto truncate"> - <div class="my-auto ml-1 text-lg font-bold">{user.username}</div> - <div class="my-auto ml-1 text-secondary text-lg truncate"> - {content} - </div> - </div> - </div> - </a> - {/each} - </div> - {/if} + {#if latest.length && user?.id === subject.id} + <div class="relative"> + {#each latest as { content, pubkey, author, recipient }} + {@const user = author.id === user.id ? recipient : author} + <a href={`/${user.username}/messages`}> + <div class="flex hover:bg-gray-100 p-4 rounded-2xl"> + <div class="my-auto"> + <Avatar {user} size={"20"} disabled={true} /> + </div> + <div class="my-auto truncate"> + <div class="my-auto ml-1 text-lg font-bold">{user.username}</div> + <div class="my-auto ml-1 text-secondary text-lg truncate"> + {content} + </div> + </div> + </div> + </a> + {/each} + </div> + {/if} </div> diff --git a/src/routes/(app)/[username]/(profile)/event/[id]/+page.server.js b/src/routes/(app)/[username]/(profile)/event/[id]/+page.server.js index 6182de17..ec4da865 100644 --- a/src/routes/(app)/[username]/(profile)/event/[id]/+page.server.js +++ b/src/routes/(app)/[username]/(profile)/event/[id]/+page.server.js @@ -1,12 +1,12 @@ -import { get } from '$lib/utils'; -import { error } from '@sveltejs/kit'; +import { get } from "$lib/utils"; +import { error } from "@sveltejs/kit"; export async function load({ params: { id } }) { - try { - let event = await get(`/event/${id}`); - if (!event) throw error(500, 'Event not found'); - return { event }; - } catch (e) { - throw error(500, e.message); - } + try { + let event = await get(`/event/${id}`); + if (!event) throw error(500, "Event not found"); + return { event }; + } catch (e) { + throw error(500, e.message); + } } diff --git a/src/routes/(app)/[username]/(profile)/event/[id]/+page.svelte b/src/routes/(app)/[username]/(profile)/event/[id]/+page.svelte index 85fc815a..1347947f 100644 --- a/src/routes/(app)/[username]/(profile)/event/[id]/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/event/[id]/+page.svelte @@ -1,7 +1,7 @@ <script> - import { Event } from '$comp'; - export let data; - let { event } = data; + import { Event } from "$comp"; + export let data; + let { event } = data; </script> <Event {event} /> diff --git a/src/routes/(app)/[username]/(profile)/followers/+page.svelte b/src/routes/(app)/[username]/(profile)/followers/+page.svelte index 56b3ba16..f51af86b 100644 --- a/src/routes/(app)/[username]/(profile)/followers/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/followers/+page.svelte @@ -1,51 +1,52 @@ <script> - import { t } from '$lib/translations'; - import { Avatar } from '$comp'; - import { browser } from '$app/environment'; - import VirtualScroll from 'svelte-virtual-scroll-list'; + import { t } from "$lib/translations"; + import { Avatar } from "$comp"; + import { browser } from "$app/environment"; + import VirtualScroll from "svelte-virtual-scroll-list"; - export let data; - let { - subject: { followers } - } = data; + export let data; + let { + subject: { followers }, + } = data; - let w; + let w; </script> <svelte:window bind:innerWidth={w} /> <div class="px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> - {$t('user.followers')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> + {$t("user.followers")} + </h1> - {#if browser} - {#if followers.length} - <VirtualScroll data={followers} key="pubkey" let:data pageMode={true}> - <a href={`/${data.pubkey}`}> - <div - class="flex border-b last:border-b-0 py-4 text-sm lg:text-lg text-secondary" - :key={data.pubkey} - > - <div class="mb-auto mr-2"> - <div class="md:hidden"> - <Avatar size={12} user={data} disabled={true} /> - </div> - <div class="hidden md:block"> - <Avatar size={20} user={data} disabled={true} /> - </div> - </div> + {#if browser} + {#if followers.length} + <VirtualScroll data={followers} key="pubkey" let:data pageMode={true}> + <a href={`/${data.pubkey}`}> + <div + class="flex border-b last:border-b-0 py-4 text-sm lg:text-lg text-secondary" + :key={data.pubkey} + > + <div class="mb-auto mr-2"> + <div class="md:hidden"> + <Avatar size={12} user={data} disabled={true} /> + </div> + <div class="hidden md:block"> + <Avatar size={20} user={data} disabled={true} /> + </div> + </div> - <div class="w-full flex pb-1 text-black my-auto"> - <div> - {data.display_name || ''} <span class="text-secondary">@{data.username}</span> - </div> - </div> - </div> - </a> - </VirtualScroll> - {:else} - <div>No followers</div> - {/if} - {/if} + <div class="w-full flex pb-1 text-black my-auto"> + <div> + {data.display_name || ""} + <span class="text-secondary">@{data.username}</span> + </div> + </div> + </div> + </a> + </VirtualScroll> + {:else} + <div>No followers</div> + {/if} + {/if} </div> diff --git a/src/routes/(app)/[username]/(profile)/follows/+page.svelte b/src/routes/(app)/[username]/(profile)/follows/+page.svelte index b238c3a7..4f1bf32b 100644 --- a/src/routes/(app)/[username]/(profile)/follows/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/follows/+page.svelte @@ -1,47 +1,51 @@ <script> - import { t } from '$lib/translations'; - import { Avatar } from '$comp'; - import { browser } from '$app/environment'; - import VirtualScroll from 'svelte-virtual-scroll-list'; + import { t } from "$lib/translations"; + import { Avatar } from "$comp"; + import { browser } from "$app/environment"; + import VirtualScroll from "svelte-virtual-scroll-list"; - export let data; - let { - subject: { follows } - } = data; - let w; + export let data; + let { + subject: { follows }, + } = data; + let w; </script> <svelte:window bind:innerWidth={w} /> <div class="px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> - {$t('user.following')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> + {$t("user.following")} + </h1> - {#if browser} - {#if follows.length} - <VirtualScroll data={follows} key="pubkey" let:data pageMode={true}> - <a href={`/${data.pubkey}`}> - <div class="flex py-4 text-sm lg:text-lg text-secondary" :key={data.pubkey}> - <div class="mb-auto mr-2"> - <div class="md:hidden"> - <Avatar size={12} user={data} disabled={true} /> - </div> - <div class="hidden md:block"> - <Avatar size={20} user={data} disabled={true} /> - </div> - </div> + {#if browser} + {#if follows.length} + <VirtualScroll data={follows} key="pubkey" let:data pageMode={true}> + <a href={`/${data.pubkey}`}> + <div + class="flex py-4 text-sm lg:text-lg text-secondary" + :key={data.pubkey} + > + <div class="mb-auto mr-2"> + <div class="md:hidden"> + <Avatar size={12} user={data} disabled={true} /> + </div> + <div class="hidden md:block"> + <Avatar size={20} user={data} disabled={true} /> + </div> + </div> - <div class="w-full flex pb-1 text-black my-auto"> - <div> - {data.display_name || ''} <span class="text-secondary">@{data.username}</span> - </div> - </div> - </div> - </a> - </VirtualScroll> - {:else} - <div>No follows</div> - {/if} - {/if} + <div class="w-full flex pb-1 text-black my-auto"> + <div> + {data.display_name || ""} + <span class="text-secondary">@{data.username}</span> + </div> + </div> + </div> + </a> + </VirtualScroll> + {:else} + <div>No follows</div> + {/if} + {/if} </div> diff --git a/src/routes/(app)/[username]/(profile)/request/+page.server.js b/src/routes/(app)/[username]/(profile)/request/+page.server.js index e7ad6a8f..754c5805 100644 --- a/src/routes/(app)/[username]/(profile)/request/+page.server.js +++ b/src/routes/(app)/[username]/(profile)/request/+page.server.js @@ -1,15 +1,15 @@ -import { redirect } from '@sveltejs/kit'; -import { fd, auth, post } from '$lib/utils'; +import { redirect } from "@sveltejs/kit"; +import { fd, auth, post } from "$lib/utils"; export const actions = { - create: async ({ cookies, params, request, url }) => { - let form = await fd(request); - let { id, requester } = await post('/requests', form, auth(cookies)); - throw redirect(307, `/${params.username}/request/${id}`); - }, + create: async ({ cookies, params, request, url }) => { + let form = await fd(request); + let { id, requester } = await post("/requests", form, auth(cookies)); + throw redirect(307, `/${params.username}/request/${id}`); + }, - delete: async ({ cookies, request }) => { - let form = await fd(request); - let r = await post('/requests/delete', form, auth(cookies)); - } + delete: async ({ cookies, request }) => { + let form = await fd(request); + let r = await post("/requests/delete", form, auth(cookies)); + }, }; diff --git a/src/routes/(app)/[username]/(profile)/request/+page.svelte b/src/routes/(app)/[username]/(profile)/request/+page.svelte index 75117ff4..5727c3d4 100644 --- a/src/routes/(app)/[username]/(profile)/request/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/request/+page.svelte @@ -1,35 +1,35 @@ <script> - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { Icon } from '$comp'; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { Icon } from "$comp"; - export let data; + export let data; - let { subject, user } = data; + let { subject, user } = data; </script> <div class="container max-w-lg px-4 mx-auto mt-10 space-y-5"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> - {$t('payments.requestAnInvoice')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> + {$t("payments.requestAnInvoice")} + </h1> - <form action="?/create" method="POST" use:enhance class="space-y-5"> - <input type="hidden" name="requester_id" value={user.id} /> - <input type="hidden" name="recipient" value={subject.username} /> + <form action="?/create" method="POST" use:enhance class="space-y-5"> + <input type="hidden" name="requester_id" value={user.id} /> + <input type="hidden" name="recipient" value={subject.username} /> - <textarea - name="memo" - placeholder={$t('payments.describeOrder')} - class="w-full p-4 border rounded-xl h-48" - /> + <textarea + name="memo" + placeholder={$t("payments.describeOrder")} + class="w-full p-4 border rounded-xl h-48" + /> - <div class="w-full flex"> - <button - type="submit" - class="bg-black text-white border rounded-full px-6 py-2 font-bold mx-auto" - > - {$t('payments.submit')} - </button> - </div> - </form> + <div class="w-full flex"> + <button + type="submit" + class="bg-black text-white border rounded-full px-6 py-2 font-bold mx-auto" + > + {$t("payments.submit")} + </button> + </div> + </form> </div> diff --git a/src/routes/(app)/[username]/(profile)/request/sent/+page.svelte b/src/routes/(app)/[username]/(profile)/request/sent/+page.svelte index 4995bfb2..6ce0c9f6 100644 --- a/src/routes/(app)/[username]/(profile)/request/sent/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/request/sent/+page.svelte @@ -1,18 +1,21 @@ <script> - import { scale } from 'svelte/transition'; - import { Icon } from '$comp'; + import { scale } from "svelte/transition"; + import { Icon } from "$comp"; - export let data; - let { subject } = data; + export let data; + let { subject } = data; </script> <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> - <h1 class="text-3xl md:text-4xl font-bold mb-6">Request sent</h1> + <div + class="flex w-full py-20 max-w-[200px] mx-auto" + in:scale={{ start: 0.5 }} + > + <Icon icon="check" style="mx-auto" /> + </div> + <h1 class="text-3xl md:text-4xl font-bold mb-6">Request sent</h1> </div> <a href={`/${subject.username}`}> - <div class="opacity-0 w-screen h-screen fixed top-24 left-0 z-50" /> + <div class="opacity-0 w-screen h-screen fixed top-24 left-0 z-50" /> </a> diff --git a/src/routes/(app)/[username]/+layout.server.js b/src/routes/(app)/[username]/+layout.server.js index 8df75f0f..0e962173 100644 --- a/src/routes/(app)/[username]/+layout.server.js +++ b/src/routes/(app)/[username]/+layout.server.js @@ -1,13 +1,13 @@ -import { get } from '$lib/utils'; -import { error } from '@sveltejs/kit'; +import { get } from "$lib/utils"; +import { error } from "@sveltejs/kit"; export let load = async ({ depends, params: { username } }) => { - depends('app:user'); + depends("app:user"); - try { - let subject = await get(`/users/${username}`); - return { subject }; - } catch (e) { - throw error(500, 'Unable to retrieve user account data'); - } + try { + let subject = await get(`/users/${username}`); + return { subject }; + } catch (e) { + throw error(500, "Unable to retrieve user account data"); + } }; diff --git a/src/routes/(app)/[username]/+layout.svelte b/src/routes/(app)/[username]/+layout.svelte index 54fcd600..3a351e48 100644 --- a/src/routes/(app)/[username]/+layout.svelte +++ b/src/routes/(app)/[username]/+layout.svelte @@ -1,57 +1,57 @@ <script> - import { AppHeader } from '$comp'; - import { onNavigate } from '$app/navigation'; - export let data; + import { AppHeader } from "$comp"; + import { onNavigate } from "$app/navigation"; + export let data; - onNavigate((navigation) => { - if (!document.startViewTransition) return; + onNavigate((navigation) => { + if (!document.startViewTransition) return; - return new Promise((resolve) => { - document.startViewTransition(async () => { - resolve(); - await navigation.complete; - }); - }); - }); + return new Promise((resolve) => { + document.startViewTransition(async () => { + resolve(); + await navigation.complete; + }); + }); + }); </script> <AppHeader {data} /> <div class="content"> - <slot /> + <slot /> </div> <style> - @keyframes fade-in { - from { - opacity: 0; - } - } + @keyframes fade-in { + from { + opacity: 0; + } + } - @keyframes fade-out { - to { - opacity: 0; - } - } + @keyframes fade-out { + to { + opacity: 0; + } + } - @keyframes slide-from-right { - from { - transform: translateX(60px); - } - } + @keyframes slide-from-right { + from { + transform: translateX(60px); + } + } - @keyframes slide-to-left { - to { - transform: translateX(-60px); - } - } + @keyframes slide-to-left { + to { + transform: translateX(-60px); + } + } - :root::view-transition-old(root) { - animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out, - 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; - } + :root::view-transition-old(root) { + animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out, + 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; + } - :root::view-transition-new(root) { - animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in, - 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; - } + :root::view-transition-new(root) { + animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in, + 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; + } </style> diff --git a/src/routes/(app)/[username]/address/+page.server.js b/src/routes/(app)/[username]/address/+page.server.js index 247a33a2..1a782f9d 100644 --- a/src/routes/(app)/[username]/address/+page.server.js +++ b/src/routes/(app)/[username]/address/+page.server.js @@ -1,10 +1,10 @@ -import { auth, get, post } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import { auth, get, post } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export async function load({ cookies, params, parent }) { - let user = (await parent()).subject; - let rates = await get('/rates'); - let invoice = { type: 'bitcoin' }; - let { hash } = await post(`/invoice`, { invoice, user }, auth(cookies)); - throw redirect(307, `/${user.username}/invoice/${hash}`); + let user = (await parent()).subject; + let rates = await get("/rates"); + let invoice = { type: "bitcoin" }; + let { hash } = await post(`/invoice`, { invoice, user }, auth(cookies)); + throw redirect(307, `/${user.username}/invoice/${hash}`); } diff --git a/src/routes/(app)/[username]/card/[serial]/+page.server.js b/src/routes/(app)/[username]/card/[serial]/+page.server.js index 40e375aa..20b657a1 100644 --- a/src/routes/(app)/[username]/card/[serial]/+page.server.js +++ b/src/routes/(app)/[username]/card/[serial]/+page.server.js @@ -1,16 +1,16 @@ -import { auth, get } from '$lib/utils'; -import { redirect } from "@sveltejs/kit" +import { auth, get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export let load = async ({ cookies, params }) => { - let { serial } = params; - let fund; - try { - fund = await get(`/fund/${serial}`, auth(cookies)); - } catch (e) { - console.log(e); - } + let { serial } = params; + let fund; + try { + fund = await get(`/fund/${serial}`, auth(cookies)); + } catch (e) { + console.log(e); + } - if (fund) throw redirect(307, `/fund/${serial}`); + if (fund) throw redirect(307, `/fund/${serial}`); - return params; + return params; }; diff --git a/src/routes/(app)/[username]/card/[serial]/+page.svelte b/src/routes/(app)/[username]/card/[serial]/+page.svelte index afdaf65d..a2e17dfc 100644 --- a/src/routes/(app)/[username]/card/[serial]/+page.svelte +++ b/src/routes/(app)/[username]/card/[serial]/+page.svelte @@ -1,35 +1,35 @@ <script> - import { Card, Icon } from '$comp'; - export let data; - let { serial } = data; - let balance = 0; + import { Card, Icon } from "$comp"; + export let data; + let { serial } = data; + let balance = 0; </script> <div class="space-y-5"> - <div class="relative flex justify-center gap-2"> - <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {serial}</h1> - </div> - <div class="flex justify-center"> - <a href={`/send/pot/${serial}`}> - <button - class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white justify-center gap-2" - > - <svg - xmlns="http://www.w3.org/2000/svg" - class="invert text-black peer-placeholder-shown:text-gray-300 h-6 w-6 my-auto" - fill="none" - viewBox="0 0 24 24" - stroke="currentColor" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" - /> - </svg> - <div class="my-auto">Add Funds</div> - </button> - </a> - </div> + <div class="relative flex justify-center gap-2"> + <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {serial}</h1> + </div> + <div class="flex justify-center"> + <a href={`/send/pot/${serial}`}> + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white justify-center gap-2" + > + <svg + xmlns="http://www.w3.org/2000/svg" + class="invert text-black peer-placeholder-shown:text-gray-300 h-6 w-6 my-auto" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" + /> + </svg> + <div class="my-auto">Add Funds</div> + </button> + </a> + </div> </div> diff --git a/src/routes/(app)/[username]/funds/+page.svelte b/src/routes/(app)/[username]/funds/+page.svelte index 6479fb8b..667176e1 100644 --- a/src/routes/(app)/[username]/funds/+page.svelte +++ b/src/routes/(app)/[username]/funds/+page.svelte @@ -1,6 +1,6 @@ <script> - import { Avatar, Icon } from '$comp'; - import { t } from '$lib/translations'; + import { Avatar, Icon } from "$comp"; + import { t } from "$lib/translations"; export let data; @@ -8,49 +8,44 @@ </script> <div class="mt-24 mb-20"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> - Funds - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> + Funds + </h1> - <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> - <div class="text-base"> - <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4"> - <div class="whitespace-nowrap my-auto"> + <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> + <div class="text-base"> + <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4"> + <div class="whitespace-nowrap my-auto"> + <div class="text-secondary" /> + </div> - <div class="text-secondary"> - - </div> - </div> + <div class="flex my-auto"> + <div class="text-secondary flex"> + <div class="my-auto mr-1"> + <img src="/images/moneypot.png" class="w-12" alt="Pot" /> + </div> - <div class="flex my-auto"> - <div class="text-secondary flex"> - <div class="my-auto mr-1"> - <img src="/images/moneypot.png" class="w-12" alt="Pot" /> - </div> + <div class="my-auto">Pot</div> + </div> + </div> - <div class="my-auto">Pot</div> - </div> - </div> + <div class="text-secondary text-right text-sm my-auto"> + <div /> + </div> + </div> + </div> - <div class="text-secondary text-right text-sm my-auto"> - <div> - - </div> - </div> - </div> - </div> - - <button - class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto" - > - <Icon icon="plus" style="opacity-50 mr-2 my-auto" /> - <div class="my-auto">{$t('payments.create')}</div> - </button> - </div> + <button + class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto" + > + <Icon icon="plus" style="opacity-50 mr-2 my-auto" /> + <div class="my-auto">{$t("payments.create")}</div> + </button> + </div> </div> <style> - .active * { - @apply bg-black text-white border-black; - } + .active * { + @apply bg-black text-white border-black; + } </style> diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/[username]/generate/+page.svelte index b9274434..4ebb5eae 100644 --- a/src/routes/(app)/[username]/generate/+page.svelte +++ b/src/routes/(app)/[username]/generate/+page.svelte @@ -1,62 +1,62 @@ <script> - import { fail, post, wait } from '$lib/utils'; - import { onMount } from 'svelte'; - import { goto, invalidate } from '$app/navigation'; - import { Pin } from '$comp'; - import { loginRedirect, pin, password, passwordPrompt } from '$lib/store'; - import { generate } from '$lib/nostr'; - import { browser } from '$app/environment'; + import { fail, post, wait } from "$lib/utils"; + import { onMount } from "svelte"; + import { goto, invalidate } from "$app/navigation"; + import { Pin } from "$comp"; + import { loginRedirect, pin, password, passwordPrompt } from "$lib/store"; + import { generate } from "$lib/nostr"; + import { browser } from "$app/environment"; - export let data; - let { user } = data; + export let data; + let { user } = data; - let loaded; - let gen = async () => { - if (!browser) return; + let loaded; + let gen = async () => { + if (!browser) return; - if (user.pubkey) { - goto($loginRedirect || `/${user.username}`, { invalidateAll: true }); - return; - } + if (user.pubkey) { + goto($loginRedirect || `/${user.username}`, { invalidateAll: true }); + return; + } - if (!$password) $passwordPrompt = true; + if (!$password) $passwordPrompt = true; - try { - await post('/password', { password: $password }); - } catch (e) { - $passwordPrompt = true; - } + try { + await post("/password", { password: $password }); + } catch (e) { + $passwordPrompt = true; + } - await wait(() => $password); + await wait(() => $password); - try { - await generate(user); - } catch (e) { - console.log('error generating', e); - } + try { + await generate(user); + } catch (e) { + console.log("error generating", e); + } - try { - user.pin = $pin; - await post(`/${user.username}/generate`, user); - goto($loginRedirect || `/${user.username}`, { invalidateAll: true }); - } catch (e) { - $pin = ''; - if (e.message?.startsWith('Pin')) { - fail('Wrong pin, try again'); - } else { - fail('Failed to generate keys'); - goto('/'); - } - } - }; + try { + user.pin = $pin; + await post(`/${user.username}/generate`, user); + goto($loginRedirect || `/${user.username}`, { invalidateAll: true }); + } catch (e) { + $pin = ""; + if (e.message?.startsWith("Pin")) { + fail("Wrong pin, try again"); + } else { + fail("Failed to generate keys"); + goto("/"); + } + } + }; - onMount(() => browser && setTimeout(() => (loaded = true) && gen(), 50)); + onMount(() => browser && setTimeout(() => (loaded = true) && gen(), 50)); - $: $pin?.length === 6 && gen(user); + $: $pin?.length === 6 && gen(user); </script> {#if loaded && user?.haspin && $pin?.length !== 6} - <Pin /> + <Pin /> {/if} {loaded} {JSON.stringify(user)} diff --git a/src/routes/(app)/[username]/generate/+server.js b/src/routes/(app)/[username]/generate/+server.js index e1df08e3..1dca1ece 100644 --- a/src/routes/(app)/[username]/generate/+server.js +++ b/src/routes/(app)/[username]/generate/+server.js @@ -1,11 +1,11 @@ -import { auth, post } from '$lib/utils'; -import { error, json } from '@sveltejs/kit'; +import { auth, post } from "$lib/utils"; +import { error, json } from "@sveltejs/kit"; export async function POST({ cookies, request }) { - try { - let body = await request.json(); - return json(await post(`/user`, body, auth(cookies))); - } catch (e) { - throw error(500, e.message); - } + try { + let body = await request.json(); + return json(await post(`/user`, body, auth(cookies))); + } catch (e) { + throw error(500, e.message); + } } diff --git a/src/routes/(app)/[username]/invoice/+server.js b/src/routes/(app)/[username]/invoice/+server.js index 461d380c..5e3d0300 100644 --- a/src/routes/(app)/[username]/invoice/+server.js +++ b/src/routes/(app)/[username]/invoice/+server.js @@ -1,7 +1,7 @@ -import { auth, post } from '$lib/utils'; -import { json } from '@sveltejs/kit'; +import { auth, post } from "$lib/utils"; +import { json } from "@sveltejs/kit"; export let POST = async ({ cookies: c, request: r }) => { - let j = await r.json(); - return json(await post('/invoice', j, auth(c))); + let j = await r.json(); + return json(await post("/invoice", j, auth(c))); }; diff --git a/src/routes/(app)/[username]/invoice/[id]/+layout.server.js b/src/routes/(app)/[username]/invoice/[id]/+layout.server.js index 4d7fd3b8..b9d1c43d 100644 --- a/src/routes/(app)/[username]/invoice/[id]/+layout.server.js +++ b/src/routes/(app)/[username]/invoice/[id]/+layout.server.js @@ -1,31 +1,39 @@ -import Qr from 'qrcode-base64'; -import { get } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import Qr from "qrcode-base64"; +import { get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export async function load({ depends, params, url, parent }) { - depends('app:invoice'); + depends("app:invoice"); - let { user } = await parent(); - let { id } = params; - let invoice = await get(`/invoice/${id}`); - let options = !!url.searchParams.get('options'); + let { user } = await parent(); + let { id } = params; + let invoice = await get(`/invoice/${id}`); + let options = !!url.searchParams.get("options"); - if (user && invoice.uid !== user.id && !url.pathname.includes('tip') && !options) { - throw redirect(307, `/send/invoice/${id}`); - } + if ( + user && + invoice.uid !== user.id && + !url.pathname.includes("tip") && + !options + ) { + throw redirect(307, `/send/invoice/${id}`); + } - let { amount, pending, received } = invoice; - amount = parseInt(amount); + let { amount, pending, received } = invoice; + amount = parseInt(amount); - let paid = (!amount && received) || (amount > 0 && (pending >= amount || received >= amount)); - if (paid && !url.pathname.endsWith('paid')) { - throw redirect( - 307, - `/${params.username}/invoice/${id}/paid` + (options ? '?options=true' : '') - ); - } + let paid = + (!amount && received) || + (amount > 0 && (pending >= amount || received >= amount)); + if (paid && !url.pathname.endsWith("paid")) { + throw redirect( + 307, + `/${params.username}/invoice/${id}/paid` + + (options ? "?options=true" : "") + ); + } - let sm = Qr.drawImg(invoice.text || '', { size: 300 }); + let sm = Qr.drawImg(invoice.text || "", { size: 300 }); - return { id, invoice, sm, lg: sm }; + return { id, invoice, sm, lg: sm }; } diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.server.js b/src/routes/(app)/[username]/invoice/[id]/+page.server.js index 1068f273..a57efa64 100644 --- a/src/routes/(app)/[username]/invoice/[id]/+page.server.js +++ b/src/routes/(app)/[username]/invoice/[id]/+page.server.js @@ -1,5 +1,5 @@ -import invoice from '$lib/invoice'; +import invoice from "$lib/invoice"; export const actions = { - default: invoice + default: invoice, }; diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/+page.svelte index a0894dd1..b6bf70b7 100644 --- a/src/routes/(app)/[username]/invoice/[id]/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[id]/+page.svelte @@ -1,217 +1,235 @@ <script> - import { enhance } from '$app/forms'; - import { send } from '$lib/socket'; - import { post, back, copy, f, get, types, sat, reverseFormat, s, sats } from '$lib/utils'; - import { tick, onMount, onDestroy } from 'svelte'; - import { browser } from '$app/environment'; - import { last } from '$lib/store'; - import { Avatar, Icon, Heart, Image } from '$comp'; - import { t } from '$lib/translations'; - import { goto, invalidate } from '$app/navigation'; + import { enhance } from "$app/forms"; + import { send } from "$lib/socket"; + import { + post, + back, + copy, + f, + get, + types, + sat, + reverseFormat, + s, + sats, + } from "$lib/utils"; + import { tick, onMount, onDestroy } from "svelte"; + import { browser } from "$app/environment"; + import { last } from "$lib/store"; + import { Avatar, Icon, Heart, Image } from "$comp"; + import { t } from "$lib/translations"; + import { goto, invalidate } from "$app/navigation"; - export let data; + export let data; - let ndef; + let ndef; - let reading = async ({ message, serialNumber }) => { - let name = serialNumber.replace(/:/g, '-'); - let result = await post(`/fund/${name}/withdraw`, { amount, hash, name }); - }; + let reading = async ({ message, serialNumber }) => { + let name = serialNumber.replace(/:/g, "-"); + let result = await post(`/fund/${name}/withdraw`, { amount, hash, name }); + }; - let readingerror = (e) => console.log('nfc error', e); + let readingerror = (e) => console.log("nfc error", e); - $: refresh(data); - let { invoice, id, user, sm, lg } = data; - let { - amount, - hash, - type, - rate, - received, - prompt, - text, - tip, - request_id, - user: { username, currency } - } = invoice; + $: refresh(data); + let { invoice, id, user, sm, lg } = data; + let { + amount, + hash, + type, + rate, + received, + prompt, + text, + tip, + request_id, + user: { username, currency }, + } = invoice; - $: showQr = invoice.uid === user?.id; + $: showQr = invoice.uid === user?.id; - $: src = sm; + $: src = sm; - let qr; - let tipPercent = 0; + let qr; + let tipPercent = 0; - let refresh = async (data) => { - ({ invoice, id, user, sm, lg } = data); - ({ - amount, - hash, - type, - rate, - received, - prompt, - text, - tip, - user: { username, currency } - } = invoice); + let refresh = async (data) => { + ({ invoice, id, user, sm, lg } = data); + ({ + amount, + hash, + type, + rate, + received, + prompt, + text, + tip, + user: { username, currency }, + } = invoice); - tipPercent = (tip / amount) * 100; + tipPercent = (tip / amount) * 100; - // if (browser && window.NDEFReader) { - // try { - // ndef = new NDEFReader(); - // await ndef.scan(); - // - // ndef.removeEventListener('readingerror', readingerror); - // ndef.removeEventListener('reading', reading); - // - // ndef.addEventListener('readingerror', readingerror); - // ndef.addEventListener('reading', reading); - // } catch (e) { - // console.log(e); - // } - // } - }; + // if (browser && window.NDEFReader) { + // try { + // ndef = new NDEFReader(); + // await ndef.scan(); + // + // ndef.removeEventListener('readingerror', readingerror); + // ndef.removeEventListener('reading', reading); + // + // ndef.addEventListener('readingerror', readingerror); + // ndef.addEventListener('reading', reading); + // } catch (e) { + // console.log(e); + // } + // } + }; - $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); - $: tipAmount = ((tip * rate) / sats).toFixed(2); + $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); + $: tipAmount = ((tip * rate) / sats).toFixed(2); - let subbed; + let subbed; - onMount(async () => { - if (browser) { - last.subscribe((v) => { - if (!v || subbed) return; - subbed = true; - send('subscribe', invoice); - }); - } - }); + onMount(async () => { + if (browser) { + last.subscribe((v) => { + if (!v || subbed) return; + subbed = true; + send("subscribe", invoice); + }); + } + }); - onDestroy(() => { - if (ndef) { - ndef.removeEventListener('readingerror', readingerror); - ndef.removeEventListener('reading', reading); - } - }); + onDestroy(() => { + if (ndef) { + ndef.removeEventListener("readingerror", readingerror); + ndef.removeEventListener("reading", reading); + } + }); - $: link = type === 'bitcoin' ? text : `lightning:${text}`; - $: txt = type === 'bitcoin' ? hash : text; + $: link = type === "bitcoin" ? text : `lightning:${text}`; + $: txt = type === "bitcoin" ? hash : text; - let toggleType = async () => { - invoice.type = invoice.type === types.lightning ? types.bitcoin : types.lightning; - ({ id } = await post(`/${username}/invoice`, { - invoice, - user: { username, currency } - })); + let toggleType = async () => { + invoice.type = + invoice.type === types.lightning ? types.bitcoin : types.lightning; + ({ id } = await post(`/${username}/invoice`, { + invoice, + user: { username, currency }, + })); - goto(`./${id}`, { invalidateAll: true }); - }; + goto(`./${id}`, { invalidateAll: true }); + }; </script> <div class="container mx-auto max-w-lg px-4 space-y-8"> - <div class="whitespace-nowrap my-auto ml-auto flex gap-2"> - <button - class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full" - class:bg-black={type === types.lightning} - class:text-white={type === types.lightning} - on:click={toggleType} - > - ⚡️ Lightning - </button> + <div class="whitespace-nowrap my-auto ml-auto flex gap-2"> + <button + class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full" + class:bg-black={type === types.lightning} + class:text-white={type === types.lightning} + on:click={toggleType} + > + ⚡️ Lightning + </button> - <button - class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full flex justify-center " - class:bg-black={type === types.bitcoin} - class:text-white={type === types.bitcoin} - on:click={toggleType} - > - <img - src="/images/bitcoin.svg" - class="my-auto w-8 border-4 border-transparent" - alt="Bitcoin" - /> - <div class="my-auto">Bitcoin</div> - </button> - </div> + <button + class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full flex justify-center" + class:bg-black={type === types.bitcoin} + class:text-white={type === types.bitcoin} + on:click={toggleType} + > + <img + src="/images/bitcoin.svg" + class="my-auto w-8 border-4 border-transparent" + alt="Bitcoin" + /> + <div class="my-auto">Bitcoin</div> + </button> + </div> - {#if showQr} - <div> - <a href={invoice.type === 'bitcoin' ? invoice.text : 'lightning:' + invoice.text}> - <div class="relative flex"> - <div class="flex mx-auto w-[360px] h-[360px]"> - <img - {src} - class="w-[300px] h-[300px] mx-auto z-10 mt-[20px]" - bind:this={qr} - alt={txt} - /> - </div> - <div - class="absolute m-auto left-0 right-0 w-[340px] h-[340px] rounded-full bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] z-0" - /> - </div> - </a> - </div> - {/if} + {#if showQr} + <div> + <a + href={invoice.type === "bitcoin" + ? invoice.text + : "lightning:" + invoice.text} + > + <div class="relative flex"> + <div class="flex mx-auto w-[360px] h-[360px]"> + <img + {src} + class="w-[300px] h-[300px] mx-auto z-10 mt-[20px]" + bind:this={qr} + alt={txt} + /> + </div> + <div + class="absolute m-auto left-0 right-0 w-[340px] h-[340px] rounded-full bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] z-0" + /> + </div> + </a> + </div> + {/if} - {#if amount > 0} - <div class="text-center font-bold text-2xl"> - <div> - {f(amountFiat, currency)} + {#if amount > 0} + <div class="text-center font-bold text-2xl"> + <div> + {f(amountFiat, currency)} - {#if tip} - <span class="text-sm"> - +{f(tip * (rate / sats), currency)} - </span> - {/if} - </div> - <div> - <span class="text-secondary font-normal text-lg">{`${sat(amount)}`}</span> + {#if tip} + <span class="text-sm"> + +{f(tip * (rate / sats), currency)} + </span> + {/if} + </div> + <div> + <span class="text-secondary font-normal text-lg" + >{`${sat(amount)}`}</span + > - {#if tip} - <span class="text-sm text-secondary font-normal"> - +{sat(tip)} - </span> - {/if} - </div> - </div> - {/if} + {#if tip} + <span class="text-sm text-secondary font-normal"> + +{sat(tip)} + </span> + {/if} + </div> + </div> + {/if} - <div class="text-center break-all"> - <a href={link}> - {txt} - </a> - </div> + <div class="text-center break-all"> + <a href={link}> + {txt} + </a> + </div> - <div class="w-full flex justify-center gap-2 flex-wrap"> - <a href={link} class="w-full md:w-auto"> - <button - class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80" - > - <Icon icon="mobile" style="mr-1 w-6" /> - <div class="text-secondary">{$t('payments.openLink')}</div> - </button> - </a> + <div class="w-full flex justify-center gap-2 flex-wrap"> + <a href={link} class="w-full md:w-auto"> + <button + class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80" + > + <Icon icon="mobile" style="mr-1 w-6" /> + <div class="text-secondary">{$t("payments.openLink")}</div> + </button> + </a> - <button - class="flex rounded-full justify-center border py-3 px-5 hover:opacity-80 w-full md:w-auto" - on:click={() => copy(txt)} - > - <Icon icon="copy" style="mr-1" /> - <div class="text-secondary">{$t('payments.copyText')}</div></button - > + <button + class="flex rounded-full justify-center border py-3 px-5 hover:opacity-80 w-full md:w-auto" + on:click={() => copy(txt)} + > + <Icon icon="copy" style="mr-1" /> + <div class="text-secondary">{$t("payments.copyText")}</div></button + > - <button - class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80" - on:click={() => (showQr = !showQr)} - > - <Icon icon="qr" style="mr-1 invert" /> - <div class="text-secondary"> - {showQr ? $t('payments.hide') : $t('payments.show')} - {$t('payments.qr')} - </div></button - > - </div> + <button + class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80" + on:click={() => (showQr = !showQr)} + > + <Icon icon="qr" style="mr-1 invert" /> + <div class="text-secondary"> + {showQr ? $t("payments.hide") : $t("payments.show")} + {$t("payments.qr")} + </div></button + > + </div> </div> diff --git a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte index 84e493d9..655bc3e4 100644 --- a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte @@ -1,64 +1,70 @@ <script> - import { scale } from 'svelte/transition'; - import { Icon } from '$comp'; - import { toast } from '@zerodevx/svelte-toast'; - import { browser } from '$app/environment'; - import { t } from '$lib/translations'; - import { fiat, f, s, sat, sats } from '$lib/utils'; + import { scale } from "svelte/transition"; + import { Icon } from "$comp"; + import { toast } from "@zerodevx/svelte-toast"; + import { browser } from "$app/environment"; + import { t } from "$lib/translations"; + import { fiat, f, s, sat, sats } from "$lib/utils"; - export let data; - let amount, currency, rate, received, pending, tip, user, id; - $: data && ({ amount, currency, rate, received, pending, tip, user, id } = data.invoice); + export let data; + let amount, currency, rate, received, pending, tip, user, id; + $: data && + ({ amount, currency, rate, received, pending, tip, user, id } = + data.invoice); - toast.pop(0); + toast.pop(0); </script> <div class="container px-4 text-center mx-auto"> - <div class="flex w-full py-5 max-w-[200px] mx-auto" in:scale={{ start: 0.5 }}> - <Icon icon="check" style="mx-auto" /> - </div> - {#if received} - <h1 class="text-3xl md:text-4xl font-bold mb-6">{$t('invoice.paymentSuccessful')}</h1> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(fiat(received - tip, rate), currency)} - {#if tip} - <span class="text-lg"> - + {f(fiat(tip, rate), currency)} - </span> - {/if} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1"> - ⚡️{s(received - tip)} + <div class="flex w-full py-5 max-w-[200px] mx-auto" in:scale={{ start: 0.5 }}> + <Icon icon="check" style="mx-auto" /> + </div> + {#if received} + <h1 class="text-3xl md:text-4xl font-bold mb-6"> + {$t("invoice.paymentSuccessful")} + </h1> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(fiat(received - tip, rate), currency)} + {#if tip} + <span class="text-lg"> + + {f(fiat(tip, rate), currency)} + </span> + {/if} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1"> + ⚡️{s(received - tip)} - {#if tip} - <span class="text-lg"> - + ⚡️{s(tip)} - </span> - {/if} - </h3> - {/if} + {#if tip} + <span class="text-lg"> + + ⚡️{s(tip)} + </span> + {/if} + </h3> + {/if} - {#if pending} - <h1 class="text-3xl md:text-4xl font-bold mb-6">Payment detected</h1> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(fiat(pending - tip, rate), currency)} - {#if tip} - + {f(fiat(tip, rate), currency)} - {/if} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1"> - ⚡️{s(pending - tip)} - {#if tip} - <span class="text-lg"> - + ⚡️{s(tip)} - </span> - {/if} - </h3> - {/if} + {#if pending} + <h1 class="text-3xl md:text-4xl font-bold mb-6">Payment detected</h1> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(fiat(pending - tip, rate), currency)} + {#if tip} + + {f(fiat(tip, rate), currency)} + {/if} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1"> + ⚡️{s(pending - tip)} + {#if tip} + <span class="text-lg"> + + ⚡️{s(tip)} + </span> + {/if} + </h3> + {/if} </div> <a href={`/${user.username}/payments`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> </a> -<div class="fixed bottom-10 w-full text-center">{$t('payments.tapAnywhere')}</div> +<div class="fixed bottom-10 w-full text-center"> + {$t("payments.tapAnywhere")} +</div> diff --git a/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.js b/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.js index 814900fb..c9377e36 100644 --- a/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.js +++ b/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.js @@ -1,11 +1,12 @@ -import invoice from '$lib/invoice'; -import { redirect } from '@sveltejs/kit'; +import invoice from "$lib/invoice"; +import { redirect } from "@sveltejs/kit"; export async function load({ parent, params }) { - let { invoice } = await parent(); - if (!invoice.amount) throw redirect(307, `/${params.username}/invoice/${invoice.id}`); + let { invoice } = await parent(); + if (!invoice.amount) + throw redirect(307, `/${params.username}/invoice/${invoice.id}`); } export const actions = { - default: invoice + default: invoice, }; diff --git a/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte index fe6c67be..87e48f01 100644 --- a/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte @@ -1,201 +1,203 @@ <script> - import { onMount } from 'svelte'; - import { browser } from '$app/environment'; - import { invoice as inv, request } from '$lib/store'; - import { copy, focus, f, sat, get, reverseFormat, s, sats } from '$lib/utils'; - import { Icon, Slider } from '$comp'; - import { goto } from '$app/navigation'; - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { tick } from 'svelte'; + import { onMount } from "svelte"; + import { browser } from "$app/environment"; + import { invoice as inv, request } from "$lib/store"; + import { copy, focus, f, sat, get, reverseFormat, s, sats } from "$lib/utils"; + import { Icon, Slider } from "$comp"; + import { goto } from "$app/navigation"; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { tick } from "svelte"; - const tipAmounts = ['No', '10%', '15%', '20%']; - let showCustomAmount; + const tipAmounts = ["No", "10%", "15%", "20%"]; + let showCustomAmount; - let customInput; - let customTipAmount; + let customInput; + let customTipAmount; - let apply = async () => { - if (customTipAmount > 0) { - tipPercent = (customTipAmount / amountFiat) * 100; - showCustomAmount = false; - } - }; + let apply = async () => { + if (customTipAmount > 0) { + tipPercent = (customTipAmount / amountFiat) * 100; + showCustomAmount = false; + } + }; - let active = (amount) => - !customTipAmount && Math.round(tipPercent) === parseInt(amount.slice(0, 2)); + let active = (amount) => + !customTipAmount && Math.round(tipPercent) === parseInt(amount.slice(0, 2)); - const handleCustomTipAmount = (e) => { - tipPercent = 0; - customTipAmount = e.target.value; - }; + const handleCustomTipAmount = (e) => { + tipPercent = 0; + customTipAmount = e.target.value; + }; - let timeout; - const handleSlide = (e) => { - customTipAmount = ''; - if (customInput) { - customInput.value = ''; - } - tipPercent = e.target.value; - }; + let timeout; + const handleSlide = (e) => { + customTipAmount = ""; + if (customInput) { + customInput.value = ""; + } + tipPercent = e.target.value; + }; - let submitting; - const handleTipButtonClick = async (amount) => { - submitting = true; - if (amount === 'No') { - tipPercent = 0; - } else { - tipPercent = parseInt(amount.slice(0, 2)); - } + let submitting; + const handleTipButtonClick = async (amount) => { + submitting = true; + if (amount === "No") { + tipPercent = 0; + } else { + tipPercent = parseInt(amount.slice(0, 2)); + } - await tick(); - submit.click(); - }; + await tick(); + submit.click(); + }; - let submit; + let submit; - export let data; - $: refresh(data); - let { invoice, id, user, rates } = data; - let { - amount, - hash, - type, - rate, - received, - prompt, - text, - tip, - user: { username, currency } - } = invoice; + export let data; + $: refresh(data); + let { invoice, id, user, rates } = data; + let { + amount, + hash, + type, + rate, + received, + prompt, + text, + tip, + user: { username, currency }, + } = invoice; - let qr; - let tipPercent = 0; + let qr; + let tipPercent = 0; - let fullscreen; + let fullscreen; - let refresh = (data) => { - $inv = null; + let refresh = (data) => { + $inv = null; - ({ invoice, id } = data); - ({ - amount, - hash, - type, - received, - prompt, - text, - tip, - user: { username } - } = invoice); + ({ invoice, id } = data); + ({ + amount, + hash, + type, + received, + prompt, + text, + tip, + user: { username }, + } = invoice); - tipPercent = (tip / amount) * 100; - }; + tipPercent = (tip / amount) * 100; + }; - $: currency = user?.currency || invoice.currency; - $: rate = invoice.rate * (rates[currency] / rates[invoice.currency]); + $: currency = user?.currency || invoice.currency; + $: rate = invoice.rate * (rates[currency] / rates[invoice.currency]); - $: tip = Math.round((amount / 100) * tipPercent); + $: tip = Math.round((amount / 100) * tipPercent); - $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); + $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); - $: tipAmount = ((tip * rate) / sats).toFixed(2); + $: tipAmount = ((tip * rate) / sats).toFixed(2); - $: invoiceAmountFiatFormatted = f(amountFiat, currency); + $: invoiceAmountFiatFormatted = f(amountFiat, currency); - onMount(() => ($request = undefined)); + onMount(() => ($request = undefined)); </script> <div class="container px-4 max-w-lg text-center mx-auto"> - <form method="POST" use:enhance class:invisible={submitting}> - <input type="hidden" name="amount" value={amount} /> - <input type="hidden" name="tip" value={tip} /> - <input type="hidden" name="username" value={username} /> - <input type="hidden" name="rate" value={invoice.rate} /> - <input type="hidden" name="prompt" value="false" /> - <input type="hidden" name="type" value={type} /> - <input type="hidden" name="hash" value={hash} /> + <form method="POST" use:enhance class:invisible={submitting}> + <input type="hidden" name="amount" value={amount} /> + <input type="hidden" name="tip" value={tip} /> + <input type="hidden" name="username" value={username} /> + <input type="hidden" name="rate" value={invoice.rate} /> + <input type="hidden" name="prompt" value="false" /> + <input type="hidden" name="type" value={type} /> + <input type="hidden" name="hash" value={hash} /> - <input type="hidden" name="request_id" value={invoice.request?.id} /> + <input type="hidden" name="request_id" value={invoice.request?.id} /> - <h1 class="text-4xl font-semibold my-8">{$t('invoice.addTipq')}</h1> - {#if !showCustomAmount} - <div class="my-2 text-xl">{Math.round(tipPercent)}%</div> + <h1 class="text-4xl font-semibold my-8">{$t("invoice.addTipq")}</h1> + {#if !showCustomAmount} + <div class="my-2 text-xl">{Math.round(tipPercent)}%</div> - <Slider handle={handleSlide} bind:value={tipPercent} /> + <Slider handle={handleSlide} bind:value={tipPercent} /> - <div class="flex mb-8 text-lg"> - <div> - <span class="mr-1"> - {f(amountFiat, currency)} - <span class="font-semibold">+{f(tipAmount, currency)}</span> - </span> - </div> + <div class="flex mb-8 text-lg"> + <div> + <span class="mr-1"> + {f(amountFiat, currency)} + <span class="font-semibold">+{f(tipAmount, currency)}</span> + </span> + </div> - <div class="ml-auto text-lg"> - {sat(amount)} - <span class="font-semibold">+{sat(tip)}</span> - </div> - </div> + <div class="ml-auto text-lg"> + {sat(amount)} + <span class="font-semibold">+{sat(tip)}</span> + </div> + </div> - {#if tip} - <div> - <button type="button" on:click={() => submit.click()}>Next</button> - <button type="button" on:click={() => (tipPercent = 0)}>Reset</button> - </div> - {:else} - {#each tipAmounts as amount} - <button - type="button" - class:active={active(amount, tipPercent)} - on:click={() => handleTipButtonClick(amount)}>{amount}</button - > - {/each} - {/if} + {#if tip} + <div> + <button type="button" on:click={() => submit.click()}>Next</button> + <button type="button" on:click={() => (tipPercent = 0)}>Reset</button> + </div> + {:else} + {#each tipAmounts as amount} + <button + type="button" + class:active={active(amount, tipPercent)} + on:click={() => handleTipButtonClick(amount)}>{amount}</button + > + {/each} + {/if} - <div> - <button type="button" on:click={() => (showCustomAmount = true)}>Custom</button> - </div> - {/if} + <div> + <button type="button" on:click={() => (showCustomAmount = true)} + >Custom</button + > + </div> + {/if} - <button type="submit" bind:this={submit} /> - </form> + <button type="submit" bind:this={submit} /> + </form> - {#if showCustomAmount} - <form on:submit|preventDefault={apply}> - <div class="relative"> - <span - class="absolute top-[18px] left-5 font-semibold {!customTipAmount - ? 'opacity-50' - : 'opacity-100'}">$</span - > - <input - bind:this={customInput} - type="number" - step="0.01" - on:input={(e) => handleCustomTipAmount(e)} - class="pl-10" - placeholder="Custom amount" - use:focus - /> - <button - type="submit" - class="w-full mt-2 bg-black text-white font-semibold py-3 px-7 rounded-2xl {!customTipAmount - ? 'opacity-50' - : 'opacity-100 hover:opacity-80'}" - disabled={!customTipAmount}>Apply</button - > - </div> - </form> - {/if} + {#if showCustomAmount} + <form on:submit|preventDefault={apply}> + <div class="relative"> + <span + class="absolute top-[18px] left-5 font-semibold {!customTipAmount + ? 'opacity-50' + : 'opacity-100'}">$</span + > + <input + bind:this={customInput} + type="number" + step="0.01" + on:input={(e) => handleCustomTipAmount(e)} + class="pl-10" + placeholder="Custom amount" + use:focus + /> + <button + type="submit" + class="w-full mt-2 bg-black text-white font-semibold py-3 px-7 rounded-2xl {!customTipAmount + ? 'opacity-50' + : 'opacity-100 hover:opacity-80'}" + disabled={!customTipAmount}>Apply</button + > + </div> + </form> + {/if} </div> <style> - .active { - @apply !bg-black !text-white; - } + .active { + @apply !bg-black !text-white; + } - .container button[type='button'] { - @apply w-full mt-2 bg-primary text-black hover:bg-black hover:text-white font-semibold py-5 px-7 rounded-2xl; - } + .container button[type="button"] { + @apply w-full mt-2 bg-primary text-black hover:bg-black hover:text-white font-semibold py-5 px-7 rounded-2xl; + } </style> diff --git a/src/routes/(app)/[username]/invoice/sent/+page.svelte b/src/routes/(app)/[username]/invoice/sent/+page.svelte index ae71eb2e..8583de09 100644 --- a/src/routes/(app)/[username]/invoice/sent/+page.svelte +++ b/src/routes/(app)/[username]/invoice/sent/+page.svelte @@ -1,10 +1,10 @@ <script> - import { Avatar, Icon } from '$comp'; - import { request } from '$lib/store'; + import { Avatar, Icon } from "$comp"; + import { request } from "$lib/store"; </script> Invoice sent to <div class="flex"> - <Avatar user={$request.requester} size={12} /> - <h1 class="my-auto"><b>{$request.requester.username}</b> is ready to pay</h1> + <Avatar user={$request.requester} size={12} /> + <h1 class="my-auto"><b>{$request.requester.username}</b> is ready to pay</h1> </div> diff --git a/src/routes/(app)/[username]/map/+page.server.js b/src/routes/(app)/[username]/map/+page.server.js index 7936e88b..616d28f6 100644 --- a/src/routes/(app)/[username]/map/+page.server.js +++ b/src/routes/(app)/[username]/map/+page.server.js @@ -1,12 +1,12 @@ -import { get } from '$lib/utils'; +import { get } from "$lib/utils"; // export const prerender = true; export let load = async () => { - let data = { locations: [] }; + let data = { locations: [] }; - try { - data = await get('/locations'); - } catch (e) {} + try { + data = await get("/locations"); + } catch (e) {} - return data; + return data; }; diff --git a/src/routes/(app)/[username]/map/+page.svelte b/src/routes/(app)/[username]/map/+page.svelte index 99c822d3..a2ae657c 100644 --- a/src/routes/(app)/[username]/map/+page.svelte +++ b/src/routes/(app)/[username]/map/+page.svelte @@ -1,8 +1,8 @@ <script> - import { LocationsMap } from '$comp'; - export let data; + import { LocationsMap } from "$comp"; + export let data; - let { locations } = data; + let { locations } = data; </script> <LocationsMap {locations} /> diff --git a/src/routes/(app)/[username]/messages/+page.server.js b/src/routes/(app)/[username]/messages/+page.server.js index ec5c5eee..f0148be7 100644 --- a/src/routes/(app)/[username]/messages/+page.server.js +++ b/src/routes/(app)/[username]/messages/+page.server.js @@ -1,22 +1,25 @@ -import { get } from '$lib/utils'; +import { get } from "$lib/utils"; export async function load({ params, parent, url }) { - let { user, subject } = await parent(); - let { since = 0 } = params; + let { user, subject } = await parent(); + let { since = 0 } = params; - let messages = []; + let messages = []; - try { - messages = await get(`/${user.pubkey}/${since}/messages`); + try { + messages = await get(`/${user.pubkey}/${since}/messages`); - messages = messages - .filter((m) => m.recipient?.id === subject.id || m.author?.id === subject.id) - .sort((a, b) => a.created_at - b.created_at); + messages = messages + .filter( + (m) => m.recipient?.id === subject.id || m.author?.id === subject.id + ) + .sort((a, b) => a.created_at - b.created_at); - if (user.id === subject.id) messages = messages.filter((m) => m.recipient.id === m.author.id); - } catch (e) { - console.log(`failed to fetch nostr messages`, e); - } + if (user.id === subject.id) + messages = messages.filter((m) => m.recipient.id === m.author.id); + } catch (e) { + console.log(`failed to fetch nostr messages`, e); + } - return { messages, user }; + return { messages, user }; } diff --git a/src/routes/(app)/[username]/messages/+page.svelte b/src/routes/(app)/[username]/messages/+page.svelte index cfa7f741..16b82679 100644 --- a/src/routes/(app)/[username]/messages/+page.svelte +++ b/src/routes/(app)/[username]/messages/+page.svelte @@ -1,171 +1,182 @@ <script> - import { cubicInOut } from 'svelte/easing'; - import { browser } from '$app/environment'; - import { t } from '$lib/translations'; - import { format } from 'date-fns'; - import { scale, fade, fly } from 'svelte/transition'; - import { enhance } from '$app/forms'; - import { Avatar, Icon } from '$comp'; - import { back, fail, focus } from '$lib/utils'; - import { sign, send, encrypt, decrypt } from '$lib/nostr'; - import { event as e, password } from '$lib/store'; - import { tick, onMount } from 'svelte'; - import { getEventHash } from 'nostr-tools'; + import { cubicInOut } from "svelte/easing"; + import { browser } from "$app/environment"; + import { t } from "$lib/translations"; + import { format } from "date-fns"; + import { scale, fade, fly } from "svelte/transition"; + import { enhance } from "$app/forms"; + import { Avatar, Icon } from "$comp"; + import { back, fail, focus } from "$lib/utils"; + import { sign, send, encrypt, decrypt } from "$lib/nostr"; + import { event as e, password } from "$lib/store"; + import { tick, onMount } from "svelte"; + import { getEventHash } from "nostr-tools"; - export let data; + export let data; - function fadeScale(node, { delay = 0, duration = 200, easing = (x) => x, baseScale = 0 }) { - const o = +getComputedStyle(node).opacity; - const m = getComputedStyle(node).transform.match(/scale\(([0-9.]+)\)/); - const s = m ? m[1] : 1; - const is = 1 - baseScale; + function fadeScale( + node, + { delay = 0, duration = 200, easing = (x) => x, baseScale = 0 } + ) { + const o = +getComputedStyle(node).opacity; + const m = getComputedStyle(node).transform.match(/scale\(([0-9.]+)\)/); + const s = m ? m[1] : 1; + const is = 1 - baseScale; - return { - delay, - duration, - css: (t) => { - const eased = easing(t); - return `opacity: ${eased * o}; transform: scale(${eased * s * is + baseScale})`; - } - }; - } + return { + delay, + duration, + css: (t) => { + const eased = easing(t); + return `opacity: ${eased * o}; transform: scale(${ + eased * s * is + baseScale + })`; + }, + }; + } - let { messages, subject, user } = data; - let input, pane; + let { messages, subject, user } = data; + let input, pane; - $: initialize($password); - let initialize = async (p) => { - if (!p) return; - await Promise.all( - messages.map(async (event) => (event.message = await decrypt({ event, user }))) - ); + $: initialize($password); + let initialize = async (p) => { + if (!p) return; + await Promise.all( + messages.map( + async (event) => (event.message = await decrypt({ event, user })) + ) + ); - messages = messages; - tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); - }; + messages = messages; + tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); + }; - e.subscribe(async (event) => { - let found = ~messages.findIndex((m) => m.id === event?.id); - if (event?.recipient.id === user.id && !found) { - event.message = await decrypt({ event, user }); - messages.push(event); - messages = messages; - tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); - } - }); + e.subscribe(async (event) => { + let found = ~messages.findIndex((m) => m.id === event?.id); + if (event?.recipient.id === user.id && !found) { + event.message = await decrypt({ event, user }); + messages.push(event); + messages = messages; + tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); + } + }); - let sent, submitting, message; - let submit = async () => { - submitting = true; + let sent, submitting, message; + let submit = async () => { + submitting = true; - let event = { - pubkey: user.pubkey, - created_at: Math.floor(Date.now() / 1000), - kind: 4, - tags: [['p', subject.pubkey]] - }; + let event = { + pubkey: user.pubkey, + created_at: Math.floor(Date.now() / 1000), + kind: 4, + tags: [["p", subject.pubkey]], + }; - event.message = message; + event.message = message; - message = ''; + message = ""; - event.author = user; - event.recipient = subject; + event.author = user; + event.recipient = subject; - messages.push(event); - messages = messages; - tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); + messages.push(event); + messages = messages; + tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); - try { - event.content = await encrypt({ message: event.message, recipient: subject.pubkey, user }); - event.id = getEventHash(event); - await sign({ event, user }); - await send(event); + try { + event.content = await encrypt({ + message: event.message, + recipient: subject.pubkey, + user, + }); + event.id = getEventHash(event); + await sign({ event, user }); + await send(event); - sent = true; - } catch (e) { - console.log(e); - fail('Failed to send message'); - } + sent = true; + } catch (e) { + console.log(e); + fail("Failed to send message"); + } - message = ''; - submitting = false; - }; + message = ""; + submitting = false; + }; - let keydown = (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - submit(); - } - }; + let keydown = (e) => { + if (e.key === "Enter") { + e.preventDefault(); + submit(); + } + }; </script> <div class="container max-w-xl mx-auto px-4 space-y-5"> - <div - class="h-[50vh] max-h-[50vh] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8" - bind:this={pane} - > - {#each messages as { id, author, message, created_at, pubkey }} - {@const ours = pubkey === user.pubkey} - {@const theirs = !ours} + <div + class="h-[50vh] max-h-[50vh] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8" + bind:this={pane} + > + {#each messages as { id, author, message, created_at, pubkey }} + {@const ours = pubkey === user.pubkey} + {@const theirs = !ours} - {#if message} - <div - class="flex gap-2" - class:flex-row-reverse={theirs} - class:justify-end={theirs} - in:fadeScale={{ - easing: cubicInOut, - baseScale: 0.5 - }} - > - <div - class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg" - class:ours - class:theirs - :key={id} - > - {message} - </div> - <div class="mt-auto"> - <Avatar user={author} size={'12'} /> - </div> - </div> - <div class="text-sm text-gray-400 mb-6" class:text-right={ours}> - {format(new Date(created_at * 1000), 'MMM d')}, - {format(new Date(created_at * 1000), 'h:mm aa')} - </div> - {/if} - {/each} - </div> + {#if message} + <div + class="flex gap-2" + class:flex-row-reverse={theirs} + class:justify-end={theirs} + in:fadeScale={{ + easing: cubicInOut, + baseScale: 0.5, + }} + > + <div + class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg" + class:ours + class:theirs + :key={id} + > + {message} + </div> + <div class="mt-auto"> + <Avatar user={author} size={"12"} /> + </div> + </div> + <div class="text-sm text-gray-400 mb-6" class:text-right={ours}> + {format(new Date(created_at * 1000), "MMM d")}, + {format(new Date(created_at * 1000), "h:mm aa")} + </div> + {/if} + {/each} + </div> - <form - method="POST" - class="w-full border rounded-xl outline-none gap-4 flex p-0 pr-2" - on:submit|preventDefault={submit} - > - <input type="hidden" name="requester_id" value={user.id} /> - <input type="hidden" name="recipient" value={subject.username} /> + <form + method="POST" + class="w-full border rounded-xl outline-none gap-4 flex p-0 pr-2" + on:submit|preventDefault={submit} + > + <input type="hidden" name="requester_id" value={user.id} /> + <input type="hidden" name="recipient" value={subject.username} /> - <div - use:focus - contenteditable - class="grow break-all py-4 outline-none mt-0 pl-4" - bind:innerHTML={message} - on:keydown={keydown} - /> - <button type="submit" class="my-auto shrink-0"> - <Icon icon="send" style="w-8" /> - </button> - </form> + <div + use:focus + contenteditable + class="grow break-all py-4 outline-none mt-0 pl-4" + bind:innerHTML={message} + on:keydown={keydown} + /> + <button type="submit" class="my-auto shrink-0"> + <Icon icon="send" style="w-8" /> + </button> + </form> </div> <style> - .ours { - @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto; - } + .ours { + @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto; + } - .theirs { - @apply bg-gray-100 text-black rounded-bl-none; - } + .theirs { + @apply bg-gray-100 text-black rounded-bl-none; + } </style> diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.server.js b/src/routes/(app)/[username]/payments/[...page]/+page.server.js index a3b0ad6f..7a3bc179 100644 --- a/src/routes/(app)/[username]/payments/[...page]/+page.server.js +++ b/src/routes/(app)/[username]/payments/[...page]/+page.server.js @@ -1,26 +1,26 @@ -import { get, auth } from '$lib/utils'; +import { get, auth } from "$lib/utils"; let limit = 25; export async function load({ cookies, depends, params, request }) { - depends('app:payments'); + depends("app:payments"); - let parts = params.page.split('/'); + let parts = params.page.split("/"); - let start, end, page; - if (parts.length === 1) [page] = parts; - if (parts.length === 2) [start, page] = parts; - if (parts.length === 3) [start, end, page] = parts; - if (!parseInt(page)) page = 1; + let start, end, page; + if (parts.length === 1) [page] = parts; + if (parts.length === 2) [start, page] = parts; + if (parts.length === 3) [start, end, page] = parts; + if (!parseInt(page)) page = 1; - let offset = (page - 1) * limit; - if (!start) start = Date.now() / 1000 - 24 * 60 * 60; + let offset = (page - 1) * limit; + if (!start) start = Date.now() / 1000 - 24 * 60 * 60; - let url = `/payments?start=${start * 1000}&limit=${limit}&offset=${offset}`; - if (end) url += `&end=${end * 1000}`; + let url = `/payments?start=${start * 1000}&limit=${limit}&offset=${offset}`; + if (end) url += `&end=${end * 1000}`; - let { count, payments, totals } = await get(url, auth(cookies)); - let pages = new Array(Math.ceil(count / limit)); + let { count, payments, totals } = await get(url, auth(cookies)); + let pages = new Array(Math.ceil(count / limit)); - return { payments, page, pages, start, end, totals }; + return { payments, page, pages, start, end, totals }; } diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.svelte b/src/routes/(app)/[username]/payments/[...page]/+page.svelte index 2ea225ad..e61e156c 100644 --- a/src/routes/(app)/[username]/payments/[...page]/+page.svelte +++ b/src/routes/(app)/[username]/payments/[...page]/+page.svelte @@ -1,249 +1,281 @@ <script> - import { Avatar, Icon } from '$comp'; - import { onMount } from 'svelte'; - import { format } from 'date-fns'; - import { newPayment } from '$lib/store'; - import { t } from '$lib/translations'; - import { get, f, s, sat, sats, types } from '$lib/utils'; - import { page } from '$app/stores'; - import { differenceInDays, getUnixTime, sub } from 'date-fns'; - import { goto, invalidate } from '$app/navigation'; + import { Avatar, Icon } from "$comp"; + import { onMount } from "svelte"; + import { format } from "date-fns"; + import { newPayment } from "$lib/store"; + import { t } from "$lib/translations"; + import { get, f, s, sat, sats, types } from "$lib/utils"; + import { page } from "$app/stores"; + import { differenceInDays, getUnixTime, sub } from "date-fns"; + import { goto, invalidate } from "$app/navigation"; - export let data; + export let data; - let { start, end, user, rates, payments } = data; + let { start, end, user, rates, payments } = data; - let change = ({ target: { value } }) => goto(value); - let link = (p) => { - if (p.pot) return `/pot/${p.pot}`; - if (p.with) return '/' + p.with.username; - return `/p/${p.id}`; - }; + let change = ({ target: { value } }) => goto(value); + let link = (p) => { + if (p.pot) return `/pot/${p.pot}`; + if (p.with) return "/" + p.with.username; + return `/p/${p.id}`; + }; - $: presets = [ - { title: $t('payments.day'), start: sub(new Date(), { days: 1 }), end: null }, - { title: $t('payments.week'), start: sub(new Date(), { days: 7 }), end: null }, - { title: $t('payments.month'), start: sub(new Date(), { months: 1 }), end: null }, - { title: $t('payments.all'), start: sub(new Date(), { years: 5 }), end: null } - ]; + $: presets = [ + { + title: $t("payments.day"), + start: sub(new Date(), { days: 1 }), + end: null, + }, + { + title: $t("payments.week"), + start: sub(new Date(), { days: 7 }), + end: null, + }, + { + title: $t("payments.month"), + start: sub(new Date(), { months: 1 }), + end: null, + }, + { + title: $t("payments.all"), + start: sub(new Date(), { years: 5 }), + end: null, + }, + ]; - $: selection = start - ? presets.findIndex((p) => Math.abs(differenceInDays(new Date(start * 1000), p.start)) < 1) - : 0; + $: selection = start + ? presets.findIndex( + (p) => Math.abs(differenceInDays(new Date(start * 1000), p.start)) < 1 + ) + : 0; - let p, - totals, - pages = []; - $: data && ({ page: p, pages, start, end, totals, payments } = data); + let p, + totals, + pages = []; + $: data && ({ page: p, pages, start, end, totals, payments } = data); - $: $page && ($newPayment = false); - $: $newPayment && invalidate(`/users/${user.username}`); + $: $page && ($newPayment = false); + $: $newPayment && invalidate(`/users/${user.username}`); - $: path = $page.params.page - ? $page.url.pathname.substring(0, $page.url.pathname.lastIndexOf('/')) - : $page.url.pathname; + $: path = $page.params.page + ? $page.url.pathname.substring(0, $page.url.pathname.lastIndexOf("/")) + : $page.url.pathname; - let csv = async () => { - let url = `/${user.username}/payments`; - if (start) url += `/${start}`; - if (end) url += `/${end}`; + let csv = async () => { + let url = `/${user.username}/payments`; + if (start) url += `/${start}`; + if (end) url += `/${end}`; - let { payments } = await get(url); + let { payments } = await get(url); - payments = payments.map((p) => ({ - ...p, - created: new Date(p.created), - total: p.amount + (p.tip || 0), - amount_fiat: f((p.amount * p.rate) / sats, p.currency), - fee_fiat: p.fee ? f((p.fee * p.rate) / sats, p.currency) : null, - tip_fiat: p.tip ? f((p.tip * p.rate) / sats, p.currency) : null, - total_fiat: f(((p.amount + (p.tip || 0)) * p.rate) / sats, p.currency) - })); + payments = payments.map((p) => ({ + ...p, + created: new Date(p.created), + total: p.amount + (p.tip || 0), + amount_fiat: f((p.amount * p.rate) / sats, p.currency), + fee_fiat: p.fee ? f((p.fee * p.rate) / sats, p.currency) : null, + tip_fiat: p.tip ? f((p.tip * p.rate) / sats, p.currency) : null, + total_fiat: f(((p.amount + (p.tip || 0)) * p.rate) / sats, p.currency), + })); - let keys = [ - 'type', - 'id', - 'created', - 'rate', - 'currency', - 'amount', - 'tip', - 'total', - 'fee', - 'amount_fiat', - 'tip_fiat', - 'total_fiat', - 'fee_fiat' - ]; + let keys = [ + "type", + "id", + "created", + "rate", + "currency", + "amount", + "tip", + "total", + "fee", + "amount_fiat", + "tip_fiat", + "total_fiat", + "fee_fiat", + ]; - let csv = - keys.map((k) => `"${k}"`).join(',') + - '\n' + - payments.map((r) => keys.map((k) => `"${r[k] || ''}"`).join(',')).join('\n'); + let csv = + keys.map((k) => `"${k}"`).join(",") + + "\n" + + payments + .map((r) => keys.map((k) => `"${r[k] || ""}"`).join(",")) + .join("\n"); - let filename = 'payments.csv'; - let blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); - if (navigator.msSaveBlob) { - navigator.msSaveBlob(blob, filename); - } else { - let link = document.createElement('a'); - if (link.download !== undefined) { - let url = URL.createObjectURL(blob); - link.setAttribute('href', url); - link.setAttribute('download', filename); - link.style.visibility = 'hidden'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } - } - }; + let filename = "payments.csv"; + let blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); + if (navigator.msSaveBlob) { + navigator.msSaveBlob(blob, filename); + } else { + let link = document.createElement("a"); + if (link.download !== undefined) { + let url = URL.createObjectURL(blob); + link.setAttribute("href", url); + link.setAttribute("download", filename); + link.style.visibility = "hidden"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } + }; </script> <div class="mt-24 mb-20"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> - {$t('payments.header')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> + {$t("payments.header")} + </h1> - <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> - <div class="flex text-md text-secondary relative"> - <div class="mx-auto flex justify-center w-full gap-1"> - {#each presets as { start, end, title }, i} - <a - class:active={selection === i} - href={`/${user.username}/payments/${getUnixTime(start) + '/'}1`} - > - <button - class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px]" - > - <div class="my-auto">{title}</div> - </button> - </a> - {/each} - </div> - </div> + <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> + <div class="flex text-md text-secondary relative"> + <div class="mx-auto flex justify-center w-full gap-1"> + {#each presets as { start, end, title }, i} + <a + class:active={selection === i} + href={`/${user.username}/payments/${getUnixTime(start) + "/"}1`} + > + <button + class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px]" + > + <div class="my-auto">{title}</div> + </button> + </a> + {/each} + </div> + </div> - <div class="flex flex-wrap justify-center mb-8"> - {#if pages.length > 1} - {#each pages as _, i} - <a class="mr-1 last:mr-0" href={`${path}/${i + 1}`} class:active={parseInt(p) === i + 1}> - <div class="border py-2 rounded-full border-2 w-12 h-12 hover:opacity-80 text-center"> - {i + 1} - </div> - </a> - {/each} - {/if} - </div> + <div class="flex flex-wrap justify-center mb-8"> + {#if pages.length > 1} + {#each pages as _, i} + <a + class="mr-1 last:mr-0" + href={`${path}/${i + 1}`} + class:active={parseInt(p) === i + 1} + > + <div + class="border py-2 rounded-full border-2 w-12 h-12 hover:opacity-80 text-center" + > + {i + 1} + </div> + </a> + {/each} + {/if} + </div> - <div class="text-base"> - {#each payments as p} - <a href={`/payment/${p.id}`}> - <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4"> - <div class="whitespace-nowrap my-auto"> - <div class="font-bold" class:text-red-800={p.amount < 0}> - {f(Math.abs(p.amount) * (p.rate / sats), p.currency)} - {#if p.tip} - <span class="text-sm text-secondary"> - +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% - </span> - {/if} - </div> + <div class="text-base"> + {#each payments as p} + <a href={`/payment/${p.id}`}> + <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4"> + <div class="whitespace-nowrap my-auto"> + <div class="font-bold" class:text-red-800={p.amount < 0}> + {f(Math.abs(p.amount) * (p.rate / sats), p.currency)} + {#if p.tip} + <span class="text-sm text-secondary"> + +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% + </span> + {/if} + </div> - <div class="text-secondary"> - {sat(Math.abs(p.amount) + (p.tip || 0))} - </div> - </div> + <div class="text-secondary"> + {sat(Math.abs(p.amount) + (p.tip || 0))} + </div> + </div> - <div class="flex my-auto"> - {#if p.type === types.pot} - <a href={`/pot/${p.memo}`}> - <div class="text-secondary flex"> - <div class="my-auto mr-1"> - <img src="/images/moneypot.png" class="w-12" alt="Pot" /> - </div> + <div class="flex my-auto"> + {#if p.type === types.pot} + <a href={`/pot/${p.memo}`}> + <div class="text-secondary flex"> + <div class="my-auto mr-1"> + <img src="/images/moneypot.png" class="w-12" alt="Pot" /> + </div> - <div class="my-auto">Pot</div> - </div> - </a> - {:else if p.with} - <div class="flex"> - <div class="my-auto"> - <Avatar user={p.with} size={16} disabled={true} /> - </div> - <div class="my-auto ml-1 text-secondary">{p.with.username}</div> - </div> - {:else if p.type === types.classic} - <img - src="/images/classic.png" - class="w-24 border-4 border-transparent mr-1" - alt="Bitcoin" - /> - <div class="my-auto text-secondary">{p.memo}</div> - {:else} - <div class="text-secondary flex"> - {#if p.type === types.lightning} - <div class="text-3xl">⚡️</div> - {:else if p.type === types.bitcoin} - <div class="my-auto mr-1"> - <img - src="/images/bitcoin.svg" - class="w-12 border-4 border-transparent" - alt="Bitcoin" - /> - </div> - {/if} + <div class="my-auto">Pot</div> + </div> + </a> + {:else if p.with} + <div class="flex"> + <div class="my-auto"> + <Avatar user={p.with} size={16} disabled={true} /> + </div> + <div class="my-auto ml-1 text-secondary"> + {p.with.username} + </div> + </div> + {:else if p.type === types.classic} + <img + src="/images/classic.png" + class="w-24 border-4 border-transparent mr-1" + alt="Bitcoin" + /> + <div class="my-auto text-secondary">{p.memo}</div> + {:else} + <div class="text-secondary flex"> + {#if p.type === types.lightning} + <div class="text-3xl">⚡️</div> + {:else if p.type === types.bitcoin} + <div class="my-auto mr-1"> + <img + src="/images/bitcoin.svg" + class="w-12 border-4 border-transparent" + alt="Bitcoin" + /> + </div> + {/if} - <div class="my-auto"> - {p.amount > 0 ? (p.confirmed ? 'Received' : 'Pending') : 'Sent'} - </div> - </div> - {/if} - </div> + <div class="my-auto"> + {p.amount > 0 + ? p.confirmed + ? "Received" + : "Pending" + : "Sent"} + </div> + </div> + {/if} + </div> - <div class="text-secondary text-right text-sm my-auto"> - <div> - {format(new Date(p.created), 'h:mm aaa')} - </div> - <div> - {format(new Date(p.created), 'MMM d')} - </div> - </div> - </div> - </a> - {:else} - <p class="text-secondary text-lg text-center">{$t('payments.empty')}</p> - {/each} - </div> + <div class="text-secondary text-right text-sm my-auto"> + <div> + {format(new Date(p.created), "h:mm aaa")} + </div> + <div> + {format(new Date(p.created), "MMM d")} + </div> + </div> + </div> + </a> + {:else} + <p class="text-secondary text-lg text-center">{$t("payments.empty")}</p> + {/each} + </div> - <div class="grid grid-cols-3 w-full text-center"> - <span class="text-lg text-secondary">{$t('payments.total')}</span> - <span class="text-lg text-secondary">{$t('payments.presentValue')}</span> - <span class="text-lg text-secondary">{$t('payments.gainLoss')}</span> + <div class="grid grid-cols-3 w-full text-center"> + <span class="text-lg text-secondary">{$t("payments.total")}</span> + <span class="text-lg text-secondary">{$t("payments.presentValue")}</span> + <span class="text-lg text-secondary">{$t("payments.gainLoss")}</span> - {#each Object.keys(totals) as c} - {@const total = totals[c]['fiat']} - {@const pv = (totals[c]['sats'] * rates[c]) / sats} - {@const gain = (pv * 100) / total - 100} - <span class="text-lg"><b>{f(total, c)}</b></span> - <span class="text-lg"><b>{f(pv, c)}</b></span> - <span class="text-lg" class:text-red-800={gain < 0} - ><b>{Math.abs(gain).toFixed(2)}%</b></span - > - {/each} - </div> + {#each Object.keys(totals) as c} + {@const total = totals[c]["fiat"]} + {@const pv = (totals[c]["sats"] * rates[c]) / sats} + {@const gain = (pv * 100) / total - 100} + <span class="text-lg"><b>{f(total, c)}</b></span> + <span class="text-lg"><b>{f(pv, c)}</b></span> + <span class="text-lg" class:text-red-800={gain < 0} + ><b>{Math.abs(gain).toFixed(2)}%</b></span + > + {/each} + </div> - <button - class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto" - on:click={csv} - > - <Icon icon="save" style="opacity-50 mr-2 my-auto" /> - <div class="my-auto">{$t('payments.export')}</div> - </button> - </div> + <button + class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto" + on:click={csv} + > + <Icon icon="save" style="opacity-50 mr-2 my-auto" /> + <div class="my-auto">{$t("payments.export")}</div> + </button> + </div> </div> <style> - .active * { - @apply bg-black text-white border-black; - } + .active * { + @apply bg-black text-white border-black; + } </style> diff --git a/src/routes/(app)/[username]/payments/[...page]/+server.js b/src/routes/(app)/[username]/payments/[...page]/+server.js index c7c5222d..a81faaf3 100644 --- a/src/routes/(app)/[username]/payments/[...page]/+server.js +++ b/src/routes/(app)/[username]/payments/[...page]/+server.js @@ -1,18 +1,18 @@ -import { get, auth } from '$lib/utils'; -import { json } from '@sveltejs/kit'; +import { get, auth } from "$lib/utils"; +import { json } from "@sveltejs/kit"; export async function GET({ cookies, params }) { - let parts = params.page.split('/'); + let parts = params.page.split("/"); - let start, end; - if (parts.length === 1) [start] = parts; - if (parts.length === 2) [start, end] = parts; + let start, end; + if (parts.length === 1) [start] = parts; + if (parts.length === 2) [start, end] = parts; - if (!start) start = Date.now() / 1000 - 24 * 60 * 60; + if (!start) start = Date.now() / 1000 - 24 * 60 * 60; - let url = `/payments?start=${start * 1000}`; - if (end) url += `&end=${end * 1000}`; + let url = `/payments?start=${start * 1000}`; + if (end) url += `&end=${end * 1000}`; - let { payments } = await get(url, auth(cookies)); - return json({ payments }); + let { payments } = await get(url, auth(cookies)); + return json({ payments }); } diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.server.js b/src/routes/(app)/[username]/receive/[...param]/+page.server.js index cdd89c8b..a6e59175 100644 --- a/src/routes/(app)/[username]/receive/[...param]/+page.server.js +++ b/src/routes/(app)/[username]/receive/[...param]/+page.server.js @@ -1,15 +1,15 @@ -import { auth, get } from '$lib/utils'; -import invoice from '$lib/invoice'; +import { auth, get } from "$lib/utils"; +import invoice from "$lib/invoice"; export let load = async ({ cookies, params }) => { - let id = params.param; + let id = params.param; - let request; - if (id) ({ request } = await get(`/request/${id}`, auth(cookies))); + let request; + if (id) ({ request } = await get(`/request/${id}`, auth(cookies))); - return { ...params, request }; + return { ...params, request }; }; export const actions = { - default: invoice + default: invoice, }; diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.svelte b/src/routes/(app)/[username]/receive/[...param]/+page.svelte index d7001079..085a6f6d 100644 --- a/src/routes/(app)/[username]/receive/[...param]/+page.svelte +++ b/src/routes/(app)/[username]/receive/[...param]/+page.svelte @@ -1,50 +1,54 @@ <script> - import { Icon, Numpad, Toggle } from '$comp'; - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { page } from '$app/stores'; - import { requestRedirect } from '$lib/store'; - import { browser } from '$app/environment'; + import { Icon, Numpad, Toggle } from "$comp"; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { page } from "$app/stores"; + import { requestRedirect } from "$lib/store"; + import { browser } from "$app/environment"; - export let data; - let type = 'lightning'; + export let data; + let type = "lightning"; - if ($page.params.param === 'bitcoin') type = 'bitcoin'; + if ($page.params.param === "bitcoin") type = "bitcoin"; - let { request } = data; + let { request } = data; - let amount; + let amount; - let lightning = true; - let { currency, username, prompt } = data.subject; - let { rates, user } = data; - let submit; + let lightning = true; + let { currency, username, prompt } = data.subject; + let { rates, user } = data; + let submit; - $: rate = rates[currency]; - $: type = lightning ? 'lightning' : 'bitcoin'; + $: rate = rates[currency]; + $: type = lightning ? "lightning" : "bitcoin"; </script> -<form method="POST" class="flex justify-center items-center mt-24 mb-3 px-3" use:enhance> - <input type="hidden" name="amount" value={amount} /> - <input type="hidden" name="currency" value={currency} /> - <input type="hidden" name="username" value={username} /> - <input type="hidden" name="type" value={type} /> +<form + method="POST" + class="flex justify-center items-center mt-24 mb-3 px-3" + use:enhance +> + <input type="hidden" name="amount" value={amount} /> + <input type="hidden" name="currency" value={currency} /> + <input type="hidden" name="username" value={username} /> + <input type="hidden" name="type" value={type} /> - {#if request} - <input type="hidden" name="request_id" value={request.id} /> - {/if} + {#if request} + <input type="hidden" name="request_id" value={request.id} /> + {/if} - <input type="hidden" name="prompt" value={prompt} /> + <input type="hidden" name="prompt" value={prompt} /> - <div class="space-y-3 w-[300px] mx-auto"> - <Numpad bind:amount bind:currency bind:rate {submit} /> - <button - bind:this={submit} - type="submit" - class="bg-black text-white rounded-xl w-full h-[48px] flex justify-center items-center font-semibold + <div class="space-y-3 w-[300px] mx-auto"> + <Numpad bind:amount bind:currency bind:rate {submit} /> + <button + bind:this={submit} + type="submit" + class="bg-black text-white rounded-xl w-full h-[48px] flex justify-center items-center font-semibold opacity-100 hover:opacity-80" - > - {$t('payments.next')} - </button> - </div> + > + {$t("payments.next")} + </button> + </div> </form> diff --git a/src/routes/(app)/[username]/request/[id]/+page.server.js b/src/routes/(app)/[username]/request/[id]/+page.server.js index 89792ab8..09cce465 100644 --- a/src/routes/(app)/[username]/request/[id]/+page.server.js +++ b/src/routes/(app)/[username]/request/[id]/+page.server.js @@ -1,25 +1,26 @@ -import { auth, get } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import { auth, get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export let load = async ({ cookies, depends, params, parent }) => { - depends('app:invoice'); + depends("app:invoice"); - let { user } = await parent(); - if (!user) throw redirect(307, '/login'); + let { user } = await parent(); + if (!user) throw redirect(307, "/login"); - let { request } = await get(`/request/${params.id}`, auth(cookies)); - let { invoice } = request; + let { request } = await get(`/request/${params.id}`, auth(cookies)); + let { invoice } = request; - if (invoice) { - let { amount, received, id } = invoice; - if ( - request.recipient.username === user.username && - ((received && !amount) || received >= amount) - ) - throw redirect(307, `/${user.username}/invoice/${id}/paid`); + if (invoice) { + let { amount, received, id } = invoice; + if ( + request.recipient.username === user.username && + ((received && !amount) || received >= amount) + ) + throw redirect(307, `/${user.username}/invoice/${id}/paid`); - if (request.requester.username === user.username) throw redirect(307, `/send/${id}`); - } + if (request.requester.username === user.username) + throw redirect(307, `/send/${id}`); + } - return { request }; + return { request }; }; diff --git a/src/routes/(app)/[username]/request/[id]/+page.svelte b/src/routes/(app)/[username]/request/[id]/+page.svelte index bdd14f4a..f9672c6f 100644 --- a/src/routes/(app)/[username]/request/[id]/+page.svelte +++ b/src/routes/(app)/[username]/request/[id]/+page.svelte @@ -1,52 +1,57 @@ <script> - import { scale } from 'svelte/transition'; - import { t } from '$lib/translations'; - import { Avatar, Icon } from '$comp'; - import { f, sat, sats } from '$lib/utils'; - import { request as req, requestRedirect } from '$lib/store'; - import { onMount } from 'svelte'; + import { scale } from "svelte/transition"; + import { t } from "$lib/translations"; + import { Avatar, Icon } from "$comp"; + import { f, sat, sats } from "$lib/utils"; + import { request as req, requestRedirect } from "$lib/store"; + import { onMount } from "svelte"; - export let data; - let { request, user } = data; + export let data; + let { request, user } = data; - let amount, currency, rate, fiat; - if (request.invoice) { - ({ amount, rate, currency } = request.invoice); - fiat = (amount * rate) / sats; - } + let amount, currency, rate, fiat; + if (request.invoice) { + ({ amount, rate, currency } = request.invoice); + fiat = (amount * rate) / sats; + } - onMount(() => req.set()); + onMount(() => req.set()); </script> <div class="container px-4 max-w-xl mx-auto space-y-8"> - <div class="text-center mb-8 space-y-5"> - {#if request.recipient_id === user.id} - <p class="text-4xl break-words"> - <span class="text-xl md:text-2xl">{$t('payments.sentInvoiceTo')}</span> - </p> - <div class="flex p-1 gap-2 justify-center"> - <Avatar user={request.requester} size={20} /> - <p class="text-4xl break-words my-auto">{request.requester.username}</p> - </div> - <div> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(fiat, currency)} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1">{sat(amount)}</h3> - </div> - {:else} - <div class="flex w-full max-w-[200px] mx-auto py-20" in:scale={{ start: 0.5 }}> - <Icon icon="check" style="mx-auto" /> - </div> - <p class="text-3xl"> - {$t('payments.sentRequestTo')} - </p> - {/if} - </div> + <div class="text-center mb-8 space-y-5"> + {#if request.recipient_id === user.id} + <p class="text-4xl break-words"> + <span class="text-xl md:text-2xl">{$t("payments.sentInvoiceTo")}</span> + </p> + <div class="flex p-1 gap-2 justify-center"> + <Avatar user={request.requester} size={20} /> + <p class="text-4xl break-words my-auto">{request.requester.username}</p> + </div> + <div> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(fiat, currency)} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1">{sat(amount)}</h3> + </div> + {:else} + <div + class="flex w-full max-w-[200px] mx-auto py-20" + in:scale={{ start: 0.5 }} + > + <Icon icon="check" style="mx-auto" /> + </div> + <p class="text-3xl"> + {$t("payments.sentRequestTo")} + </p> + {/if} + </div> </div> <a href={$requestRedirect || `/${user.username}`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> </a> -<div class="fixed bottom-10 left-0 text-center w-full">{$t('payments.tapAnywhere')}</div> +<div class="fixed bottom-10 left-0 text-center w-full"> + {$t("payments.tapAnywhere")} +</div> diff --git a/src/routes/(app)/[username]/reset/+server.js b/src/routes/(app)/[username]/reset/+server.js index 52fff367..f7dba02c 100644 --- a/src/routes/(app)/[username]/reset/+server.js +++ b/src/routes/(app)/[username]/reset/+server.js @@ -1,10 +1,10 @@ -import { json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { json } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request, params }) { - let { username } = params; - let r = await request.json(); - let { password } = r; - let result = await post('/reset', { password, username }, auth(cookies)); - return json(result); + let { username } = params; + let r = await request.json(); + let { password } = r; + let result = await post("/reset", { password, username }, auth(cookies)); + return json(result); } diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.server.js b/src/routes/(app)/[username]/settings/[...tab]/+page.server.js index b0c849d0..156b6062 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.server.js +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.server.js @@ -1,23 +1,24 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { fd, auth, get, post } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { fd, auth, get, post } from "$lib/utils"; export function load({ params, url }) { - if (url.pathname.endsWith('settings')) throw redirect(307, url.pathname + '/account'); - return params; + if (url.pathname.endsWith("settings")) + throw redirect(307, url.pathname + "/account"); + return params; } export const actions = { - default: async ({ cookies, request }) => { - let form = await fd(request); - if (form.prompt) form.prompt = form.prompt === 'on'; - let user = { ...(await get('/me', auth(cookies))), ...form }; + default: async ({ cookies, request }) => { + let form = await fd(request); + if (form.prompt) form.prompt = form.prompt === "on"; + let user = { ...(await get("/me", auth(cookies))), ...form }; - try { - ({ user } = await post(`/user`, user, auth(cookies))); - } catch (e) { - return fail(400, { message: e.message }); - } + try { + ({ user } = await post(`/user`, user, auth(cookies))); + } catch (e) { + return fail(400, { message: e.message }); + } - return { user, success: true }; - } + return { user, success: true }; + }, }; diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte index 933fa1f6..92772f43 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte @@ -1,206 +1,218 @@ <script> - import { browser } from '$app/environment'; - import { onMount, tick } from 'svelte'; - import { fly } from 'svelte/transition'; - import { applyAction, deserialize } from '$app/forms'; + import { browser } from "$app/environment"; + import { onMount, tick } from "svelte"; + import { fly } from "svelte/transition"; + import { applyAction, deserialize } from "$app/forms"; - import { Icon, Spinner, Pin } from '$comp'; - import { t } from '$lib/translations'; - import { fail, post, warning, success } from '$lib/utils'; - import { avatar, banner, password, pin } from '$lib/store'; - import { upload } from '$lib/upload'; - import { page } from '$app/stores'; - import { sign, send, reEncryptEntropy } from '$lib/nostr'; - import { invalidateAll } from '$app/navigation'; + import { Icon, Spinner, Pin } from "$comp"; + import { t } from "$lib/translations"; + import { fail, post, warning, success } from "$lib/utils"; + import { avatar, banner, password, pin } from "$lib/store"; + import { upload } from "$lib/upload"; + import { page } from "$app/stores"; + import { sign, send, reEncryptEntropy } from "$lib/nostr"; + import { invalidateAll } from "$app/navigation"; - import Account from './_account.svelte'; - import Pos from './_pos.svelte'; - import Security from './_security.svelte'; - import Shopify from './_shopify.svelte'; + import Account from "./_account.svelte"; + import Pos from "./_pos.svelte"; + import Security from "./_security.svelte"; + import Shopify from "./_shopify.svelte"; - import { PUBLIC_COINOS_URL } from '$env/static/public'; + import { PUBLIC_COINOS_URL } from "$env/static/public"; - export let data; - export let form; + export let data; + export let form; - let submit; - let formElement; + let submit; + let formElement; - let { user, token, rates, tab } = data; - let prev; + let { user, token, rates, tab } = data; + let prev; - $: update(data); - let update = () => { - ({ user, token, rates, tab } = data); - prev = { ...user }; - }; + $: update(data); + let update = () => { + ({ user, token, rates, tab } = data); + prev = { ...user }; + }; - $: form?.user && ({ user } = form); + $: form?.user && ({ user } = form); - $: form?.success && success('Settings saved!'); + $: form?.success && success("Settings saved!"); - $: form?.message && fail(form.message); + $: form?.message && fail(form.message); - $: if (form?.message?.startsWith('Pin')) { - fail('Wrong pin, try again'); - $pin = ''; - } + $: if (form?.message?.startsWith("Pin")) { + fail("Wrong pin, try again"); + $pin = ""; + } - let tabs = [ - { name: 'account', key: 'ACCOUNT', comp: Account }, - { name: 'pos', key: 'POINT_OF_SALE', comp: Pos }, - { name: 'security', key: 'SECURITY', comp: Security }, - { name: 'shopify', key: 'SHOPIFY', comp: Shopify } - ]; + let tabs = [ + { name: "account", key: "ACCOUNT", comp: Account }, + { name: "pos", key: "POINT_OF_SALE", comp: Pos }, + { name: "security", key: "SECURITY", comp: Security }, + { name: "shopify", key: "SHOPIFY", comp: Shopify }, + ]; - $: ({ comp } = tabs.find((t) => t.name === tab)); + $: ({ comp } = tabs.find((t) => t.name === tab)); - let { address, id, username } = user; - let loading; + let { address, id, username } = user; + let loading; - async function handleSubmit() { - try { - loading = true; - let data = new FormData(formElement); + async function handleSubmit() { + try { + loading = true; + let data = new FormData(formElement); - if (data.get('password')) { - try { - data.set('cipher', await reEncryptEntropy(user, data.get('password'))); - } catch (e) { - console.log('Failed to encrypt keys with new password'); - throw e; - } - } + if (data.get("password")) { + try { + data.set( + "cipher", + await reEncryptEntropy(user, data.get("password")) + ); + } catch (e) { + console.log("Failed to encrypt keys with new password"); + throw e; + } + } - if ($avatar) { - try { - let { hash } = JSON.parse( - await upload($avatar.file, $avatar.type, $avatar.progress, token) - ); + if ($avatar) { + try { + let { hash } = JSON.parse( + await upload($avatar.file, $avatar.type, $avatar.progress, token) + ); - data.set('profile', hash); + data.set("profile", hash); - await fetch(`/api/public/${hash}.webp`, { cache: 'reload', mode: 'no-cors' }); - } catch (e) { - console.log('problem uploading avatar', e); - } - } + await fetch(`/api/public/${hash}.webp`, { + cache: "reload", + mode: "no-cors", + }); + } catch (e) { + console.log("problem uploading avatar", e); + } + } - if ($banner) { - try { - let { hash } = JSON.parse( - await upload($banner.file, $banner.type, $banner.progress, token) - ); + if ($banner) { + try { + let { hash } = JSON.parse( + await upload($banner.file, $banner.type, $banner.progress, token) + ); - data.set('banner', hash); + data.set("banner", hash); - await fetch(`/api/public/${hash}.webp`, { cache: 'reload', mode: 'no-cors' }); - } catch (e) { - console.log('problem uploading banner', e); - } - } + await fetch(`/api/public/${hash}.webp`, { + cache: "reload", + mode: "no-cors", + }); + } catch (e) { + console.log("problem uploading banner", e); + } + } - if (['username', 'address', 'profile'].some((a) => user[a] !== prev[a])) { - let event = { - pubkey: user.pubkey, - created_at: Math.floor(Date.now() / 1000), - kind: 0, - content: JSON.stringify({ - name: user.username, - about: user.address, - picture: `${PUBLIC_COINOS_URL}/public/${user.profile}.webp` - }), - tags: [] - }; + if (["username", "address", "profile"].some((a) => user[a] !== prev[a])) { + let event = { + pubkey: user.pubkey, + created_at: Math.floor(Date.now() / 1000), + kind: 0, + content: JSON.stringify({ + name: user.username, + about: user.address, + picture: `${PUBLIC_COINOS_URL}/public/${user.profile}.webp`, + }), + tags: [], + }; - try { - await sign({ event, user }); - await send(event); - } catch (e) { - warning('nostr profile could not be updated'); - } - } + try { + await sign({ event, user }); + await send(event); + } catch (e) { + warning("nostr profile could not be updated"); + } + } - if (user.email !== prev.email) { - await post('/api/verify', { test: true }); - } + if (user.email !== prev.email) { + await post("/api/verify", { test: true }); + } - const response = await fetch(formElement.action, { - method: 'POST', - body: data - }); + const response = await fetch(formElement.action, { + method: "POST", + body: data, + }); - const result = deserialize(await response.text()); + const result = deserialize(await response.text()); - if (result.type === 'success') { - await invalidateAll(); - if (data.get('password')) $password = data.get('password'); - } + if (result.type === "success") { + await invalidateAll(); + if (data.get("password")) $password = data.get("password"); + } - applyAction(result); - } catch (e) { - console.log(e); - fail('Something went wrong'); - } + applyAction(result); + } catch (e) { + console.log(e); + fail("Something went wrong"); + } - loading = false; - } + loading = false; + } </script> {#if user.haspin && $pin?.length !== 6} - <Pin /> + <Pin /> {/if} <form - method="POST" - class="mb-[154px]" - on:submit|preventDefault={handleSubmit} - bind:this={formElement} + method="POST" + class="mb-[154px]" + on:submit|preventDefault={handleSubmit} + bind:this={formElement} > - <input type="hidden" name="pin" value={$pin} /> + <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"> - <div class="header"> - <h1 class="text-center text-3xl md:text-4xl font-semibold mb-10"> - {$t('user.settings.header')} - </h1> + <div class="mt-24 mb-20 px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> + <div class="header"> + <h1 class="text-center text-3xl md:text-4xl font-semibold mb-10"> + {$t("user.settings.header")} + </h1> - <div class="font-bold flex justify-around items-center border-b pb-3 text-secondary"> - {#each tabs.filter((t) => t.name !== 'shopify') as { name, key }} - <a href={`/${user.username}/settings/${name}`}> - <button - class="hover:opacity-80" - class:text-black={tab === name || (tab === 'shopify' && name === 'pos')} - >{$t(`user.settings.${key}`)}</button - > - </a> - {/each} - </div> - </div> + <div + class="font-bold flex justify-around items-center border-b pb-3 text-secondary" + > + {#each tabs.filter((t) => t.name !== "shopify") as { name, key }} + <a href={`/${user.username}/settings/${name}`}> + <button + class="hover:opacity-80" + class:text-black={tab === name || + (tab === "shopify" && name === "pos")} + >{$t(`user.settings.${key}`)}</button + > + </a> + {/each} + </div> + </div> - <svelte:component this={comp} {user} {rates} {submit} /> - </div> + <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="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80" - class:bg-black={loading} - > - {#if loading} - <Spinner /> - {:else} - <div class="my-auto">{$t('user.settings.saveSettings')}</div> - {/if} - </button> - </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="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80" + class:bg-black={loading} + > + {#if loading} + <Spinner /> + {:else} + <div class="my-auto">{$t("user.settings.saveSettings")}</div> + {/if} + </button> + </div> </form> <style> - .header { - view-transition-name: tabs; - } + .header { + view-transition-name: tabs; + } </style> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte index 2a06b31d..64ab669f 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte @@ -1,183 +1,193 @@ <script> - import { tick } from 'svelte'; - import { colorTheme, avatar, banner } from '$lib/store'; - import { Icon } from '$comp'; - import { t } from '$lib/translations'; - import { page } from '$app/stores'; + import { tick } from "svelte"; + import { colorTheme, avatar, banner } from "$lib/store"; + import { Icon } from "$comp"; + import { t } from "$lib/translations"; + import { page } from "$app/stores"; - export let user, rates, submit; + export let user, rates, submit; - let { id } = user; + let { id } = user; - let selectedTheme = 1; - let avatarFile, avatarInput, bannerFile, bannerInput; + let selectedTheme = 1; + let avatarFile, avatarInput, bannerFile, bannerInput; - let 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]' } - ]; + let 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]", + }, + ]; - let handleThemeClick = (colors) => { - selectedTheme = colors.theme; - $colorTheme = colors.color1 + ' ' + colors.color2; - }; + let handleThemeClick = (colors) => { + selectedTheme = colors.theme; + $colorTheme = colors.color1 + " " + colors.color2; + }; - let selectAvatar = () => avatarInput.click(); - let selectBanner = () => bannerInput.click(); + let selectAvatar = () => avatarInput.click(); + let selectBanner = () => bannerInput.click(); - let percent; - let progress = async (event) => { - percent = Math.round((event.loaded / event.total) * 100); - }; + let percent; + let progress = async (event) => { + percent = Math.round((event.loaded / event.total) * 100); + }; - let tooLarge = {}; + let tooLarge = {}; - let handleFile = async ({ target }, type) => { - tooLarge[type] = false; - let file = target.files[0]; - if (!file) return; + let handleFile = async ({ target }, type) => { + tooLarge[type] = false; + let file = target.files[0]; + if (!file) return; - if (file.size > 10000000) return (tooLarge[type] = true); + if (file.size > 10000000) return (tooLarge[type] = true); - if (type === 'profile') { - $avatar = { id, file, type, progress }; - } else if (type === 'banner') { - $banner = { id, file, type, progress }; - } + if (type === "profile") { + $avatar = { id, file, type, progress }; + } else if (type === "banner") { + $banner = { id, file, type, progress }; + } - var reader = new FileReader(); - reader.onload = async (e) => { - if (type === 'profile') { - $avatar.src = e.target.result; - } else if (type === 'banner') { - $banner.src = e.target.result; - } - }; + var reader = new FileReader(); + reader.onload = async (e) => { + if (type === "profile") { + $avatar.src = e.target.result; + } else if (type === "banner") { + $banner.src = e.target.result; + } + }; - reader.readAsDataURL(file); - }; + reader.readAsDataURL(file); + }; - if (!user.display) user.display = user.username; + if (!user.display) user.display = user.username; - $: url = `${$page.url.host}/${user.username}`; - $: full = `${$page.url.protocol}//${url}`; - $: addr = `${user.username}@${$page.url.host}`; + $: url = `${$page.url.host}/${user.username}`; + $: full = `${$page.url.protocol}//${url}`; + $: addr = `${user.username}@${$page.url.host}`; </script> <div> - <label for="username" class="font-bold mb-1 block">{$t('user.settings.username')}</label> - <div class="flex mb-2"> - <input - type="text" - name="username" - bind:value={user.username} - class="w-auto min-w-0 pr-1 grow" - /> - </div> + <label for="username" class="font-bold mb-1 block" + >{$t("user.settings.username")}</label + > + <div class="flex mb-2"> + <input + type="text" + name="username" + bind:value={user.username} + class="w-auto min-w-0 pr-1 grow" + /> + </div> </div> <div> - <label for="display" class="font-bold mb-1 block">{$t('user.settings.displayName')}</label> - <input type="text" name="display" bind:value={user.display} /> + <label for="display" class="font-bold mb-1 block" + >{$t("user.settings.displayName")}</label + > + <input type="text" name="display" bind:value={user.display} /> </div> <div> - <span class="font-bold">{$t('user.settings.profileImage')}</span> + <span class="font-bold">{$t("user.settings.profileImage")}</span> - <div class="flex"> - {#if $avatar || user.profile} - <div - class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer" - on:click={selectAvatar} - on:keydown={selectAvatar} - > - <img - src={$avatar?.src || `/api/public/${user.profile}.webp`} - class="absolute w-full h-full object-cover object-center visible overflow-hidden" - alt={user.username} - /> - </div> - {:else} - <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={selectAvatar} - on:keydown={selectAvatar} - > - <Icon icon="logo-symbol-white" style="mx-auto" /> - </div> - {/if} - <div class="ml-2 p-2"> - <!-- found missing translation --> - <button - type="button" - class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" - on:click={selectAvatar} - on:keydown={selectAvatar}>{$t('user.settings.select')}</button - > - <input - type="file" - class="hidden" - bind:this={avatarInput} - on:change={(e) => handleFile(e, 'profile')} - /> - </div> - </div> + <div class="flex"> + {#if $avatar || user.profile} + <div + class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer" + on:click={selectAvatar} + on:keydown={selectAvatar} + > + <img + src={$avatar?.src || `/api/public/${user.profile}.webp`} + class="absolute w-full h-full object-cover object-center visible overflow-hidden" + alt={user.username} + /> + </div> + {:else} + <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={selectAvatar} + on:keydown={selectAvatar} + > + <Icon icon="logo-symbol-white" style="mx-auto" /> + </div> + {/if} + <div class="ml-2 p-2"> + <!-- found missing translation --> + <button + type="button" + class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" + on:click={selectAvatar} + on:keydown={selectAvatar}>{$t("user.settings.select")}</button + > + <input + type="file" + class="hidden" + bind:this={avatarInput} + on:change={(e) => handleFile(e, "profile")} + /> + </div> + </div> - {#if tooLarge['avatar']} - <div class="text-red-600">Max file size 10MB</div> - {/if} + {#if tooLarge["avatar"]} + <div class="text-red-600">Max file size 10MB</div> + {/if} </div> <div> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t('user.settings.bannerImage')}</span> - </div> + <div class="flex justify-between items-center"> + <span class="font-bold">{$t("user.settings.bannerImage")}</span> + </div> - {#if $banner || user.banner} - <img - src={$banner ? $banner.src : `/api/public/${user.banner}.webp`} - class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80" - on:click={selectBanner} - on:keydown={selectBanner} - alt="Banner" - /> - {:else} - <div - class="bg-gradient-to-r {$colorTheme} w-full h-48 mb-4 cursor-pointer hover:opacity-80" - on:click={selectBanner} - on:keydown={selectBanner} - alt="Banner" - /> - {/if} + {#if $banner || user.banner} + <img + src={$banner ? $banner.src : `/api/public/${user.banner}.webp`} + class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80" + on:click={selectBanner} + on:keydown={selectBanner} + alt="Banner" + /> + {:else} + <div + class="bg-gradient-to-r {$colorTheme} w-full h-48 mb-4 cursor-pointer hover:opacity-80" + on:click={selectBanner} + on:keydown={selectBanner} + alt="Banner" + /> + {/if} - <button - type="button" - class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" - on:click={selectBanner} - on:keydown={selectBanner}>{$t('user.settings.select')}</button - > - <input - type="file" - class="hidden" - bind:this={bannerInput} - on:change={(e) => handleFile(e, 'banner')} - /> + <button + type="button" + class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" + on:click={selectBanner} + on:keydown={selectBanner}>{$t("user.settings.select")}</button + > + <input + type="file" + class="hidden" + bind:this={bannerInput} + on:change={(e) => handleFile(e, "banner")} + /> - {#if tooLarge['banner']} - <div class="text-red-600">Max file size 10MB</div> - {/if} + {#if tooLarge["banner"]} + <div class="text-red-600">Max file size 10MB</div> + {/if} </div> <div> - <label for="address" class="font-bold mb-1 block">{$t('user.settings.about')}</label> - <textarea - type="text" - name="address" - bind:value={user.address} - placeholder={$t('user.settings.aboutPlaceholder')} - /> + <label for="address" class="font-bold mb-1 block" + >{$t("user.settings.about")}</label + > + <textarea + type="text" + name="address" + bind:value={user.address} + placeholder={$t("user.settings.aboutPlaceholder")} + /> </div> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte index 9100401a..94a35493 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte @@ -1,61 +1,71 @@ <script> - import { Icon, LocaleSelector, Toggle } from '$comp'; - import { t } from '$lib/translations'; - import { success, fail } from '$lib/utils'; - import { page } from '$app/stores'; + import { Icon, LocaleSelector, Toggle } from "$comp"; + import { t } from "$lib/translations"; + import { success, fail } from "$lib/utils"; + import { page } from "$app/stores"; - export let user, rates, submit; + export let user, rates, submit; - let fiats = Object.keys(rates); + let fiats = Object.keys(rates); </script> <div> - <label for="language" class="font-bold block mb-1">{$t('user.settings.locale')}</label> - <LocaleSelector style="select-styles block py-3 w-full" /> + <label for="language" class="font-bold block mb-1" + >{$t("user.settings.locale")}</label + > + <LocaleSelector style="select-styles block py-3 w-full" /> </div> <div> - <label for="currency" class="font-bold block mb-1">{$t('user.settings.localCurrency')}</label> - <select name="currency" class="select-styles block py-3 w-full" value={user.currency}> - {#each fiats as fiat} - <option value={fiat}>{fiat}</option> - {/each} - </select> + <label for="currency" class="font-bold block mb-1" + >{$t("user.settings.localCurrency")}</label + > + <select + name="currency" + class="select-styles block py-3 w-full" + value={user.currency} + > + {#each fiats as fiat} + <option value={fiat}>{fiat}</option> + {/each} + </select> </div> <div class="space-y-1"> - <label for="email" class="font-bold block">{$t('user.settings.email')}</label> + <label for="email" class="font-bold block">{$t("user.settings.email")}</label> <p class="text-secondary"> - {$t('user.settings.emailDescription')} + {$t("user.settings.emailDescription")} </p> - <input type="text" name="email" bind:value={user.email} /> + <input type="text" name="email" bind:value={user.email} /> </div> <div> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t('user.settings.tipPrompt')}</span> - <Toggle id="prompt" bind:value={user.prompt} /> - </div> - <p class="text-secondary mt-1 w-9/12"> - {$t('user.settings.tipPromptDescription')} - </p> + <div class="flex justify-between items-center"> + <span class="font-bold">{$t("user.settings.tipPrompt")}</span> + <Toggle id="prompt" bind:value={user.prompt} /> + </div> + <p class="text-secondary mt-1 w-9/12"> + {$t("user.settings.tipPromptDescription")} + </p> </div> <div> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t('user.settings.publishNip5')}</span> - <Toggle id="nip5" bind:value={user.nip5} /> - </div> - <p class="text-secondary mt-1 w-9/12"> - {$t('user.settings.nip5a')} <span class="font-bold">{user.username}@{$page.url.host}</span> {$t('user.settings.nip5b')} - </p> + <div class="flex justify-between items-center"> + <span class="font-bold">{$t("user.settings.publishNip5")}</span> + <Toggle id="nip5" bind:value={user.nip5} /> + </div> + <p class="text-secondary mt-1 w-9/12"> + {$t("user.settings.nip5a")} + <span class="font-bold">{user.username}@{$page.url.host}</span> + {$t("user.settings.nip5b")} + </p> </div> <a href="./shopify"> - <button - class="flex rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full gap-2 my-5 justify-center" - > - <img src="/icons/shopify.svg" class="w-8" /> - <div class="my-auto">Shopify Integration</div> - </button> + <button + class="flex rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full gap-2 my-5 justify-center" + > + <img src="/icons/shopify.svg" class="w-8" /> + <div class="my-auto">Shopify Integration</div> + </button> </a> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte index 3cd0480e..f8c14e7b 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte @@ -1,290 +1,317 @@ <script> - import { getMnemonic, getNsec } from '$lib/nostr'; - import { tick } from 'svelte'; - import { t } from '$lib/translations'; - import { Pin, Icon, Qr } from '$comp'; - import { copy, post, success, fail } from '$lib/utils'; - import { pin as current } from '$lib/store'; - import { invalidate } from '$app/navigation'; - import { page } from '$app/stores'; + import { getMnemonic, getNsec } from "$lib/nostr"; + import { tick } from "svelte"; + import { t } from "$lib/translations"; + import { Pin, Icon, Qr } from "$comp"; + import { copy, post, success, fail } from "$lib/utils"; + import { pin as current } from "$lib/store"; + import { invalidate } from "$app/navigation"; + import { page } from "$app/stores"; - export let user, rates, submit; + export let user, rates, submit; - let confirming2fa, - disabling2fa, - locked, - mnemonic, - nsec, - old, - otp, - password, - pin, - revealPassword, - revealSeed, - revealNsec, - token = '', - setting2fa, - settingPin, - verify; + let confirming2fa, + disabling2fa, + locked, + mnemonic, + nsec, + old, + otp, + password, + pin, + revealPassword, + revealSeed, + revealNsec, + token = "", + setting2fa, + settingPin, + verify; - let toggleNsec = async () => { - try { - nsec = await getNsec(user); - revealNsec = !revealNsec; - } catch (e) { - console.log(e); - } - }; + let toggleNsec = async () => { + try { + nsec = await getNsec(user); + revealNsec = !revealNsec; + } catch (e) { + console.log(e); + } + }; - let togglePassword = () => (revealPassword = !revealPassword); - let toggleSeed = async () => { - try { - mnemonic = await getMnemonic(user); - revealSeed = !revealSeed; - } catch (e) { - console.log(e); - } - }; + let togglePassword = () => (revealPassword = !revealPassword); + let toggleSeed = async () => { + try { + mnemonic = await getMnemonic(user); + revealSeed = !revealSeed; + } catch (e) { + console.log(e); + } + }; - $: verifying = pin?.length > 5; - $: verify && checkPin(pin); - let checkPin = async () => { - old = $current; + $: verifying = pin?.length > 5; + $: verify && checkPin(pin); + let checkPin = async () => { + old = $current; - try { - if (pin.length > 5 && pin === verify) { - user.haspin = true; - $current = pin; - pin = ''; - verify = ''; - submit.click(); - settingPin = false; - verifying = false; - } else { - fail('Pin mismatch, try again'); - pin = ''; - verify = ''; - verifying = false; - settingPin = true; - } - } catch (e) { - console.log(e); - fail('Problem setting PIN'); - } - }; + try { + if (pin.length > 5 && pin === verify) { + user.haspin = true; + $current = pin; + pin = ""; + verify = ""; + submit.click(); + settingPin = false; + verifying = false; + } else { + fail("Pin mismatch, try again"); + pin = ""; + verify = ""; + verifying = false; + settingPin = true; + } + } catch (e) { + console.log(e); + fail("Problem setting PIN"); + } + }; - let reset = () => { - token = ''; - return true; - }; + let reset = () => { + token = ""; + return true; + }; - let disablingPin = false; + let disablingPin = false; - let togglePin = async () => { - if (user.haspin) { - try { - disablingPin = true; - await tick(); - submit.click(); - } catch (e) { - console.log(e); - fail('Failed to disable pin'); - } - } else { - settingPin = true; + let togglePin = async () => { + if (user.haspin) { + try { + disablingPin = true; + await tick(); + submit.click(); + } catch (e) { + console.log(e); + fail("Failed to disable pin"); + } + } else { + settingPin = true; disablingPin = false; - } - }; + } + }; - let startEnabling2fa = async () => { - reset(); - try { - if (!otp) otp = await post('/otpsecret', { pin: $current }); - } catch (e) { - console.log(e); - } + let startEnabling2fa = async () => { + reset(); + try { + if (!otp) otp = await post("/otpsecret", { pin: $current }); + } catch (e) { + console.log(e); + } - setting2fa = true; - }; + setting2fa = true; + }; - let startDisabling2fa = () => (disabling2fa = true); - let startConfirming2fa = () => (confirming2fa = true); - let cancel = () => { - pin = null; - token = null; - verifying = false; - settingPin = false; - setting2fa = false; - confirming2fa = false; - disabling2fa = false; - }; + let startDisabling2fa = () => (disabling2fa = true); + let startConfirming2fa = () => (confirming2fa = true); + let cancel = () => { + pin = null; + token = null; + verifying = false; + settingPin = false; + setting2fa = false; + confirming2fa = false; + disabling2fa = false; + }; - $: enable2fa(token); - let enable2fa = async (twoFa) => { - try { - if (setting2fa && token.length === 6) { - await post('/enable2fa', { token }); - success('2FA enabled'); - user.twofa = 1; - cancel(); - } - } catch (e) { - fail('Failed to enable 2FA, try again'); - } - }; + $: enable2fa(token); + let enable2fa = async (twoFa) => { + try { + if (setting2fa && token.length === 6) { + await post("/enable2fa", { token }); + success("2FA enabled"); + user.twofa = 1; + cancel(); + } + } catch (e) { + fail("Failed to enable 2FA, try again"); + } + }; - $: disable2fa(token); - let disable2fa = async () => { - try { - if (disabling2fa && token.length === 6) { - await post('/disable2fa', { token }); - success('2FA disabled'); - delete user.twofa; - disabling2fa = false; - } - } catch (e) { - console.log(e); - fail('Failed to disable 2FA, try again'); - } - }; + $: disable2fa(token); + let disable2fa = async () => { + try { + if (disabling2fa && token.length === 6) { + await post("/disable2fa", { token }); + success("2FA disabled"); + delete user.twofa; + disabling2fa = false; + } + } catch (e) { + console.log(e); + fail("Failed to disable 2FA, try again"); + } + }; </script> -<input type="hidden" name="newpin" value={disablingPin ? 'delete' : pin} /> +<input type="hidden" name="newpin" value={disablingPin ? "delete" : pin} /> <input type="hidden" name="confirm" bind:value={password} /> <div class="relative mb-5"> - <label for="password" class="block font-bold block mb-1">{$t('user.settings.newPassword')}</label> - {#if revealPassword} - <input name="password" type="text" bind:value={password} /> - {:else} - <input name="password" type="password" bind:value={password} /> - {/if} - <button - type="button" - on:click={togglePassword} - on:keydown={togglePassword} - class="absolute right-5 top-11" - > - <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> - </button> + <label for="password" class="block font-bold block mb-1" + >{$t("user.settings.newPassword")}</label + > + {#if revealPassword} + <input name="password" type="text" bind:value={password} /> + {:else} + <input name="password" type="password" bind:value={password} /> + {/if} + <button + type="button" + on:click={togglePassword} + on:keydown={togglePassword} + class="absolute right-5 top-11" + > + <Icon icon={revealPassword ? "eye" : "eye-off"} /> + </button> </div> <div> - <span class="font-bold mb-1" - >{verifying ? $t('user.settings.verifyPIN') : $t('user.settings.securityPIN')}</span - > - <p class="text-secondary mb-1"> - {$t('user.settings.securityPINDescription')} - </p> - {#if verifying} - <Pin bind:value={verify} {cancel} notify={false} /> - {:else if settingPin} - <Pin bind:value={pin} title={$t('user.settings.setPIN')} {cancel} notify={false} /> - {:else} - <button type="button" class="primary" on:click={togglePin} - ><Icon icon="lock" style="mr-1" /> - {user.haspin ? $t('user.settings.disablePIN') : $t('user.settings.enablePIN')}</button - > - {/if} + <span class="font-bold mb-1" + >{verifying + ? $t("user.settings.verifyPIN") + : $t("user.settings.securityPIN")}</span + > + <p class="text-secondary mb-1"> + {$t("user.settings.securityPINDescription")} + </p> + {#if verifying} + <Pin bind:value={verify} {cancel} notify={false} /> + {:else if settingPin} + <Pin + bind:value={pin} + title={$t("user.settings.setPIN")} + {cancel} + notify={false} + /> + {:else} + <button type="button" class="primary" on:click={togglePin} + ><Icon icon="lock" style="mr-1" /> + {user.haspin + ? $t("user.settings.disablePIN") + : $t("user.settings.enablePIN")}</button + > + {/if} </div> {#if user.haspin} - <div> - <label for="locktime" class="font-bold mb-1">{$t('user.settings.autoLock')}</label> - <p class="text-secondary mb-1"> - {$t('user.settings.autoLockDescription')} - </p> - <select name="locktime" class="select-styles block py-3 w-full" value={parseInt(user.locktime)}> - <option value={5 * 60}>5 {$t('user.settings.minutes')}</option> - <option value={10 * 60}>10 {$t('user.settings.minutes')}</option> - <option value={30 * 60}>30 {$t('user.settings.minutes')}</option> - <option value={60 * 60}>1 {$t('user.settings.hour')}</option> - <option value={8 * 60 * 60}>8 {$t('user.settings.hours')}</option> - </select> - </div> + <div> + <label for="locktime" class="font-bold mb-1" + >{$t("user.settings.autoLock")}</label + > + <p class="text-secondary mb-1"> + {$t("user.settings.autoLockDescription")} + </p> + <select + name="locktime" + class="select-styles block py-3 w-full" + value={parseInt(user.locktime)} + > + <option value={5 * 60}>5 {$t("user.settings.minutes")}</option> + <option value={10 * 60}>10 {$t("user.settings.minutes")}</option> + <option value={30 * 60}>30 {$t("user.settings.minutes")}</option> + <option value={60 * 60}>1 {$t("user.settings.hour")}</option> + <option value={8 * 60 * 60}>8 {$t("user.settings.hours")}</option> + </select> + </div> {/if} <div> - <span class="font-bold mb-1">{$t('user.settings.twofa')}</span> - <p class="text-secondary mb-4"> - {$t('user.settings.twofaDescription')} - </p> + <span class="font-bold mb-1">{$t("user.settings.twofa")}</span> + <p class="text-secondary mb-4"> + {$t("user.settings.twofaDescription")} + </p> - {#if setting2fa} - <a href={otp.uri}> - <Qr src={otp.qr} /> - </a> + {#if setting2fa} + <a href={otp.uri}> + <Qr src={otp.qr} /> + </a> - <div class="text-center my-4"> - {$t('user.settings.accountId')}<br /> - <b>{otp.secret}</b> - </div> + <div class="text-center my-4"> + {$t("user.settings.accountId")}<br /> + <b>{otp.secret}</b> + </div> - <button type="button" class="primary" on:click={startConfirming2fa}> - <Icon icon="mobile" style="mr-1" /> - Confirm - </button> - {:else if user.twofa} - <button type="button" class="primary" on:click={startDisabling2fa}> - <Icon icon="mobile" style="mr-1" /> - {$t('user.settings.twofaDisable')} - </button> - {:else} - <button type="button" class="primary" on:click={startEnabling2fa}> - <Icon icon="mobile" style="mr-1" /> - {$t('user.settings.twofaSetup')} - </button> - {/if} + <button type="button" class="primary" on:click={startConfirming2fa}> + <Icon icon="mobile" style="mr-1" /> + Confirm + </button> + {:else if user.twofa} + <button type="button" class="primary" on:click={startDisabling2fa}> + <Icon icon="mobile" style="mr-1" /> + {$t("user.settings.twofaDisable")} + </button> + {:else} + <button type="button" class="primary" on:click={startEnabling2fa}> + <Icon icon="mobile" style="mr-1" /> + {$t("user.settings.twofaSetup")} + </button> + {/if} - {#if confirming2fa || disabling2fa} - <Pin bind:value={token} title="Enter 2FA Code" {cancel} persist={false} notify={false} /> - {/if} + {#if confirming2fa || disabling2fa} + <Pin + bind:value={token} + title="Enter 2FA Code" + {cancel} + persist={false} + notify={false} + /> + {/if} </div> <div> - <label for="seedphrase" class="font-bold">{$t('user.settings.seedPhrase')}</label> + <label for="seedphrase" class="font-bold" + >{$t("user.settings.seedPhrase")}</label + > - <p class="text-secondary mb-1"> - {$t('user.settings.seedDescription')} - </p> + <p class="text-secondary mb-1"> + {$t("user.settings.seedDescription")} + </p> - <button type="button" class="primary" on:click={toggleSeed}> - <Icon icon="warning" style="mr-1 w-6 my-auto" /> - {revealSeed ? $t('user.settings.hideSeed') : $t('user.settings.revealSeed')} - </button> + <button type="button" class="primary" on:click={toggleSeed}> + <Icon icon="warning" style="mr-1 w-6 my-auto" /> + {revealSeed ? $t("user.settings.hideSeed") : $t("user.settings.revealSeed")} + </button> - {#if revealSeed} - <div class="text-lg"> - {mnemonic} - </div> + {#if revealSeed} + <div class="text-lg"> + {mnemonic} + </div> - <button type="button" class="primary" on:click={() => copy(mnemonic)}> - <Icon icon="copy" style="mr-1 w-6 my-auto" /> - Copy - </button> - {/if} + <button type="button" class="primary" on:click={() => copy(mnemonic)}> + <Icon icon="copy" style="mr-1 w-6 my-auto" /> + Copy + </button> + {/if} </div> <div> - <label for="seedphrase" class="font-bold">{$t('user.settings.nostrKeys')}</label> + <label for="seedphrase" class="font-bold" + >{$t("user.settings.nostrKeys")}</label + > - <p class="text-secondary mb-1"> - {$t('user.settings.nostrDescription')} - </p> + <p class="text-secondary mb-1"> + {$t("user.settings.nostrDescription")} + </p> - <button type="button" class="primary" on:click={toggleNsec}> - <Icon icon="warning" style="mr-1 w-6 my-auto" /> - {revealNsec ? $t('user.settings.hideNsec') : $t('user.settings.revealNsec')} - </button> + <button type="button" class="primary" on:click={toggleNsec}> + <Icon icon="warning" style="mr-1 w-6 my-auto" /> + {revealNsec ? $t("user.settings.hideNsec") : $t("user.settings.revealNsec")} + </button> - {#if revealNsec} - <div class="text-lg break-all"> - {nsec} - </div> + {#if revealNsec} + <div class="text-lg break-all"> + {nsec} + </div> - <button type="button" class="primary" on:click={() => copy(nsec)}> - <Icon icon="copy" style="mr-1 w-6 my-auto" /> - Copy - </button> - {/if} + <button type="button" class="primary" on:click={() => copy(nsec)}> + <Icon icon="copy" style="mr-1 w-6 my-auto" /> + Copy + </button> + {/if} </div> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte b/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte index aeecc4d7..3e2a8dae 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte @@ -1,69 +1,92 @@ <script> - import { t } from '$lib/translations'; - import { Icon } from '$comp'; - import { page } from '$app/stores'; + import { t } from "$lib/translations"; + import { Icon } from "$comp"; + import { page } from "$app/stores"; - export let user; - $: paymentsUrl = - user.shopifyStore && - 'https://admin.shopify.com/store/' + user.shopifyStore + '/settings/payments'; - $: checkoutUrl = - user.shopifyStore && - 'https://admin.shopify.com/store/' + user.shopifyStore + '/settings/checkout'; - $: appsUrl = - user.shopifyStore && - 'https://admin.shopify.com/store/' + user.shopifyStore + '/settings/apps/development'; + export let user; + $: paymentsUrl = + user.shopifyStore && + "https://admin.shopify.com/store/" + + user.shopifyStore + + "/settings/payments"; + $: checkoutUrl = + user.shopifyStore && + "https://admin.shopify.com/store/" + + user.shopifyStore + + "/settings/checkout"; + $: appsUrl = + user.shopifyStore && + "https://admin.shopify.com/store/" + + user.shopifyStore + + "/settings/apps/development"; + + let script = `<script src="${$page.url.protocol}//${$page.url.host}/${user.username}/shopify.js"><\/script>`; </script> <div> - <label for="shopifyStore" class="font-bold mb-1 block">Store ID</label> - <div class="flex mb-2"> - <div class="text-gray-600 p-4 my-auto bg-gray-100 border rounded-l-2xl border-r-0 pr-1"> - admin.shopify.com/store/ - </div> - <input - type="text" - name="shopifyStore" - bind:value={user.shopifyStore} - class="border-l-0 rounded-l-none w-auto min-w-0 pl-1 grow" - /> - </div> - <p class="text-secondary my-2"> - Add Bitcoin under <a class:active={!!paymentsUrl} href={paymentsUrl || undefined} - >Payments > Manual payment methods</a - > with a description like "Pay with Bitcoin". - </p> + <label for="shopifyStore" class="font-bold mb-1 block">Store ID</label> + <div class="flex mb-2"> + <div + class="text-gray-600 p-4 my-auto bg-gray-100 border rounded-l-2xl border-r-0 pr-1" + > + admin.shopify.com/store/ + </div> + <input + type="text" + name="shopifyStore" + bind:value={user.shopifyStore} + class="border-l-0 rounded-l-none w-auto min-w-0 pl-1 grow" + /> + </div> + <p class="text-secondary my-2"> + Add Bitcoin under <a + class:active={!!paymentsUrl} + href={paymentsUrl || undefined}>Payments > Manual payment methods</a + > with a description like "Pay with Bitcoin". + </p> </div> <div> - <label for="display" class="font-bold mb-1 block">Additional scripts</label> - <p class="text-secondary my-2"> - Copy this into <a class:active={!!paymentsUrl} href={checkoutUrl} - >Checkout -> Additional scripts</a - > - </p> - <textarea rows={5} type="text" name="shopifyScripts" readonly><script src="{`${$page.url.protocol}//${$page.url.host}/${user.username}/shopify.js`}"> </script></textarea> + <label for="display" class="font-bold mb-1 block">Additional scripts</label> + <p class="text-secondary my-2"> + Copy this into <a class:active={!!paymentsUrl} href={checkoutUrl} + >Checkout -> Additional scripts</a + > + </p> + <textarea rows={5} type="text" name="shopifyScripts" readonly + >{script}</textarea + > </div> <div> - <label for="shopifyToken" class="font-bold mb-1 block">Admin API token <span class="font-normal">(Optional)</span></label> - <p class="text-secondary my-2">Coinos can automatically mark orders as Paid if you provide an API token. - <input class="my-4" type="text" name="shopifyToken" bind:value={user.shopifyToken} /> - - <p class="text-secondary my-2"> - Create a new app in <a class:active={!!appsUrl} href={appsUrl || undefined} - >Apps > App Development</a - > - </p> + <label for="shopifyToken" class="font-bold mb-1 block" + >Admin API token <span class="font-normal">(Optional)</span></label + > + <p class="text-secondary my-2"> + Coinos can automatically mark orders as Paid if you provide an API token. + <input + class="my-4" + type="text" + name="shopifyToken" + bind:value={user.shopifyToken} + /> + </p> + <p class="text-secondary my-2"> + Create a new app in <a class:active={!!appsUrl} href={appsUrl || undefined} + >Apps > App Development</a + > + </p> - <p class="text-secondary my-2"> - Edit the Admin API scopes to enable <code class="text-black">write_orders</code> permission then install the - app into your store and copy the Admin API access token here. - </p> + <p class="text-secondary my-2"> + Edit the Admin API scopes to enable <code class="text-black" + >write_orders</code + > permission then install the app into your store and copy the Admin API access + token here. + </p> </div> <style> - .active { - @apply text-blue-500; - } + .active { + @apply text-blue-500; + } </style> diff --git a/src/routes/(app)/[username]/shopify.js/+server.js b/src/routes/(app)/[username]/shopify.js/+server.js index 7b5c9d41..c93ac729 100644 --- a/src/routes/(app)/[username]/shopify.js/+server.js +++ b/src/routes/(app)/[username]/shopify.js/+server.js @@ -1,7 +1,7 @@ export async function GET({ cookies, params, url }) { - let { username } = params; + let { username } = params; - let js = ` + let js = ` var qrcode=function(r,_){var t=r,e=QRErrorCorrectLevel[_],n=null,$=0,o=null,a=[],f={},i=function(r,_){n=function(r){for(var _=Array(r),t=0;t<r;t+=1){_[t]=Array(r);for(var e=0;e<r;e+=1)_[t][e]=null}return _}($=4*t+17),u(0,0),u($-7,0),u(0,$-7),l(),g(),v(r,_),t>=7&&h(r),null==o&&(o=x(t,e,a)),s(o,_)},u=function(r,_){for(var t=-1;t<=7;t+=1)if(!(r+t<=-1)&&!($<=r+t))for(var e=-1;e<=7;e+=1)_+e<=-1||$<=_+e||(0<=t&&t<=6&&(0==e||6==e)||0<=e&&e<=6&&(0==t||6==t)||2<=t&&t<=4&&2<=e&&e<=4?n[r+t][_+e]=!0:n[r+t][_+e]=!1)},c=function(){for(var r=0,_=0,t=0;t<8;t+=1){i(!0,t);var e=QRUtil.getLostPoint(f);(0==t||r>e)&&(r=e,_=t)}return _},g=function(){for(var r=8;r<$-8;r+=1)null==n[r][6]&&(n[r][6]=r%2==0);for(var _=8;_<$-8;_+=1)null==n[6][_]&&(n[6][_]=_%2==0)},l=function(){for(var r=QRUtil.getPatternPosition(t),_=0;_<r.length;_+=1)for(var e=0;e<r.length;e+=1){var $=r[_],o=r[e];if(null==n[$][o])for(var a=-2;a<=2;a+=1)for(var f=-2;f<=2;f+=1)-2==a||2==a||-2==f||2==f||0==a&&0==f?n[$+a][o+f]=!0:n[$+a][o+f]=!1}},h=function(r){for(var _=QRUtil.getBCHTypeNumber(t),e=0;e<18;e+=1){var o=!r&&(_>>e&1)==1;n[Math.floor(e/3)][e%3+$-8-3]=o}for(var e=0;e<18;e+=1){var o=!r&&(_>>e&1)==1;n[e%3+$-8-3][Math.floor(e/3)]=o}},v=function(r,_){for(var t=QRUtil.getBCHTypeInfo(e<<3|_),o=0;o<15;o+=1){var a=!r&&(t>>o&1)==1;o<6?n[o][8]=a:o<8?n[o+1][8]=a:n[$-15+o][8]=a}for(var o=0;o<15;o+=1){var a=!r&&(t>>o&1)==1;o<8?n[8][$-o-1]=a:o<9?n[8][15-o-1+1]=a:n[8][15-o-1]=a}n[$-8][8]=!r},s=function(r,_){for(var t=-1,e=$-1,o=7,a=0,f=QRUtil.getMaskFunction(_),i=$-1;i>0;i-=2)for(6==i&&(i-=1);;){for(var u=0;u<2;u+=1)if(null==n[e][i-u]){var c=!1;a<r.length&&(c=(r[a]>>>o&1)==1),f(e,i-u)&&(c=!c),n[e][i-u]=c,-1==(o-=1)&&(a+=1,o=7)}if((e+=t)<0||$<=e){e-=t,t=-t;break}}},d=function(r,_){for(var t=0,e=0,n=0,$=Array(_.length),o=Array(_.length),a=0;a<_.length;a+=1){var f=_[a].dataCount,i=_[a].totalCount-f;e=Math.max(e,f),n=Math.max(n,i),$[a]=Array(f);for(var u=0;u<$[a].length;u+=1)$[a][u]=255&r.getBuffer()[u+t];t+=f;var c=QRUtil.getErrorCorrectPolynomial(i),g=qrPolynomial($[a],c.getLength()-1).mod(c);o[a]=Array(c.getLength()-1);for(var u=0;u<o[a].length;u+=1){var l=u+g.getLength()-o[a].length;o[a][u]=l>=0?g.getAt(l):0}}for(var h=0,u=0;u<_.length;u+=1)h+=_[u].totalCount;for(var v=Array(h),s=0,u=0;u<e;u+=1)for(var a=0;a<_.length;a+=1)u<$[a].length&&(v[s]=$[a][u],s+=1);for(var u=0;u<n;u+=1)for(var a=0;a<_.length;a+=1)u<o[a].length&&(v[s]=o[a][u],s+=1);return v},x=function(r,_,t){for(var e=QRRSBlock.getRSBlocks(r,_),n=qrBitBuffer(),$=0;$<t.length;$+=1){var o=t[$];n.put(o.getMode(),4),n.put(o.getLength(),QRUtil.getLengthInBits(o.getMode(),r)),o.write(n)}for(var a=0,$=0;$<e.length;$+=1)a+=e[$].dataCount;if(n.getLengthInBits()>8*a)throw Error("code length overflow. ("+n.getLengthInBits()+">"+8*a+")");for(n.getLengthInBits()+4<=8*a&&n.put(0,4);n.getLengthInBits()%8!=0;)n.putBit(!1);for(;!(n.getLengthInBits()>=8*a)&&(n.put(236,8),!(n.getLengthInBits()>=8*a));){;n.put(17,8)}return d(n,e)};return f.addData=function(r){var _=qr8BitByte(r);a.push(_),o=null},f.isDark=function(r,_){if(r<0||$<=r||_<0||$<=_)throw Error(r+","+_);return n[r][_]},f.getModuleCount=function(){return $},f.make=function(){i(!1,c())},f.createTableTag=function(r,_){r=r||2;var t="";t+='<table style="',t+=" border-width: 0px; border-style: none;",t+=" border-collapse: collapse;",t+=" padding: 0px; margin: "+(_=void 0===_?4*r:_)+"px;",t+='">',t+="<tbody>";for(var e=0;e<f.getModuleCount();e+=1){t+="<tr>";for(var n=0;n<f.getModuleCount();n+=1)t+='<td style="',t+=" border-width: 0px; border-style: none;",t+=" border-collapse: collapse;",t+=" padding: 0px; margin: 0px;",t+=" width: "+r+"px;",t+=" height: "+r+"px;",t+=" background-color: ",t+=f.isDark(e,n)?"#000000":"#ffffff",t+=";",t+='"/>';t+="</tr>"}return t+="</tbody>",t+="</table>"},f.createImgTag=function(r,_,t){r=r||2;var e=_=void 0===_?4*r:_,n=f.getModuleCount()*r+_;return createImgTag(t,t,function(_,t){if(!(e<=_)||!(_<n)||!(e<=t)||!(t<n))return 1;var $=Math.floor((_-e)/r),o=Math.floor((t-e)/r);return f.isDark(o,$)?0:1})},f};qrcode.stringToBytes=function(r){for(var _=[],t=0;t<r.length;t+=1){var e=r.charCodeAt(t);_.push(255&e)}return _},qrcode.createStringToBytes=function(r,_){var t=function(){for(var t=base64DecodeInputStream(r),e=function(){var r=t.read();if(-1==r)throw Error();return r},n=0,$={};;){var o=t.read();if(-1==o)break;var a=e(),f=e(),i=e(),u=String.fromCharCode(o<<8|a),c=f<<8|i;$[u]=c,n+=1}if(n!=_)throw Error(n+" != "+_);return $}();return function(r){for(var _=[],e=0;e<r.length;e+=1){var n=r.charCodeAt(e);if(n<128)_.push(n);else{var $=t[r.charAt(e)];"number"==typeof $?(255&$)==$?_.push($):(_.push($>>>8),_.push(255&$)):_.push(63)}}return _}};var QRMode={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},QRErrorCorrectLevel={L:1,M:0,Q:3,H:2},QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},QRUtil=function(){var r=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],_={},t=function(r){for(var _=0;0!=r;)_+=1,r>>>=1;return _};return _.getBCHTypeInfo=function(r){for(var _=r<<10;t(_)-t(1335)>=0;)_^=1335<<t(_)-t(1335);return(r<<10|_)^21522},_.getBCHTypeNumber=function(r){for(var _=r<<12;t(_)-t(7973)>=0;)_^=7973<<t(_)-t(7973);return r<<12|_},_.getPatternPosition=function(_){return r[_-1]},_.getMaskFunction=function(r){switch(r){case QRMaskPattern.PATTERN000:return function(r,_){return(r+_)%2==0};case QRMaskPattern.PATTERN001:return function(r,_){return r%2==0};case QRMaskPattern.PATTERN010:return function(r,_){return _%3==0};case QRMaskPattern.PATTERN011:return function(r,_){return(r+_)%3==0};case QRMaskPattern.PATTERN100:return function(r,_){return(Math.floor(r/2)+Math.floor(_/3))%2==0};case QRMaskPattern.PATTERN101:return function(r,_){return r*_%2+r*_%3==0};case QRMaskPattern.PATTERN110:return function(r,_){return(r*_%2+r*_%3)%2==0};case QRMaskPattern.PATTERN111:return function(r,_){return(r*_%3+(r+_)%2)%2==0};default:throw Error("bad maskPattern:"+r)}},_.getErrorCorrectPolynomial=function(r){for(var _=qrPolynomial([1],0),t=0;t<r;t+=1)_=_.multiply(qrPolynomial([1,QRMath.gexp(t)],0));return _},_.getLengthInBits=function(r,_){if(1<=_&&_<10)switch(r){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:case QRMode.MODE_KANJI:return 8;default:throw Error("mode:"+r)}else if(_<27)switch(r){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw Error("mode:"+r)}else if(_<41)switch(r){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw Error("mode:"+r)}else throw Error("type:"+_)},_.getLostPoint=function(r){for(var _=r.getModuleCount(),t=0,e=0;e<_;e+=1)for(var n=0;n<_;n+=1){for(var $=0,o=r.isDark(e,n),a=-1;a<=1;a+=1)if(!(e+a<0)&&!(_<=e+a))for(var f=-1;f<=1;f+=1)!(n+f<0)&&!(_<=n+f)&&(0!=a||0!=f)&&o==r.isDark(e+a,n+f)&&($+=1);$>5&&(t+=3+$-5)}for(var e=0;e<_-1;e+=1)for(var n=0;n<_-1;n+=1){var i=0;r.isDark(e,n)&&(i+=1),r.isDark(e+1,n)&&(i+=1),r.isDark(e,n+1)&&(i+=1),r.isDark(e+1,n+1)&&(i+=1),(0==i||4==i)&&(t+=3)}for(var e=0;e<_;e+=1)for(var n=0;n<_-6;n+=1)r.isDark(e,n)&&!r.isDark(e,n+1)&&r.isDark(e,n+2)&&r.isDark(e,n+3)&&r.isDark(e,n+4)&&!r.isDark(e,n+5)&&r.isDark(e,n+6)&&(t+=40);for(var n=0;n<_;n+=1)for(var e=0;e<_-6;e+=1)r.isDark(e,n)&&!r.isDark(e+1,n)&&r.isDark(e+2,n)&&r.isDark(e+3,n)&&r.isDark(e+4,n)&&!r.isDark(e+5,n)&&r.isDark(e+6,n)&&(t+=40);for(var u=0,n=0;n<_;n+=1)for(var e=0;e<_;e+=1)r.isDark(e,n)&&(u+=1);return t+10*(Math.abs(100*u/_/_-50)/5)},_}(),QRMath=function(){for(var r=Array(256),_=Array(256),t=0;t<8;t+=1)r[t]=1<<t;for(var t=8;t<256;t+=1)r[t]=r[t-4]^r[t-5]^r[t-6]^r[t-8];for(var t=0;t<255;t+=1)_[r[t]]=t;var e={};return e.glog=function(r){if(r<1)throw Error("glog("+r+")");return _[r]},e.gexp=function(_){for(;_<0;)_+=255;for(;_>=256;)_-=255;return r[_]},e}();function qrPolynomial(r,_){if(void 0===r.length)throw Error(r.length+"/"+_);var t=function(){for(var t=0;t<r.length&&0==r[t];)t+=1;for(var e=Array(r.length-t+_),n=0;n<r.length-t;n+=1)e[n]=r[n+t];return e}(),e={};return e.getAt=function(r){return t[r]},e.getLength=function(){return t.length},e.multiply=function(r){for(var _=Array(e.getLength()+r.getLength()-1),t=0;t<e.getLength();t+=1)for(var n=0;n<r.getLength();n+=1)_[t+n]^=QRMath.gexp(QRMath.glog(e.getAt(t))+QRMath.glog(r.getAt(n)));return qrPolynomial(_,0)},e.mod=function(r){if(e.getLength()-r.getLength()<0)return e;for(var _=QRMath.glog(e.getAt(0))-QRMath.glog(r.getAt(0)),t=Array(e.getLength()),n=0;n<e.getLength();n+=1)t[n]=e.getAt(n);for(var n=0;n<r.getLength();n+=1)t[n]^=QRMath.gexp(QRMath.glog(r.getAt(n))+_);return qrPolynomial(t,0).mod(r)},e}var QRRSBlock=function(){var r=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],_=function(r,_){var t={};return t.totalCount=r,t.dataCount=_,t},t={},e=function(_,t){switch(t){case QRErrorCorrectLevel.L:return r[(_-1)*4+0];case QRErrorCorrectLevel.M:return r[(_-1)*4+1];case QRErrorCorrectLevel.Q:return r[(_-1)*4+2];case QRErrorCorrectLevel.H:return r[(_-1)*4+3];default:return}};return t.getRSBlocks=function(r,t){var n=e(r,t);if(void 0===n)throw Error("bad rs block @ typeNumber:"+r+"/errorCorrectLevel:"+t);for(var $=n.length/3,o=[],a=0;a<$;a+=1)for(var f=n[3*a+0],i=n[3*a+1],u=n[3*a+2],c=0;c<f;c+=1)o.push(_(i,u));return o},t}(),qrBitBuffer=function(){var r=[],_=0,t={};return t.getBuffer=function(){return r},t.getAt=function(_){return(r[Math.floor(_/8)]>>>7-_%8&1)==1},t.put=function(r,_){for(var e=0;e<_;e+=1)t.putBit((r>>>_-e-1&1)==1)},t.getLengthInBits=function(){return _},t.putBit=function(t){var e=Math.floor(_/8);r.length<=e&&r.push(0),t&&(r[e]|=128>>>_%8),_+=1},t},qr8BitByte=function(r){for(var _=QRMode.MODE_8BIT_BYTE,t=r,e=[],n={},$=0,o=t.length;$<o;$++){var a=[],f=t.charCodeAt($);f>65536?(a[0]=240|(1835008&f)>>>18,a[1]=128|(258048&f)>>>12,a[2]=128|(4032&f)>>>6,a[3]=128|63&f):f>2048?(a[0]=224|(61440&f)>>>12,a[1]=128|(4032&f)>>>6,a[2]=128|63&f):f>128?(a[0]=192|(1984&f)>>>6,a[1]=128|63&f):a[0]=f,e.push(a)}(e=Array.prototype.concat.apply([],e)).length!=t.length&&(e.unshift(191),e.unshift(187),e.unshift(239));var i=e;return n.getMode=function(){return _},n.getLength=function(r){return i.length},n.write=function(r){for(var _=0;_<i.length;_+=1)r.put(i[_],8)},n},byteArrayOutputStream=function(){var r=[],_={};return _.writeByte=function(_){r.push(255&_)},_.writeShort=function(r){_.writeByte(r),_.writeByte(r>>>8)},_.writeBytes=function(r,t,e){t=t||0,e=e||r.length;for(var n=0;n<e;n+=1)_.writeByte(r[n+t])},_.writeString=function(r){for(var t=0;t<r.length;t+=1)_.writeByte(r.charCodeAt(t))},_.toByteArray=function(){return r},_.toString=function(){var _="";_+="[";for(var t=0;t<r.length;t+=1)t>0&&(_+=","),_+=r[t];return _+"]"},_},base64EncodeOutputStream=function(){var r=0,_=0,t=0,e="",n={},$=function(r){e+=String.fromCharCode(o(63&r))},o=function(r){if(r<0);else if(r<26)return 65+r;else if(r<52)return 97+(r-26);else if(r<62)return 48+(r-52);else if(62==r)return 43;else if(63==r)return 47;throw Error("n:"+r)};return n.writeByte=function(e){for(r=r<<8|255&e,_+=8,t+=1;_>=6;)$(r>>>_-6),_-=6},n.flush=function(){if(_>0&&($(r<<6-_),r=0,_=0),t%3!=0)for(var n=3-t%3,o=0;o<n;o+=1)e+="="},n.toString=function(){return e},n},base64DecodeInputStream=function(r){var _=r,t=0,e=0,n=0,$={};$.read=function(){for(;n<8;){if(t>=_.length){if(0==n)return -1;throw Error("unexpected end of file./"+n)}var r=_.charAt(t);if(t+=1,"="==r)return n=0,-1;!r.match(/^\s$/)&&(e=e<<6|o(r.charCodeAt(0)),n+=6)}var $=e>>>n-8&255;return n-=8,$};var o=function(r){if(65<=r&&r<=90)return r-65;if(97<=r&&r<=122)return r-97+26;if(48<=r&&r<=57)return r-48+52;if(43==r)return 62;if(47==r)return 63;else throw Error("c:"+r)};return $},gifImage=function(r,_){var t=r,e=_,n=Array(r*_),$={};$.setPixel=function(r,_,e){n[_*t+r]=e},$.write=function(r){r.writeString("GIF87a"),r.writeShort(t),r.writeShort(e),r.writeByte(128),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(255),r.writeByte(255),r.writeByte(255),r.writeString(","),r.writeShort(0),r.writeShort(0),r.writeShort(t),r.writeShort(e),r.writeByte(0);var _=a(2);r.writeByte(2);for(var n=0;_.length-n>255;)r.writeByte(255),r.writeBytes(_,n,255),n+=255;r.writeByte(_.length-n),r.writeBytes(_,n,_.length-n),r.writeByte(0),r.writeString(";")};var o=function(r){var _=r,t=0,e=0,n={};return n.write=function(r,n){if(r>>>n!=0)throw Error("length over");for(;t+n>=8;)_.writeByte(255&(r<<t|e)),n-=8-t,r>>>=8-t,e=0,t=0;e=r<<t|e,t+=n},n.flush=function(){t>0&&_.writeByte(e)},n},a=function(r){for(var _=1<<r,t=(1<<r)+1,e=r+1,$=f(),a=0;a<_;a+=1)$.add(String.fromCharCode(a));$.add(String.fromCharCode(_)),$.add(String.fromCharCode(t));var i=byteArrayOutputStream(),u=o(i);u.write(_,e);var c=0,g=String.fromCharCode(n[c]);for(c+=1;c<n.length;){var l=String.fromCharCode(n[c]);c+=1,$.contains(g+l)?g+=l:(u.write($.indexOf(g),e),4095>$.size()&&($.size()==1<<e&&(e+=1),$.add(g+l)),g=l)}return u.write($.indexOf(g),e),u.write(t,e),u.flush(),i.toByteArray()},f=function(){var r={},_=0,t={};return t.add=function(e){if(t.contains(e))throw Error("dup key:"+e);r[e]=_,_+=1},t.size=function(){return _},t.indexOf=function(_){return r[_]},t.contains=function(_){return void 0!==r[_]},t};return $},createImgTag=function(r,_,t,e){for(var n=gifImage(r,_),$=0;$<_;$+=1)for(var o=0;o<r;o+=1)n.setPixel(o,$,t(o,$));var a=byteArrayOutputStream();n.write(a);for(var f=base64EncodeOutputStream(),i=a.toByteArray(),u=0;u<i.length;u+=1)f.writeByte(i[u]);f.flush();var c="";return c+="data:image/gif;base64,",c+=f},drawImg=function(r,_){var t,e=(_=_||{}).typeNumber||4,n=_.errorCorrectLevel||"M",$=_.size||500;try{(t=qrcode(e,n||"M")).addData(r),t.make()}catch(o){if(!(e>=40))return drawImg(r,{size:$,errorCorrectLevel:n,typeNumber:e+1});throw Error("Text too long to encode")}var a=parseInt($/t.getModuleCount()),f=parseInt(($-t.getModuleCount()*a)/2);return t.createImgTag(a,f,$)}; @@ -183,9 +183,9 @@ window.addEventListener('load', async () => { }); `; - return new Response(js, { - headers: { - 'Content-Type': 'application/javascript' - } - }); + return new Response(js, { + headers: { + "Content-Type": "application/javascript", + }, + }); } diff --git a/src/routes/(app)/admin/+layout.server.js b/src/routes/(app)/admin/+layout.server.js index 26449467..e49c6e40 100644 --- a/src/routes/(app)/admin/+layout.server.js +++ b/src/routes/(app)/admin/+layout.server.js @@ -1,11 +1,11 @@ -import { auth, get } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import { auth, get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export async function load({ cookies, parent }) { - let token = cookies.get('token'); - if (!token) throw redirect(307, '/login'); - let user = await get('/me', auth(cookies)); - if (!user) throw redirect(307, '/login'); + let token = cookies.get("token"); + if (!token) throw redirect(307, "/login"); + let user = await get("/me", auth(cookies)); + if (!user) throw redirect(307, "/login"); - return { user }; + return { user }; } diff --git a/src/routes/(app)/admin/+layout.svelte b/src/routes/(app)/admin/+layout.svelte index d7561e32..33b64b6a 100644 --- a/src/routes/(app)/admin/+layout.svelte +++ b/src/routes/(app)/admin/+layout.svelte @@ -1,10 +1,10 @@ <script> - import { LandingHeader } from '$comp'; - export let data; - let { user } = data; + import { LandingHeader } from "$comp"; + export let data; + let { user } = data; </script> <LandingHeader {user} /> <main class="container px-10 md:px-40 mx-auto min-h-[600px] pb-20"> - <slot /> + <slot /> </main> diff --git a/src/routes/(app)/admin/+page.server.js b/src/routes/(app)/admin/+page.server.js index e19f894c..93931aa3 100644 --- a/src/routes/(app)/admin/+page.server.js +++ b/src/routes/(app)/admin/+page.server.js @@ -1,10 +1,12 @@ -import { auth, get } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import { auth, get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export async function load({ cookies, parent }) { - let { user } = await parent(); - if (!user) throw redirect(307, '/login'); + let { user } = await parent(); + if (!user) throw redirect(307, "/login"); - let users = (await get('/users', auth(cookies))).sort((a, b) => b.balance - a.balance); - return { users }; + let users = (await get("/users", auth(cookies))).sort( + (a, b) => b.balance - a.balance + ); + return { users }; } diff --git a/src/routes/(app)/admin/+page.svelte b/src/routes/(app)/admin/+page.svelte index 529d2f77..c61a2e45 100644 --- a/src/routes/(app)/admin/+page.svelte +++ b/src/routes/(app)/admin/+page.svelte @@ -1,22 +1,22 @@ <script> - import { post } from '$lib/utils'; - export let data; - let reset = (username) => post(`/${username}/reset`, { password }); - let { users } = data; - let password; + import { post } from "$lib/utils"; + export let data; + let reset = (username) => post(`/${username}/reset`, { password }); + let { users } = data; + let password; </script> {#each users as { balance, username, expected }} - <div class="grid grid-cols-5"> - <div>{username}</div> - <div>{balance}</div> - <div>{expected}</div> - <div><input bind:value={password} /></div> - <div> - <button - class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px] bg-black text-white" - on:click={() => reset(username)}>Reset</button - > - </div> - </div> + <div class="grid grid-cols-5"> + <div>{username}</div> + <div>{balance}</div> + <div>{expected}</div> + <div><input bind:value={password} /></div> + <div> + <button + class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px] bg-black text-white" + on:click={() => reset(username)}>Reset</button + > + </div> + </div> {/each} diff --git a/src/routes/(app)/buy/+page.server.js b/src/routes/(app)/buy/+page.server.js index fc7ae473..3d3265cb 100644 --- a/src/routes/(app)/buy/+page.server.js +++ b/src/routes/(app)/buy/+page.server.js @@ -1,27 +1,28 @@ -import { env } from '$env/dynamic/private'; -import { error, fail, redirect } from '@sveltejs/kit'; -import { post, auth } from '$lib/utils'; -import { createTicket } from '$lib/ticket'; +import { env } from "$env/dynamic/private"; +import { error, fail, redirect } from "@sveltejs/kit"; +import { post, auth } from "$lib/utils"; +import { createTicket } from "$lib/ticket"; export const actions = { - default: async ({ cookies, request }) => { - let form = await request.formData(); + default: async ({ cookies, request }) => { + let form = await request.formData(); - let amount = parseFloat(form.get('amount')); - if (!amount || amount < 0 || amount > 500) return fail(400, { message: 'Invalid amount' }); + let amount = parseFloat(form.get("amount")); + if (!amount || amount < 0 || amount > 500) + return fail(400, { message: "Invalid amount" }); - await post( - '/buy', - { - amount: form.get('amount'), - number: form.get('number').replace(/\s+/g, ''), - month: form.get('expiry').slice(0, 2), - year: form.get('expiry').slice(-2), - cvc: form.get('cvc') - }, - auth(cookies) - ); + await post( + "/buy", + { + amount: form.get("amount"), + number: form.get("number").replace(/\s+/g, ""), + month: form.get("expiry").slice(0, 2), + year: form.get("expiry").slice(-2), + cvc: form.get("cvc"), + }, + auth(cookies) + ); - throw redirect(307, `/${form.get('username')}/payments`); - } + throw redirect(307, `/${form.get("username")}/payments`); + }, }; diff --git a/src/routes/(app)/buy/+page.svelte b/src/routes/(app)/buy/+page.svelte index 22e7e9ec..2f999555 100644 --- a/src/routes/(app)/buy/+page.svelte +++ b/src/routes/(app)/buy/+page.svelte @@ -1,131 +1,135 @@ <script> - import { enhance } from '$app/forms'; - import { Icon, Lock, Card, Calendar, Spinner } from '$comp'; - import { animatedRate } from '$lib/store'; - import { f } from '$lib/utils'; + import { enhance } from "$app/forms"; + import { Icon, Lock, Card, Calendar, Spinner } from "$comp"; + import { animatedRate } from "$lib/store"; + import { f } from "$lib/utils"; - export let data, form; + export let data, form; - let { username, currency } = data.user; - let loading; + let { username, currency } = data.user; + let loading; - let amount, n, d, cvc; + let amount, n, d, cvc; </script> <div class="container px-4 max-w-xl mx-auto mt-10 space-y-5"> - <div class="w-full flex"> - <a href="/" class="mx-auto"> - <Icon icon="logo" /> - </a> - </div> + <div class="w-full flex"> + <a href="/" class="mx-auto"> + <Icon icon="logo" /> + </a> + </div> - <div class="flex justify-center items-center"> - <div class="shadow-xl rounded-3xl px-10 pt-5 pb-10 space-y-5 w-full mx-5"> - <div class="relative"> - <h1 class="text-2xl md:text-3xl font-semibold text-center">Buy Bitcoin</h1> - </div> - <p class="text-secondary text-center">Purchase up to $500 worth of Bitcoin</p> + <div class="flex justify-center items-center"> + <div class="shadow-xl rounded-3xl px-10 pt-5 pb-10 space-y-5 w-full mx-5"> + <div class="relative"> + <h1 class="text-2xl md:text-3xl font-semibold text-center"> + Buy Bitcoin + </h1> + </div> + <p class="text-secondary text-center"> + Purchase up to $500 worth of Bitcoin + </p> - {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> - {/if} + {#if form?.message} + <div class="text-red-600 text-center"> + {form.message} + </div> + {/if} - <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} /> + <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">Amount</span> - <input - class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" - type="text" - name="amount" - placeholder="•••" - bind:value={amount} - /> - <span - class="absolute bottom-[9px] left-0 translate-x-1/2 w-6 text-black peer-placeholder-shown:text-gray-300 + <label class="relative w-full flex flex-col"> + <span class="font-bold mb-3">Amount</span> + <input + class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" + type="text" + name="amount" + placeholder="•••" + bind:value={amount} + /> + <span + class="absolute bottom-[9px] left-0 translate-x-1/2 w-6 text-black peer-placeholder-shown:text-gray-300 ">$</span - > - </label> + > + </label> - <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" - bind:value={n} - /> - <Card /> - </label> + <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" + bind:value={n} + /> + <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" - bind:value={d} - /> - <Calendar /> - </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" + bind:value={d} + /> + <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="•••" - bind:value={cvc} - /> - <Lock /> - </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="•••" + bind:value={cvc} + /> + <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 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 class="flex"> - <div class="text-secondary flex mr-auto"> - <div class="flex mr-1"> - <div class="my-auto mr-1">1</div> - <img src="/images/bitcoin.svg" class="w-5 my-auto" alt="Bitcoin" /> - </div> - </div> - <p class="text-secondary">3% credit card fee applies</p> - </div> - </div> - </div> + <div class="flex"> + <div class="text-secondary flex mr-auto"> + <div class="flex mr-1"> + <div class="my-auto mr-1">1</div> + <img src="/images/bitcoin.svg" class="w-5 my-auto" alt="Bitcoin" /> + </div> + </div> + <p class="text-secondary">3% credit card fee applies</p> + </div> + </div> + </div> </div> diff --git a/src/routes/(app)/disable2fa/+server.js b/src/routes/(app)/disable2fa/+server.js index b2bb07ec..1e264398 100644 --- a/src/routes/(app)/disable2fa/+server.js +++ b/src/routes/(app)/disable2fa/+server.js @@ -1,13 +1,13 @@ -import { error, json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { error, json } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - try { - let body = await request.json(); - let res = await post('/disable2fa', body, auth(cookies)); + try { + let body = await request.json(); + let res = await post("/disable2fa", body, auth(cookies)); - return json(res); - } catch (e) { - throw error(500, 'Problem disabling 2fa'); - } + return json(res); + } catch (e) { + throw error(500, "Problem disabling 2fa"); + } } diff --git a/src/routes/(app)/enable2fa/+server.js b/src/routes/(app)/enable2fa/+server.js index 344850da..6917ae21 100644 --- a/src/routes/(app)/enable2fa/+server.js +++ b/src/routes/(app)/enable2fa/+server.js @@ -1,8 +1,8 @@ -import { auth, post } from '$lib/utils'; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - let body = await request.json(); - let res = await post('/2fa', body, auth(cookies)); + let body = await request.json(); + let res = await post("/2fa", body, auth(cookies)); - return new Response(JSON.stringify(res)); + return new Response(JSON.stringify(res)); } diff --git a/src/routes/(app)/events/+server.js b/src/routes/(app)/events/+server.js index decd43cb..95df0bc5 100644 --- a/src/routes/(app)/events/+server.js +++ b/src/routes/(app)/events/+server.js @@ -1,6 +1,6 @@ -import { auth, post } from '$lib/utils'; -import { json } from '@sveltejs/kit'; +import { auth, post } from "$lib/utils"; +import { json } from "@sveltejs/kit"; export async function POST({ cookies, request }) { - return json(post('/event', await request.json(), auth(cookies))); + return json(post("/event", await request.json(), auth(cookies))); } diff --git a/src/routes/(app)/fund/[id]/+page.server.js b/src/routes/(app)/fund/[id]/+page.server.js index 4194deed..6c5c943f 100644 --- a/src/routes/(app)/fund/[id]/+page.server.js +++ b/src/routes/(app)/fund/[id]/+page.server.js @@ -1,23 +1,23 @@ -import { error, redirect } from '@sveltejs/kit'; -import { auth, get, post } from '$lib/utils'; -import Qr from 'qrcode-base64'; +import { error, redirect } from "@sveltejs/kit"; +import { auth, get, post } from "$lib/utils"; +import Qr from "qrcode-base64"; export let load = async ({ params: { id }, url }) => { - let { amount, payments } = await get(`/fund/${id}`); + let { amount, payments } = await get(`/fund/${id}`); - let src = Qr.drawImg(url.href, { size: 300 }); - return { amount, payments, src }; + let src = Qr.drawImg(url.href, { size: 300 }); + return { amount, payments, src }; }; export const actions = { - default: async ({ cookies, locals, params, request }) => { - let { user } = locals; + default: async ({ cookies, locals, params, request }) => { + let { user } = locals; - if (user) { - await post('/redeem', params, auth(cookies)); - throw redirect(307, `/${user.username}/payments`); - } + if (user) { + await post("/redeem", params, auth(cookies)); + throw redirect(307, `/${user.username}/payments`); + } - throw redirect(307, '/register'); - } + throw redirect(307, "/register"); + }, }; diff --git a/src/routes/(app)/fund/[id]/+page.svelte b/src/routes/(app)/fund/[id]/+page.svelte index d2d3079c..117a4b42 100644 --- a/src/routes/(app)/fund/[id]/+page.svelte +++ b/src/routes/(app)/fund/[id]/+page.svelte @@ -1,138 +1,146 @@ <script> - import { format } from 'date-fns'; - import { enhance } from '$app/forms'; - import { page } from '$app/stores'; - import { Avatar, Icon } from '$comp'; - import { toast } from '@zerodevx/svelte-toast'; - import { t } from '$lib/translations'; - import { types, copy, f, s, sat, sats } from '$lib/utils'; - import { loginRedirect } from '$lib/store'; + import { format } from "date-fns"; + import { enhance } from "$app/forms"; + import { page } from "$app/stores"; + import { Avatar, Icon } from "$comp"; + import { toast } from "@zerodevx/svelte-toast"; + import { t } from "$lib/translations"; + import { types, copy, f, s, sat, sats } from "$lib/utils"; + import { loginRedirect } from "$lib/store"; - export let data; + export let data; - let { amount, src, payments, rates, user } = data; - let { id } = $page.params; + let { amount, src, payments, rates, user } = data; + let { id } = $page.params; - toast.pop(0); + toast.pop(0); - let currency = user ? user.currency : 'CAD'; - let rate = rates[currency]; + let currency = user ? user.currency : "CAD"; + let rate = rates[currency]; - $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); - $loginRedirect = $page.url.pathname; + $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); + $loginRedirect = $page.url.pathname; - let show; - let toggle = () => (show = !show); + let show; + let toggle = () => (show = !show); </script> <div class="container px-4 max-w-4xl mx-auto mt-10 space-y-5"> - <div class="flex justify-center items-center"> - <div class="md:shadow-xl rounded-3xl md:px-10 pt-5 pb-10 space-y-5 w-full md:mx-5"> - <div class="relative flex justify-center gap-2"> - <!-- <p class="absolute right-0 top-1 underline"><Icon icon="settings" /></p> --> - {#if id.split('-').length === 4} - <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {id}</h1> - {:else} - <img src="/images/moneypot.jpg" class="w-24" alt="Fund" /> - <div class="my-auto"> - <h1 class="text-2xl font-semibold my-auto">{$t('payments.fund')}</h1> - <h1 class="text-secondary my-auto">{id}</h1> - </div> - {/if} - </div> - {#if show} - <img {src} class="mx-auto" alt={id} /> - {/if} - <div class="flex justify-center gap-4"> - <div class="my-auto"> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(amountFiat, currency)} - </h2> - <h3 class="text-secondary md:text-lg mt-1"> - {sat(amount)} - </h3> - </div> - </div> - <div class="flex gap-2" data-sveltekit-prefetch="off"> - <div class="grow"> - <a href={`/send/fund/${id}`}> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - > - <Icon icon="plus" /> - {$t('payments.addFunds')} - </button> - </a> - </div> - <div class="grow"> - <a href={`/fund/${id}/withdraw`}> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - > - <Icon icon="minus" style="rotate-180" /> - {$t('payments.takeFunds')} - </button> - </a> - </div> - </div> - <div class="flex gap-2" data-sveltekit-prefetch="off"> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - on:click={toggle} - > - <Icon icon="qr" style="invert" /> - {show ? $t('payments.hide') : $t('payments.show')} QR - </button> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - on:click={() => copy($page.url.host + $page.url.pathname)} - > - <Icon icon="copy" style="w-6" /> - {$t('payments.copyLink')} - </button> - </div> - <div class="text-base"> - {#each payments as p} - <a href={`/payment/${p.id}`}> - <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4"> - <div class="whitespace-nowrap my-auto"> - <div class="font-bold" class:text-red-800={p.amount > 0}> - {f(Math.abs(p.amount) * (p.rate / sats), p.currency)} - {#if p.tip} - <span class="text-sm text-secondary"> - +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% - </span> - {/if} - </div> + <div class="flex justify-center items-center"> + <div + class="md:shadow-xl rounded-3xl md:px-10 pt-5 pb-10 space-y-5 w-full md:mx-5" + > + <div class="relative flex justify-center gap-2"> + <!-- <p class="absolute right-0 top-1 underline"><Icon icon="settings" /></p> --> + {#if id.split("-").length === 4} + <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {id}</h1> + {:else} + <img src="/images/moneypot.jpg" class="w-24" alt="Fund" /> + <div class="my-auto"> + <h1 class="text-2xl font-semibold my-auto"> + {$t("payments.fund")} + </h1> + <h1 class="text-secondary my-auto">{id}</h1> + </div> + {/if} + </div> + {#if show} + <img {src} class="mx-auto" alt={id} /> + {/if} + <div class="flex justify-center gap-4"> + <div class="my-auto"> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(amountFiat, currency)} + </h2> + <h3 class="text-secondary md:text-lg mt-1"> + {sat(amount)} + </h3> + </div> + </div> + <div class="flex gap-2" data-sveltekit-prefetch="off"> + <div class="grow"> + <a href={`/send/fund/${id}`}> + <button + class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" + > + <Icon icon="plus" /> + {$t("payments.addFunds")} + </button> + </a> + </div> + <div class="grow"> + <a href={`/fund/${id}/withdraw`}> + <button + class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" + > + <Icon icon="minus" style="rotate-180" /> + {$t("payments.takeFunds")} + </button> + </a> + </div> + </div> + <div class="flex gap-2" data-sveltekit-prefetch="off"> + <button + class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" + on:click={toggle} + > + <Icon icon="qr" style="invert" /> + {show ? $t("payments.hide") : $t("payments.show")} QR + </button> + <button + class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" + on:click={() => copy($page.url.host + $page.url.pathname)} + > + <Icon icon="copy" style="w-6" /> + {$t("payments.copyLink")} + </button> + </div> + <div class="text-base"> + {#each payments as p} + <a href={`/payment/${p.id}`}> + <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4"> + <div class="whitespace-nowrap my-auto"> + <div class="font-bold" class:text-red-800={p.amount > 0}> + {f(Math.abs(p.amount) * (p.rate / sats), p.currency)} + {#if p.tip} + <span class="text-sm text-secondary"> + +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% + </span> + {/if} + </div> - <div class="text-secondary"> - {sat(Math.abs(p.amount) + (p.tip || 0))} - </div> - </div> + <div class="text-secondary"> + {sat(Math.abs(p.amount) + (p.tip || 0))} + </div> + </div> - <div class="flex my-auto"> - <div class="flex"> - <div class="my-auto"> - <Avatar user={p.user} size={16} disabled={true} /> - </div> - <div class="my-auto ml-1 text-secondary">{p.user.username}</div> - </div> - </div> + <div class="flex my-auto"> + <div class="flex"> + <div class="my-auto"> + <Avatar user={p.user} size={16} disabled={true} /> + </div> + <div class="my-auto ml-1 text-secondary"> + {p.user.username} + </div> + </div> + </div> - <div class="text-secondary text-right text-sm my-auto"> - <div> - {format(new Date(p.created), 'h:mm aaa')} - </div> - <div> - {format(new Date(p.created), 'MMM d')} - </div> - </div> - </div> - </a> - {:else} - <p class="text-secondary text-lg text-center">{$t('payments.empty')}</p> - {/each} - </div> - </div> - </div> + <div class="text-secondary text-right text-sm my-auto"> + <div> + {format(new Date(p.created), "h:mm aaa")} + </div> + <div> + {format(new Date(p.created), "MMM d")} + </div> + </div> + </div> + </a> + {:else} + <p class="text-secondary text-lg text-center"> + {$t("payments.empty")} + </p> + {/each} + </div> + </div> + </div> </div> diff --git a/src/routes/(app)/fund/[id]/sweep/+page.server.js b/src/routes/(app)/fund/[id]/sweep/+page.server.js index 9fd244a3..05fb18b6 100644 --- a/src/routes/(app)/fund/[id]/sweep/+page.server.js +++ b/src/routes/(app)/fund/[id]/sweep/+page.server.js @@ -1,14 +1,13 @@ -import { get, auth, post } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import { get, auth, post } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export async function load({ cookies, params, parent }) { - let { user } = await parent(); - let { id } = params; - let { amount } = await get(`/fund/${id}`); - + let { user } = await parent(); + let { id } = params; + let { amount } = await get(`/fund/${id}`); if (!amount) throw redirect(307, `/fund/${id}`); - if (!user) throw redirect(307, `/register?redirect=/fund/${id}/sweep`); - await post('/take', { amount, id }, auth(cookies)); - return { amount, id }; + if (!user) throw redirect(307, `/register?redirect=/fund/${id}/sweep`); + await post("/take", { amount, id }, auth(cookies)); + return { amount, id }; } diff --git a/src/routes/(app)/fund/[id]/sweep/+page.svelte b/src/routes/(app)/fund/[id]/sweep/+page.svelte index 64bed4dd..bbd7013f 100644 --- a/src/routes/(app)/fund/[id]/sweep/+page.svelte +++ b/src/routes/(app)/fund/[id]/sweep/+page.svelte @@ -1,37 +1,41 @@ <script> - import { scale } from 'svelte/transition'; - import { fiat, f, s } from '$lib/utils'; - import { Icon } from '$comp'; - import { t } from '$lib/translations'; + import { scale } from "svelte/transition"; + import { fiat, f, s } from "$lib/utils"; + import { Icon } from "$comp"; + import { t } from "$lib/translations"; - export let data; + export let data; - let { amount, id, user, rates } = data; - let { currency } = user; - $: rate = rates[currency]; + let { amount, id, user, rates } = data; + let { currency } = user; + $: rate = rates[currency]; </script> <div class="container px-4 max-w-xl mx-auto mt-10 space-y-5 text-center"> - <div class="w-full flex"> - <a href="/" class="mx-auto"> - <Icon icon="logo" /> - </a> - </div> + <div class="w-full flex"> + <a href="/" class="mx-auto"> + <Icon icon="logo" /> + </a> + </div> - <div class="flex w-full py-5 max-w-[200px] mx-auto" in:scale={{ start: 0.5 }}> - <Icon icon="check" style="mx-auto" /> - </div> - <h1 class="text-3xl md:text-4xl font-bold mb-6">{$t('payments.fundsClaimed')}</h1> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(fiat(amount, rate), currency)} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1"> - ⚡️{s(amount)} - </h3> + <div class="flex w-full py-5 max-w-[200px] mx-auto" in:scale={{ start: 0.5 }}> + <Icon icon="check" style="mx-auto" /> + </div> + <h1 class="text-3xl md:text-4xl font-bold mb-6"> + {$t("payments.fundsClaimed")} + </h1> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(fiat(amount, rate), currency)} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1"> + ⚡️{s(amount)} + </h3> </div> <a href={`/${user.username}/payments`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> </a> -<div class="fixed bottom-10 w-full text-center">{$t('payments.tapAnywhere')}</div> +<div class="fixed bottom-10 w-full text-center"> + {$t("payments.tapAnywhere")} +</div> diff --git a/src/routes/(app)/fund/[id]/withdraw/+page.server.js b/src/routes/(app)/fund/[id]/withdraw/+page.server.js index 476149f2..6fb08f61 100644 --- a/src/routes/(app)/fund/[id]/withdraw/+page.server.js +++ b/src/routes/(app)/fund/[id]/withdraw/+page.server.js @@ -1,20 +1,20 @@ -import { fail, json, redirect } from '@sveltejs/kit'; -import { fd, auth, post } from '$lib/utils'; +import { fail, json, redirect } from "@sveltejs/kit"; +import { fd, auth, post } from "$lib/utils"; export async function load({ parent }) { - let { user } = await parent(); - if (!user) throw redirect(307, '/register'); + let { user } = await parent(); + if (!user) throw redirect(307, "/register"); } export const actions = { - default: async ({ cookies, request }) => { - let body = await fd(request); - try { - await post('/take', body, auth(cookies)); - } catch (e) { - return fail(400, { message: e.message }); - } + default: async ({ cookies, request }) => { + let body = await fd(request); + try { + await post("/take", body, auth(cookies)); + } catch (e) { + return fail(400, { message: e.message }); + } - throw redirect(307, `/fund/${body.id}`); - } + throw redirect(307, `/fund/${body.id}`); + }, }; diff --git a/src/routes/(app)/fund/[id]/withdraw/+page.svelte b/src/routes/(app)/fund/[id]/withdraw/+page.svelte index 9cad945a..6b2010b7 100644 --- a/src/routes/(app)/fund/[id]/withdraw/+page.svelte +++ b/src/routes/(app)/fund/[id]/withdraw/+page.svelte @@ -1,58 +1,58 @@ <script> - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { Icon, Numpad, Spinner } from '$comp'; - import { page } from '$app/stores'; - import { back } from '$lib/utils'; - import { pin } from '$lib/store'; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + 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; + export let data; + export let form; - let { rates } = data; - let { amount, id } = $page.params; - let { currency } = data.user; - let loading; + let { rates } = data; + let { amount, id } = $page.params; + let { currency } = data.user; + let loading; - let submit = () => (loading = true); - $: update(form); - let update = () => { - if (form?.message.includes('pin')) $pin = undefined; - loading = false; - }; + let submit = () => (loading = true); + $: update(form); + let update = () => { + if (form?.message.includes("pin")) $pin = undefined; + loading = false; + }; - $: rate = rates[currency]; + $: rate = rates[currency]; </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" /> + <Icon icon="arrow-left" style="w-10" /> </button> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <div class="text-red-600 text-center"> + {form.message} + </div> {/if} <div class="container px-4 mt-20 max-w-xl mx-auto"> - <Numpad bind:amount bind:currency bind:rate {submit} /> + <Numpad bind:amount bind:currency bind:rate {submit} /> - <form method="POST" use:enhance on:submit={submit}> - <input name="id" value={id} type="hidden" /> - <input name="amount" value={amount} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> + <form method="POST" use:enhance on:submit={submit}> + <input name="id" value={id} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> - <div class="flex w-full"> - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.send')} - {/if} - </button> - </div> - </form> + <div class="flex w-full"> + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + </div> + </form> </div> diff --git a/src/routes/(app)/fund/[id]/withdraw/+server.js b/src/routes/(app)/fund/[id]/withdraw/+server.js index 0f9a23f3..96d66413 100644 --- a/src/routes/(app)/fund/[id]/withdraw/+server.js +++ b/src/routes/(app)/fund/[id]/withdraw/+server.js @@ -1,6 +1,6 @@ -import { json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { json } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - return json(await post('/take', await request.json(), auth(cookies))); + return json(await post("/take", await request.json(), auth(cookies))); } diff --git a/src/routes/(app)/launch/+page.server.js b/src/routes/(app)/launch/+page.server.js index 2b44dfae..d11af500 100644 --- a/src/routes/(app)/launch/+page.server.js +++ b/src/routes/(app)/launch/+page.server.js @@ -1,6 +1,6 @@ -import { auth, get } from '$lib/utils'; +import { auth, get } from "$lib/utils"; export const load = async ({ parent }) => { - let tickets = await get('/tickets'); - return { ...(await parent()), tickets }; + let tickets = await get("/tickets"); + return { ...(await parent()), tickets }; }; diff --git a/src/routes/(app)/launch/+page.svelte b/src/routes/(app)/launch/+page.svelte index 3469f0bc..028274fa 100644 --- a/src/routes/(app)/launch/+page.svelte +++ b/src/routes/(app)/launch/+page.svelte @@ -1,103 +1,118 @@ <script> - 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'; + 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, tickets } = data; - $loginRedirect = '/launch/purchase'; + export let data; + 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'; + let hasTicket = !!user?.accounts.find((a) => tickets.includes(a.asset)); + $: link = hasTicket ? "/launch/ticket" : user ? $loginRedirect : "/register"; + $: text = (hasTicket ? "View" : "Purchase") + " Ticket"; - beforeNavigate(({ to }) => { - if (to?.routeId === 'register') info('Please sign in so we can get you a ticket'); - }); + beforeNavigate(({ to }) => { + 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); - }; + let showMobileMenu = false; + const mobileMenuButtonClick = (section) => { + showMobileMenu = false; + scroll(section); + }; </script> -<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 + 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">26</div> - <div class="md:text-xl">6 pm</div> - </div> - <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"> - Launch Party! - </h1> + <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">26</div> + <div class="md:text-xl">6 pm</div> + </div> + <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" + > + Launch Party! + </h1> - <p class="leading-normal mb-2"> - Come join us at our launch party celebrating our new design and 10 years serving the - Vancouver Bitcoin community! - </p> + <p class="leading-normal mb-2"> + 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> + <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> + <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> + 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> + <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} class="mx-auto"> - <button - class="bg-white text-black border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl" - > - {text} - </button> - </a> - </div> - </div> - </div> - </div> + <div class="flex w-full my-8"> + <a href={link} class="mx-auto"> + <button + class="bg-white text-black border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl" + > + {text} + </button> + </a> + </div> + </div> + </div> + </div> </div> diff --git a/src/routes/(app)/launch/purchase/+page.server.js b/src/routes/(app)/launch/purchase/+page.server.js index a3043d9c..58669606 100644 --- a/src/routes/(app)/launch/purchase/+page.server.js +++ b/src/routes/(app)/launch/purchase/+page.server.js @@ -1,45 +1,49 @@ -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'; +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 }; + 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 = { - internal: async ({ cookies, request }) => { - let form = await request.formData(); - let asset = await createTicket(cookies, form.get('username')); - await post('/payments', { amount: 22000, username: 'launch' }, auth(cookies)); + internal: async ({ cookies, request }) => { + let form = await request.formData(); + let asset = await createTicket(cookies, form.get("username")); + await post( + "/payments", + { amount: 22000, username: "launch" }, + auth(cookies) + ); - throw redirect(307, `/launch/thanks/${asset}`); - }, + throw redirect(307, `/launch/thanks/${asset}`); + }, - lightning: async ({ cookies, request }) => { - let form = await request.formData(); + lightning: async ({ cookies, request }) => { + let form = await request.formData(); - let rates = await get('/rates'); + let rates = await get("/rates"); - let invoice = { - amount: 22000, - memo: 'launch', - type: 'lightning', - prompt: false, - rate: rates[form.get('currency')] - }; + let invoice = { + amount: 22000, + memo: "launch", + type: "lightning", + prompt: false, + rate: rates[form.get("currency")], + }; - let user = { username: form.get('username') }; + let user = { username: form.get("username") }; - let { id } = await post('/invoice', { invoice, user }, auth(cookies)); + let { id } = await post("/invoice", { invoice, user }, auth(cookies)); - throw redirect(307, `/${user.username}/invoice/${id}`); - } + throw redirect(307, `/${user.username}/invoice/${id}`); + }, }; diff --git a/src/routes/(app)/launch/purchase/+page.svelte b/src/routes/(app)/launch/purchase/+page.svelte index fbaa58c0..49a51973 100644 --- a/src/routes/(app)/launch/purchase/+page.svelte +++ b/src/routes/(app)/launch/purchase/+page.svelte @@ -1,66 +1,74 @@ <script> - import { onMount } from 'svelte'; - import { enhance } from '$app/forms'; - import { post } from '$lib/utils'; - import { goto } from '$app/navigation'; + import { onMount } from "svelte"; + import { enhance } from "$app/forms"; + import { post } from "$lib/utils"; + import { goto } from "$app/navigation"; - export let data; + export let data; - let amount = 22000; - let { - currency, - account: { balance }, - username - } = data.user; + let amount = 22000; + let { + currency, + account: { balance }, + username, + } = data.user; - let { tickets } = data; - let ticket = tickets.length; + let { tickets } = data; + let ticket = tickets.length; </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"> - Purchase Ticket - </h1> + <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> - <img - src={`/tickets/${(ticket + 1).toString().padStart(2, '0')}.png`} - class="w-full max-w-[800px]" - alt="Ticket" - /> + <img + src={`/tickets/${(ticket + 1).toString().padStart(2, "0")}.png`} + class="w-full max-w-[800px]" + alt="Ticket" + /> - {#if balance >= amount} - <form action="?/internal" method="POST" use:enhance> - <input type="hidden" name="username" value={username} /> + {#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> - </div> - </form> - {:else} - <h1 class="mb-4 text-xl font-bold leading-none tracking-tight text-gray-800 text-center"> - Payment Options - </h1> + <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> + </div> + </form> + {:else} + <h1 + class="mb-4 text-xl font-bold leading-none tracking-tight text-gray-800 text-center" + > + Payment Options + </h1> - <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> + <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> - <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> + <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/(app)/launch/purchase/card/+page.server.js b/src/routes/(app)/launch/purchase/card/+page.server.js index 1f564f78..bca601d3 100644 --- a/src/routes/(app)/launch/purchase/card/+page.server.js +++ b/src/routes/(app)/launch/purchase/card/+page.server.js @@ -1,44 +1,52 @@ -import { env } from '$env/dynamic/private'; -import { error, redirect } from '@sveltejs/kit'; -import { post, auth } from '$lib/utils'; -import { createTicket } from '$lib/ticket'; +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'; +let stripe = "https://api.stripe.com/v1"; export const actions = { - default: async ({ cookies, request }) => { - let form = await request.formData(); + default: async ({ cookies, request }) => { + let form = await request.formData(); - let headers = new Headers(); - headers.set('Authorization', 'Basic ' + new Buffer(env.STRIPE + ':').toString('base64')); + 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 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 method = "POST"; - let { id: source } = await fetch(`${stripe}/tokens`, { body, headers, method }).then((r) => - r.json() - ); + 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' - }); + body = new URLSearchParams({ + amount: 600, + currency: "cad", + source, + description: "launch ticket", + }); - let { status } = await fetch(`${stripe}/charges`, { body, headers, method }).then((r) => - r.json() - ); + 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')); + if (status !== "succeeded") + throw error(500, { message: "Card payment failed" }); + let asset = await createTicket(cookies, form.get("username")); - throw redirect(307, `/launch/thanks/${asset}`); - } + throw redirect(307, `/launch/thanks/${asset}`); + }, }; diff --git a/src/routes/(app)/launch/purchase/card/+page.svelte b/src/routes/(app)/launch/purchase/card/+page.svelte index 05d09025..037a65b2 100644 --- a/src/routes/(app)/launch/purchase/card/+page.svelte +++ b/src/routes/(app)/launch/purchase/card/+page.svelte @@ -1,77 +1,77 @@ <script> - import { enhance } from '$app/forms'; - import { Lock, Card, Calendar, Spinner } from '$comp'; + import { enhance } from "$app/forms"; + import { Lock, Card, Calendar, Spinner } from "$comp"; - export let data; + export let data; - let { username } = data.user; - let loading; + 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> + <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 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="•••" - /> - <Lock /> - </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="•••" + /> + <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 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/(app)/launch/thanks/[asset]/+page.svelte b/src/routes/(app)/launch/thanks/[asset]/+page.svelte index 43f10057..9b37002b 100644 --- a/src/routes/(app)/launch/thanks/[asset]/+page.svelte +++ b/src/routes/(app)/launch/thanks/[asset]/+page.svelte @@ -1,29 +1,31 @@ <script> - import { page } from '$app/stores'; + import { page } from "$app/stores"; - export let data; + export let data; - let { user } = data; + let { user } = data; - let { asset } = $page.params; + 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> + <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> + <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 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/(app)/launch/ticket/+page.server.js b/src/routes/(app)/launch/ticket/+page.server.js index 0362eb7d..7b51e679 100644 --- a/src/routes/(app)/launch/ticket/+page.server.js +++ b/src/routes/(app)/launch/ticket/+page.server.js @@ -1,9 +1,11 @@ -import { get } from '$lib/utils'; +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 }; + 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/(app)/launch/ticket/+page.svelte b/src/routes/(app)/launch/ticket/+page.svelte index 3879abcd..6b16e804 100644 --- a/src/routes/(app)/launch/ticket/+page.svelte +++ b/src/routes/(app)/launch/ticket/+page.svelte @@ -1,35 +1,37 @@ <script> - export let data; - let { asset, ticket } = data; + 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> + <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> + <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" - /> + <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 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/(app)/ln/[lnurl]/+page.server.js b/src/routes/(app)/ln/[lnurl]/+page.server.js index ddd714bf..332bc9e3 100644 --- a/src/routes/(app)/ln/[lnurl]/+page.server.js +++ b/src/routes/(app)/ln/[lnurl]/+page.server.js @@ -1,63 +1,75 @@ -import { fd, auth, get, post } from '$lib/utils'; -import { error, fail, redirect } from '@sveltejs/kit'; +import { fd, auth, get, post } from "$lib/utils"; +import { error, fail, redirect } from "@sveltejs/kit"; export async function load({ params, parent }) { - let { user } = await parent(); - if (!user) throw redirect(307, '/register'); + let { user } = await parent(); + if (!user) throw redirect(307, "/register"); - let data; + let data; - try { - let { lnurl } = params; - data = await get(`/decode?text=${lnurl}`); - } catch (e) { - throw error(500, e.message); - } + try { + let { lnurl } = params; + data = await get(`/decode?text=${lnurl}`); + } catch (e) { + throw error(500, e.message); + } - if (!['payRequest', 'withdrawRequest'].includes(data.tag)) - throw error(500, 'We only support LNURLp and LNURLw at this time'); + if (!["payRequest", "withdrawRequest"].includes(data.tag)) + throw error(500, "We only support LNURLp and LNURLw at this time"); - return data; + return data; } export const actions = { - pay: async ({ cookies, fetch, request }) => { - let error; + pay: async ({ cookies, fetch, request }) => { + let error; - let { callback, amount, minSendable, maxSendable } = await fd(request); - minSendable = Math.round(minSendable / 1000); - maxSendable = Math.round(maxSendable / 1000); + let { callback, amount, minSendable, maxSendable } = await fd(request); + minSendable = Math.round(minSendable / 1000); + maxSendable = Math.round(maxSendable / 1000); - if (amount < minSendable) error = `Amount must be at least ${minSendable} sats`; - if (amount > maxSendable) error = `Amount must be at most ${maxSendable} sats`; - if (error) return fail(400, { error }); + if (amount < minSendable) + error = `Amount must be at least ${minSendable} sats`; + if (amount > maxSendable) + error = `Amount must be at most ${maxSendable} sats`; + if (error) return fail(400, { error }); - let { pr } = await fetch(`${callback}?amount=${amount * 1000}`).then((r) => r.json()); - throw redirect(307, `/send/lightning/${pr}`); - }, + let { pr } = await fetch(`${callback}?amount=${amount * 1000}`).then((r) => + r.json() + ); + throw redirect(307, `/send/lightning/${pr}`); + }, - withdraw: async ({ cookies, fetch, request }) => { - let error; + withdraw: async ({ cookies, fetch, request }) => { + let error; - let { callback, amount, username, currency, minWithdrawable, maxWithdrawable, k1 } = await fd( - request - ); + let { + callback, + amount, + username, + currency, + minWithdrawable, + maxWithdrawable, + k1, + } = await fd(request); - minWithdrawable = Math.round(minWithdrawable / 1000); - maxWithdrawable = Math.round(maxWithdrawable / 1000); + minWithdrawable = Math.round(minWithdrawable / 1000); + maxWithdrawable = Math.round(maxWithdrawable / 1000); - if (amount < minWithdrawable) error = `Amount must be at least ${minWithdrawable} sats`; - if (amount > maxWithdrawable) error = `Amount must be at most ${maxWithdrawable} sats`; - if (error) return fail(400, { error }); + if (amount < minWithdrawable) + error = `Amount must be at least ${minWithdrawable} sats`; + if (amount > maxWithdrawable) + error = `Amount must be at most ${maxWithdrawable} sats`; + if (error) return fail(400, { error }); - let invoice = { amount, type: 'lightning' }; - let user = { username, currency }; + let invoice = { amount, type: "lightning" }; + let user = { username, currency }; - let { text: pr } = await post('/invoice', { invoice, user }, auth(cookies)); + let { text: pr } = await post("/invoice", { invoice, user }, auth(cookies)); - let c = callback.includes('?') ? '&' : '?'; - await fetch(`${callback}${c}k1=${k1}&pr=${pr}`).then((r) => r.json()); + let c = callback.includes("?") ? "&" : "?"; + await fetch(`${callback}${c}k1=${k1}&pr=${pr}`).then((r) => r.json()); - throw redirect(307, `/ln/withdrawal/sent`); - } + throw redirect(307, `/ln/withdrawal/sent`); + }, }; diff --git a/src/routes/(app)/ln/[lnurl]/+page.svelte b/src/routes/(app)/ln/[lnurl]/+page.svelte index 7656c9fb..9b733f89 100644 --- a/src/routes/(app)/ln/[lnurl]/+page.svelte +++ b/src/routes/(app)/ln/[lnurl]/+page.svelte @@ -1,11 +1,11 @@ <script> - import { Lnurlp, Lnurlw } from '$comp'; - export let data; - let { tag } = data; + import { Lnurlp, Lnurlw } from "$comp"; + export let data; + let { tag } = data; </script> -{#if tag === 'payRequest'} - <Lnurlp {data} /> -{:else if tag === 'withdrawRequest'} - <Lnurlw {data} /> +{#if tag === "payRequest"} + <Lnurlp {data} /> +{:else if tag === "withdrawRequest"} + <Lnurlw {data} /> {/if} diff --git a/src/routes/(app)/ln/withdrawal/sent/+page.svelte b/src/routes/(app)/ln/withdrawal/sent/+page.svelte index 84e6a7e0..c527c017 100644 --- a/src/routes/(app)/ln/withdrawal/sent/+page.svelte +++ b/src/routes/(app)/ln/withdrawal/sent/+page.svelte @@ -1,23 +1,28 @@ <script> - import { scale } from 'svelte/transition'; - import { Icon } from '$comp'; - import { toast } from '@zerodevx/svelte-toast'; - import { t } from '$lib/translations'; + import { scale } from "svelte/transition"; + import { Icon } from "$comp"; + import { toast } from "@zerodevx/svelte-toast"; + import { t } from "$lib/translations"; - export let data; - let { user } = data; - toast.pop(0); + export let data; + let { user } = data; + toast.pop(0); </script> <div class="text-center mt-20 md:mt-0"> - <div class="flex w-full max-w-[200px] mx-auto py-20" in:scale={{ start: 0.5 }}> - <Icon icon="check" style="mx-auto" /> - </div> - <h1 class="text-3xl md:text-4xl font-bold mb-6">Request Sent!</h1> + <div + class="flex w-full max-w-[200px] mx-auto py-20" + in:scale={{ start: 0.5 }} + > + <Icon icon="check" style="mx-auto" /> + </div> + <h1 class="text-3xl md:text-4xl font-bold mb-6">Request Sent!</h1> </div> <a href={`/${user.username}/payments`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> </a> -<div class="fixed bottom-10 w-full text-center">{$t('payments.tapAnywhere')}</div> +<div class="fixed bottom-10 w-full text-center"> + {$t("payments.tapAnywhere")} +</div> diff --git a/src/routes/(app)/locations/+server.js b/src/routes/(app)/locations/+server.js index dd9e984b..8a046475 100644 --- a/src/routes/(app)/locations/+server.js +++ b/src/routes/(app)/locations/+server.js @@ -1,15 +1,15 @@ -import { get } from '$lib/utils'; -import { json } from '@sveltejs/kit'; +import { get } from "$lib/utils"; +import { json } from "@sveltejs/kit"; export async function GET({ fetch, setHeaders }) { - let locations = []; + let locations = []; - try { - ({ locations } = await get('/locations')); - setHeaders({ 'cache-control': 'public, max-age=600' }); - } catch (e) { - console.log('failed to fetch locations'); - } + try { + ({ locations } = await get("/locations")); + setHeaders({ "cache-control": "public, max-age=600" }); + } catch (e) { + console.log("failed to fetch locations"); + } - return json({ locations }); + return json({ locations }); } diff --git a/src/routes/(app)/login/+page.server.js b/src/routes/(app)/login/+page.server.js index a9cf867b..6876c93d 100644 --- a/src/routes/(app)/login/+page.server.js +++ b/src/routes/(app)/login/+page.server.js @@ -1,25 +1,29 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { login, fd } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { login, fd } from "$lib/utils"; export const load = async ({ parent }) => { - let { user } = await parent(); - if (user?.pubkey) throw redirect(307, `/${user.username}`); + let { user } = await parent(); + if (user?.pubkey) throw redirect(307, `/${user.username}`); }; export const actions = { - default: async ({ cookies, request }) => { - let form = await fd(request); - let { username, password, token, loginRedirect } = form; - let user = { username, password, token }; + default: async ({ cookies, request }) => { + let form = await fd(request); + let { username, password, token, loginRedirect } = form; + let user = { username, password, token }; - if (loginRedirect === 'undefined') loginRedirect = undefined; + if (loginRedirect === "undefined") loginRedirect = undefined; - try { - let r = await login(user, cookies, request.headers.get('cf-connecting-ip')); - } catch (e) { - return fail(400, { error: 'Login failed', message: e.message, ...form }); - } + try { + let r = await login( + user, + cookies, + request.headers.get("cf-connecting-ip") + ); + } catch (e) { + return fail(400, { error: "Login failed", message: e.message, ...form }); + } - throw redirect(303, loginRedirect || `/${user.username}`); - } + throw redirect(303, loginRedirect || `/${user.username}`); + }, }; diff --git a/src/routes/(app)/login/+page.svelte b/src/routes/(app)/login/+page.svelte index 84ecc3ad..6190ae0d 100644 --- a/src/routes/(app)/login/+page.svelte +++ b/src/routes/(app)/login/+page.svelte @@ -1,16 +1,16 @@ <script> - import { RegisterOrLogin } from '$comp'; - import { onMount } from 'svelte'; - import { animatedRate, pin } from '$lib/store'; + import { RegisterOrLogin } from "$comp"; + import { onMount } from "svelte"; + import { animatedRate, pin } from "$lib/store"; - onMount(() => { - $animatedRate = 0; - $pin = undefined; - localStorage.clear(); - sessionStorage.clear(); - }); + onMount(() => { + $animatedRate = 0; + $pin = undefined; + localStorage.clear(); + sessionStorage.clear(); + }); - export let form; + export let form; </script> <RegisterOrLogin pageID="signIn" bind:form /> diff --git a/src/routes/(app)/logout/+page.server.js b/src/routes/(app)/logout/+page.server.js index 2dabc9d4..60750dfa 100644 --- a/src/routes/(app)/logout/+page.server.js +++ b/src/routes/(app)/logout/+page.server.js @@ -1,12 +1,12 @@ -import { redirect } from '@sveltejs/kit'; +import { redirect } from "@sveltejs/kit"; const opts = { - expires: new Date(0), - path: '/' + expires: new Date(0), + path: "/", }; export async function load({ cookies }) { - cookies.set('username', '', opts); - cookies.set('token', '', opts); - throw redirect(307, '/login'); + cookies.set("username", "", opts); + cookies.set("token", "", opts); + throw redirect(307, "/login"); } diff --git a/src/routes/(app)/otpsecret/+server.js b/src/routes/(app)/otpsecret/+server.js index 7d5c1ad9..63d06849 100644 --- a/src/routes/(app)/otpsecret/+server.js +++ b/src/routes/(app)/otpsecret/+server.js @@ -1,15 +1,17 @@ -import { auth, post } from '$lib/utils'; -import Qr from 'qrcode-base64'; -import { error } from '@sveltejs/kit'; +import { auth, post } from "$lib/utils"; +import Qr from "qrcode-base64"; +import { error } from "@sveltejs/kit"; export async function POST({ cookies, request }) { - try { - let body = await request.json(); - let { username, secret } = await post('/otpsecret', body, auth(cookies)); - let uri = `otpauth://totp/coinos:${username}?secret=${secret}&period=30&digits=6&algorithm=SHA1&issuer=coinos`; - return new Response(JSON.stringify({ secret, uri, qr: Qr.drawImg(uri, { size: 600 }) })); - } catch (e) { - if (e.message.includes('pin')) throw e; - throw new error(500, 'Problem saving secret'); - } + try { + let body = await request.json(); + let { username, secret } = await post("/otpsecret", body, auth(cookies)); + let uri = `otpauth://totp/coinos:${username}?secret=${secret}&period=30&digits=6&algorithm=SHA1&issuer=coinos`; + return new Response( + JSON.stringify({ secret, uri, qr: Qr.drawImg(uri, { size: 600 }) }) + ); + } catch (e) { + if (e.message.includes("pin")) throw e; + throw new error(500, "Problem saving secret"); + } } diff --git a/src/routes/(app)/password/+server.js b/src/routes/(app)/password/+server.js index b4a81740..26ed3858 100644 --- a/src/routes/(app)/password/+server.js +++ b/src/routes/(app)/password/+server.js @@ -1,7 +1,7 @@ -import { json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { json } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - let result = await post('/password', await request.json(), auth(cookies)); - return json(result); + let result = await post("/password", await request.json(), auth(cookies)); + return json(result); } diff --git a/src/routes/(app)/payment/[id]/+page.server.js b/src/routes/(app)/payment/[id]/+page.server.js index 1c90ab69..5a364c65 100644 --- a/src/routes/(app)/payment/[id]/+page.server.js +++ b/src/routes/(app)/payment/[id]/+page.server.js @@ -1,6 +1,6 @@ -import { auth, get } from '$lib/utils'; +import { auth, get } from "$lib/utils"; export async function load({ cookies, params }) { - let payment = await get(`/payments/${params.id}`, auth(cookies)); - return { payment }; + let payment = await get(`/payments/${params.id}`, auth(cookies)); + return { payment }; } diff --git a/src/routes/(app)/payment/[id]/+page.svelte b/src/routes/(app)/payment/[id]/+page.svelte index 98c5a68b..7e3c6792 100644 --- a/src/routes/(app)/payment/[id]/+page.svelte +++ b/src/routes/(app)/payment/[id]/+page.svelte @@ -1,158 +1,173 @@ <script> - import { t } from '$lib/translations'; - import { back, copy, f, s, fiat, post, sats, success, types } from '$lib/utils'; - import { Avatar, Icon } from '$comp'; - import { format } from 'date-fns'; - import { PUBLIC_EXPLORER as expl } from '$env/static/public'; + import { t } from "$lib/translations"; + import { + back, + copy, + f, + s, + fiat, + post, + sats, + success, + types, + } from "$lib/utils"; + import { Avatar, Icon } from "$comp"; + import { format } from "date-fns"; + import { PUBLIC_EXPLORER as expl } from "$env/static/public"; - export let data; - let { user, payment: p } = data; + export let data; + let { user, payment: p } = data; - let { username } = user; - let { id, amount, created, rate, type, ref, tip, ourfee, fee, currency } = p; - let a = Math.abs(amount); + let { username } = user; + let { id, amount, created, rate, type, ref, tip, ourfee, fee, currency } = p; + let a = Math.abs(amount); - let print = async () => { - await post(`/payment/${id}/print`, { id }); - success('Printing!'); - }; + let print = async () => { + await post(`/payment/${id}/print`, { id }); + success("Printing!"); + }; - fee = fee || 0; + fee = fee || 0; - let direction = amount > 0 ? $t('payments.from') : $t('payments.to') - direction = direction[0].toUpperCase() + direction.substr(1, direction.length); + let direction = amount > 0 ? $t("payments.from") : $t("payments.to"); + direction = + direction[0].toUpperCase() + direction.substr(1, direction.length); </script> <a href={`/${username}/payments`}> - <button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80"> - <Icon icon="arrow-left" style="w-10" /> - </button> + <button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80"> + <Icon icon="arrow-left" style="w-10" /> + </button> </a> <div class="container mx-auto max-w-lg px-4 space-y-8 break-all text-2xl"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> - {$t(amount < 0 ? 'payments.sent' : 'payments.received')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> + {$t(amount < 0 ? "payments.sent" : "payments.received")} + </h1> - {#if p.with} - <div> - <span class="text-lg text-secondary my-auto mr-2">{direction}</span> - <a href={`/${p.with.username}`}> - <div class="flex"> - <div class="my-auto"> - <Avatar user={p.with} size={20} /> - </div> - <div class="my-auto ml-1">{p.with.username}</div> - </div> - </a> - </div> - {/if} - {#if p.type === types.fund} - <div> - <span class="text-lg text-secondary my-auto mr-2">{direction}</span> - <a href={`/fund/${p.memo}`}> - <div class="flex"> - <div class="my-auto"> - <img src="/images/moneypot.png" class="w-12" alt="Pot" /> - </div> - <div class="my-auto ml-1">{p.memo}</div> - </div> - </a> - </div> - {/if} - <div> - <span class="text-lg text-secondary">{$t('payments.amount')}</span> - <div> - {f(fiat(a, rate), currency)} - <span class="text-secondary text-lg"> - ⚡️{s(a)} - </span> - </div> - </div> + {#if p.with} + <div> + <span class="text-lg text-secondary my-auto mr-2">{direction}</span> + <a href={`/${p.with.username}`}> + <div class="flex"> + <div class="my-auto"> + <Avatar user={p.with} size={20} /> + </div> + <div class="my-auto ml-1">{p.with.username}</div> + </div> + </a> + </div> + {/if} + {#if p.type === types.fund} + <div> + <span class="text-lg text-secondary my-auto mr-2">{direction}</span> + <a href={`/fund/${p.memo}`}> + <div class="flex"> + <div class="my-auto"> + <img src="/images/moneypot.png" class="w-12" alt="Pot" /> + </div> + <div class="my-auto ml-1">{p.memo}</div> + </div> + </a> + </div> + {/if} + <div> + <span class="text-lg text-secondary">{$t("payments.amount")}</span> + <div> + {f(fiat(a, rate), currency)} + <span class="text-secondary text-lg"> + ⚡️{s(a)} + </span> + </div> + </div> - {#if tip} - <div> - <span class="text-lg text-secondary">{$t('invoice.tip')}</span> - <div> - {f(fiat(tip, rate), currency)} - ({Math.round((tip * 100) / Math.abs(a))}%) - <span class="text-secondary text-lg"> - ⚡️{s(tip)} - </span> - </div> - </div> + {#if tip} + <div> + <span class="text-lg text-secondary">{$t("invoice.tip")}</span> + <div> + {f(fiat(tip, rate), currency)} + ({Math.round((tip * 100) / Math.abs(a))}%) + <span class="text-secondary text-lg"> + ⚡️{s(tip)} + </span> + </div> + </div> - <div> - <span class="text-lg text-secondary">Total</span> - <div> - {f(fiat(a + (tip || 0), rate), currency)} - <span class="text-secondary text-lg">⚡️{`${s(a + (tip || 0))}`} </span> - </div> - </div> - {/if} + <div> + <span class="text-lg text-secondary">Total</span> + <div> + {f(fiat(a + (tip || 0), rate), currency)} + <span class="text-secondary text-lg">⚡️{`${s(a + (tip || 0))}`} </span> + </div> + </div> + {/if} - <div> - <span class="text-lg text-secondary">Network fee</span> - <div> - {f(fiat(fee, rate), currency)} - <span class="text-secondary text-lg">⚡️{`${s(fee)}`}</span> - </div> - </div> + <div> + <span class="text-lg text-secondary">Network fee</span> + <div> + {f(fiat(fee, rate), currency)} + <span class="text-secondary text-lg">⚡️{`${s(fee)}`}</span> + </div> + </div> - {#if ourfee > 0} - <div> - <span class="text-lg text-secondary">Platform fee</span> - <div> - {f(fiat(ourfee, rate), currency)} - <span class="text-secondary">⚡️{`${s(ourfee)}`}</span> - </div> - </div> - {/if} + {#if ourfee > 0} + <div> + <span class="text-lg text-secondary">Platform fee</span> + <div> + {f(fiat(ourfee, rate), currency)} + <span class="text-secondary">⚡️{`${s(ourfee)}`}</span> + </div> + </div> + {/if} - <div> - <span class="text-lg text-secondary">Date</span> - <div> - {format(new Date(created), 'MMMM d')}, - {format(new Date(created), 'h:mm aaa')} - </div> - </div> + <div> + <span class="text-lg text-secondary">Date</span> + <div> + {format(new Date(created), "MMMM d")}, + {format(new Date(created), "h:mm aaa")} + </div> + </div> - {#if type === 'bitcoin'} - <div> - <span class="text-lg text-secondary">Txid</span> - <div class="flex"> - <div> - <a href={`${expl}/tx/${id}`} target="_blank" rel="noreferrer">{id}</a> - </div> - <button class="flex font-bold hover:opacity-80 mb-auto my-auto" on:click={() => copy(id)} - ><Icon icon="copy" style="ml-2 w-20 my-auto" /> - </button> - </div> - </div> - {/if} + {#if type === "bitcoin"} + <div> + <span class="text-lg text-secondary">Txid</span> + <div class="flex"> + <div> + <a href={`${expl}/tx/${id}`} target="_blank" rel="noreferrer">{id}</a> + </div> + <button + class="flex font-bold hover:opacity-80 mb-auto my-auto" + on:click={() => copy(id)} + ><Icon icon="copy" style="ml-2 w-20 my-auto" /> + </button> + </div> + </div> + {/if} - {#if user.hasprinter} - <div> - <button - class="rounded-full border py-3 px-6 hover:opacity-80 flex w-full md:w-60" - on:click={print} - > - <div class="mx-auto flex"> - <Icon icon="printer" style="my-auto h-6 mr-2" /> - <div class="my-auto mt-1 text-base">{$t('payments.print')}</div> - </div> - </button> - </div> - {:else} - <div> - <a href={`/payment/${id}/plain`}> - <button class="rounded-full border py-3 px-6 hover:opacity-80 flex w-full md:w-60"> - <div class="mx-auto flex"> - <Icon icon="printer" style="my-auto h-6 mr-2" /> - <div class="my-auto mt-1 text-base">{$t('payments.print')}</div> - </div> - </button> - </a> - </div> - {/if} + {#if user.hasprinter} + <div> + <button + class="rounded-full border py-3 px-6 hover:opacity-80 flex w-full md:w-60" + on:click={print} + > + <div class="mx-auto flex"> + <Icon icon="printer" style="my-auto h-6 mr-2" /> + <div class="my-auto mt-1 text-base">{$t("payments.print")}</div> + </div> + </button> + </div> + {:else} + <div> + <a href={`/payment/${id}/plain`}> + <button + class="rounded-full border py-3 px-6 hover:opacity-80 flex w-full md:w-60" + > + <div class="mx-auto flex"> + <Icon icon="printer" style="my-auto h-6 mr-2" /> + <div class="my-auto mt-1 text-base">{$t("payments.print")}</div> + </div> + </button> + </a> + </div> + {/if} </div> diff --git a/src/routes/(app)/payment/[id]/plain/+page.server.js b/src/routes/(app)/payment/[id]/plain/+page.server.js index 1c90ab69..5a364c65 100644 --- a/src/routes/(app)/payment/[id]/plain/+page.server.js +++ b/src/routes/(app)/payment/[id]/plain/+page.server.js @@ -1,6 +1,6 @@ -import { auth, get } from '$lib/utils'; +import { auth, get } from "$lib/utils"; export async function load({ cookies, params }) { - let payment = await get(`/payments/${params.id}`, auth(cookies)); - return { payment }; + let payment = await get(`/payments/${params.id}`, auth(cookies)); + return { payment }; } diff --git a/src/routes/(app)/payment/[id]/plain/+page.svelte b/src/routes/(app)/payment/[id]/plain/+page.svelte index 5ffd1656..27e8d0ed 100644 --- a/src/routes/(app)/payment/[id]/plain/+page.svelte +++ b/src/routes/(app)/payment/[id]/plain/+page.svelte @@ -1,77 +1,87 @@ <script> - import { goto } from '$app/navigation'; - import { onMount } from 'svelte'; - import { browser } from '$app/environment'; - import { t } from '$lib/translations'; - import { back, copy, f, s, fiat, post, sats, success, types } from '$lib/utils'; - import { Avatar, Icon } from '$comp'; - import { format } from 'date-fns'; - import { PUBLIC_EXPLORER as expl } from '$env/static/public'; + import { goto } from "$app/navigation"; + import { onMount } from "svelte"; + import { browser } from "$app/environment"; + import { t } from "$lib/translations"; + import { + back, + copy, + f, + s, + fiat, + post, + sats, + success, + types, + } from "$lib/utils"; + import { Avatar, Icon } from "$comp"; + import { format } from "date-fns"; + import { PUBLIC_EXPLORER as expl } from "$env/static/public"; - export let data; - let { user, payment: p } = data; + export let data; + let { user, payment: p } = data; - let { username } = user; - let { id, amount, created, rate, type, ref, tip, ourfee, fee, currency } = p; - let a = Math.abs(amount); + let { username } = user; + let { id, amount, created, rate, type, ref, tip, ourfee, fee, currency } = p; + let a = Math.abs(amount); - onMount(() => { - if (browser) { - let main = document.querySelector('main'); - main.style.paddingBottom = '0'; - window.onfocus = () => goto(`/payment/${id}`); - window.print(); - } - }); + onMount(() => { + if (browser) { + let main = document.querySelector("main"); + main.style.paddingBottom = "0"; + window.onfocus = () => goto(`/payment/${id}`); + window.print(); + } + }); - fee = fee || 0; + fee = fee || 0; </script> <div class="space-y-5"> - <div> - <span class="text-lg text-secondary">Amount</span> - <div> - {f(fiat(a, rate), currency)} - </div> - </div> + <div> + <span class="text-lg text-secondary">Amount</span> + <div> + {f(fiat(a, rate), currency)} + </div> + </div> - {#if tip} - <div> - <span class="text-lg text-secondary">Tip</span> - <div> - {f(fiat(tip, rate), currency)} - ({Math.round((tip * 100) / Math.abs(a))}%) - </div> - </div> + {#if tip} + <div> + <span class="text-lg text-secondary">Tip</span> + <div> + {f(fiat(tip, rate), currency)} + ({Math.round((tip * 100) / Math.abs(a))}%) + </div> + </div> - <div> - <span class="text-lg text-secondary">Total</span> - <div> - {f(fiat(a + (tip || 0), rate), currency)} - </div> - </div> - {/if} + <div> + <span class="text-lg text-secondary">Total</span> + <div> + {f(fiat(a + (tip || 0), rate), currency)} + </div> + </div> + {/if} - <div> - <span class="text-lg text-secondary">Date</span> - <div> - {format(new Date(created), 'MMMM d')}, - {format(new Date(created), 'h:mm aaa')} - </div> - </div> + <div> + <span class="text-lg text-secondary">Date</span> + <div> + {format(new Date(created), "MMMM d")}, + {format(new Date(created), "h:mm aaa")} + </div> + </div> - {#if type === 'bitcoin'} - <div> - <span class="text-lg text-secondary">Txid</span> - <div class="flex"> - <div> - {id} - </div> - </div> - </div> - {/if} + {#if type === "bitcoin"} + <div> + <span class="text-lg text-secondary">Txid</span> + <div class="flex"> + <div> + {id} + </div> + </div> + </div> + {/if} </div> <a href={`/payment/${id}`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> </a> diff --git a/src/routes/(app)/payment/[id]/print/+server.js b/src/routes/(app)/payment/[id]/print/+server.js index fde834d1..2fdfc8c1 100644 --- a/src/routes/(app)/payment/[id]/print/+server.js +++ b/src/routes/(app)/payment/[id]/print/+server.js @@ -1,7 +1,7 @@ -import { auth, post } from '$lib/utils'; -import { json } from '@sveltejs/kit'; +import { auth, post } from "$lib/utils"; +import { json } from "@sveltejs/kit"; export async function POST({ cookies, request }) { - let body = await request.json(); - return json(post('/print', body, auth(cookies))); + let body = await request.json(); + return json(post("/print", body, auth(cookies))); } diff --git a/src/routes/(app)/pin/+server.js b/src/routes/(app)/pin/+server.js index 9f63f545..76d407bf 100644 --- a/src/routes/(app)/pin/+server.js +++ b/src/routes/(app)/pin/+server.js @@ -1,7 +1,7 @@ -import { json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { json } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - let result = await post('/pin', await request.json(), auth(cookies)); - return json(result); + let result = await post("/pin", await request.json(), auth(cookies)); + return json(result); } diff --git a/src/routes/(app)/qr/[text]/+page.server.js b/src/routes/(app)/qr/[text]/+page.server.js index 3ab0725f..5eaafe57 100644 --- a/src/routes/(app)/qr/[text]/+page.server.js +++ b/src/routes/(app)/qr/[text]/+page.server.js @@ -1,8 +1,8 @@ -import Qr from 'qrcode-base64'; +import Qr from "qrcode-base64"; export async function load({ params }) { - let { text } = params; - let src = Qr.drawImg(text, { size: 600 }); + let { text } = params; + let src = Qr.drawImg(text, { size: 600 }); - return { src, text }; + return { src, text }; } diff --git a/src/routes/(app)/qr/[text]/+page.svelte b/src/routes/(app)/qr/[text]/+page.svelte index f3cd791a..d943b0b5 100644 --- a/src/routes/(app)/qr/[text]/+page.svelte +++ b/src/routes/(app)/qr/[text]/+page.svelte @@ -1,24 +1,24 @@ <script> - import { Icon, Qr } from '$comp'; + import { Icon, Qr } from "$comp"; import { back } from "$lib/utils"; - export let data; - let { src, text } = data; + export let data; + let { src, text } = data; </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> - <div class="container px-4 max-w-4xl mx-auto mt-10 space-y-5"> - <div class="flex justify-center items-center"> - <div class="md:shadow-xl rounded-3xl md:px-10 pt-5 pb-10 space-y-5 w-full md:mx-5"> + <div class="flex justify-center items-center"> + <div + class="md:shadow-xl rounded-3xl md:px-10 pt-5 pb-10 space-y-5 w-full md:mx-5" + > <Qr {src} /> <div class="text-center w-full text-2xl py-10 break-all"> {text} </div> - </div> - </div> + </div> + </div> </div> diff --git a/src/routes/(app)/redeem/[redeemcode]/+page.server.js b/src/routes/(app)/redeem/[redeemcode]/+page.server.js index 4f685d68..f227f22e 100644 --- a/src/routes/(app)/redeem/[redeemcode]/+page.server.js +++ b/src/routes/(app)/redeem/[redeemcode]/+page.server.js @@ -1,17 +1,17 @@ -import { error, redirect } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { error, redirect } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export async function load({ cookies, locals, params }) { - let { user } = locals; + let { user } = locals; - if (user) { - try { - await post('/redeem', params, auth(cookies)); - } catch (e) { - throw redirect(307, '/'); - } - throw redirect(307, `/${user.username}/payments`); - } + if (user) { + try { + await post("/redeem", params, auth(cookies)); + } catch (e) { + throw redirect(307, "/"); + } + throw redirect(307, `/${user.username}/payments`); + } - throw redirect(307, '/login'); + throw redirect(307, "/login"); } diff --git a/src/routes/(app)/redeem/[redeemcode]/+page.svelte b/src/routes/(app)/redeem/[redeemcode]/+page.svelte index a4609307..b2fcf2f4 100644 --- a/src/routes/(app)/redeem/[redeemcode]/+page.svelte +++ b/src/routes/(app)/redeem/[redeemcode]/+page.svelte @@ -1,4 +1,4 @@ <script> - import { loginRedirect } from '$lib/store'; - $loginRedirect = undefined; + import { loginRedirect } from "$lib/store"; + $loginRedirect = undefined; </script> diff --git a/src/routes/(app)/register/+page.server.js b/src/routes/(app)/register/+page.server.js index 45344907..e986c73d 100644 --- a/src/routes/(app)/register/+page.server.js +++ b/src/routes/(app)/register/+page.server.js @@ -1,41 +1,42 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { fd, post, login } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { fd, post, login } from "$lib/utils"; export const load = async ({ parent }) => { - let { user } = await parent(); - if (user) throw redirect(307, `/${user.username}`); + let { user } = await parent(); + if (user) throw redirect(307, `/${user.username}`); - let index = Math.floor(Math.random() * 64) + 1; + let index = Math.floor(Math.random() * 64) + 1; - return { index }; + return { index }; }; export const actions = { - default: async ({ cookies, request }) => { - let ip = request.headers.get('cf-connecting-ip'); + default: async ({ cookies, request }) => { + let ip = request.headers.get("cf-connecting-ip"); - let form = await fd(request); - let { profile, username, password, cipher, salt, pubkey, loginRedirect } = form; - let user = { profile, username, password, cipher, salt, pubkey }; - let error; + let form = await fd(request); + let { profile, username, password, cipher, salt, pubkey, loginRedirect } = + form; + let user = { profile, username, password, cipher, salt, pubkey }; + let error; - if (loginRedirect === 'undefined') loginRedirect = undefined; + if (loginRedirect === "undefined") loginRedirect = undefined; - try { - await post('/register', { user }, { 'cf-connecting-ip': ip }); - } catch (e) { - console.log(e); - if (e.message.includes('taken')) error = e.message; - } + try { + await post("/register", { user }, { "cf-connecting-ip": ip }); + } catch (e) { + console.log(e); + if (e.message.includes("taken")) error = e.message; + } - try { - await login(user, cookies, ip); - error = null; - } catch (e) { - error ||= e.message; - } + try { + await login(user, cookies, ip); + error = null; + } catch (e) { + error ||= e.message; + } - if (error) return fail(400, { error }); - throw redirect(307, loginRedirect || `/${user.username}`); - } + if (error) return fail(400, { error }); + throw redirect(307, loginRedirect || `/${user.username}`); + }, }; diff --git a/src/routes/(app)/register/+page.svelte b/src/routes/(app)/register/+page.svelte index a9f9d047..8310aed1 100644 --- a/src/routes/(app)/register/+page.svelte +++ b/src/routes/(app)/register/+page.svelte @@ -1,287 +1,311 @@ <script> - import punks from '$lib/punks'; - import { upload } from '$lib/upload'; - import { afterNavigate, invalidateAll } from '$app/navigation'; - import { applyAction, deserialize } from '$app/forms'; - import { tick } from 'svelte'; - import { fly } from 'svelte/transition'; - import { enhance } from '$app/forms'; - import { Pin, Icon, Spinner } from '$comp'; - import { focus, fail } from '$lib/utils'; - import { avatar, password, loginRedirect } from '$lib/store'; - import { t } from '$lib/translations'; - import { page } from '$app/stores'; - import { generate } from '$lib/nostr'; - import { - NumberDictionary, - uniqueNamesGenerator, - colors, - adjectives, - animals - } from 'unique-names-generator'; + import punks from "$lib/punks"; + import { upload } from "$lib/upload"; + import { afterNavigate, invalidateAll } from "$app/navigation"; + import { applyAction, deserialize } from "$app/forms"; + import { tick } from "svelte"; + import { fly } from "svelte/transition"; + import { enhance } from "$app/forms"; + import { Pin, Icon, Spinner } from "$comp"; + import { focus, fail } from "$lib/utils"; + import { avatar, password, loginRedirect } from "$lib/store"; + import { t } from "$lib/translations"; + import { page } from "$app/stores"; + import { generate } from "$lib/nostr"; + import { + NumberDictionary, + uniqueNamesGenerator, + colors, + adjectives, + animals, + } from "unique-names-generator"; - export let form; - export let data; + export let form; + export let data; - $: data && ($avatar = undefined); - let username; - let { index } = data; + $: data && ($avatar = undefined); + let username; + let { index } = data; - let cleared; - let clear = () => { - if (!cleared) { - cleared = true; - username = ''; - $password = ''; - revealPassword = false; - } - }; + let cleared; + let clear = () => { + if (!cleared) { + cleared = true; + username = ""; + $password = ""; + revealPassword = false; + } + }; - let refresh = async () => { - username = uniqueNamesGenerator({ - dictionaries: [animals, NumberDictionary.generate({ min: 10, max: 99 })], - length: 2, - separator: '' - }); + let refresh = async () => { + username = uniqueNamesGenerator({ + dictionaries: [animals, NumberDictionary.generate({ min: 10, max: 99 })], + length: 2, + separator: "", + }); - $password = uniqueNamesGenerator({ - dictionaries: [colors, NumberDictionary.generate({ min: 100, max: 999 })], - length: 2, - separator: '' - }); + $password = uniqueNamesGenerator({ + dictionaries: [colors, NumberDictionary.generate({ min: 100, max: 999 })], + length: 2, + separator: "", + }); - revealPassword = true; - }; + revealPassword = true; + }; - afterNavigate(async () => { - try { - await invalidateAll(); - } catch (e) { - console.log(e); - } - }); + afterNavigate(async () => { + try { + await invalidateAll(); + } catch (e) { + console.log(e); + } + }); - let token, formElement; - let code = []; - let redirect; + let token, formElement; + let code = []; + let redirect; - $: need2fa = form?.message === '2fa'; - let cancel = () => (need2fa = false); - $: if (form?.message === '2fa' && form.token === token) token = ''; + $: need2fa = form?.message === "2fa"; + let cancel = () => (need2fa = false); + $: if (form?.message === "2fa" && form.token === token) token = ""; - let email, btn; + let email, btn; - $: update(form); - let update = (form) => form && ({ username, password: $password } = form); + $: update(form); + let update = (form) => form && ({ username, password: $password } = form); - $: token && token?.length === 6 && tick().then(() => btn.click()); + $: token && token?.length === 6 && tick().then(() => btn.click()); - let revealPassword; + let revealPassword; - let loading; - async function handleSubmit(e) { - loading = true; + let loading; + async function handleSubmit(e) { + loading = true; - let data = new FormData(this); - let user = Object.fromEntries(data); - await generate(user); + let data = new FormData(this); + let user = Object.fromEntries(data); + await generate(user); - for (let k in user) { - data.set(k, user[k]); - } + for (let k in user) { + data.set(k, user[k]); + } - data.set('profile', punks[index]); - if ($avatar) { - try { - let { hash } = JSON.parse( - await upload($avatar.file, $avatar.type, $avatar.progress, token) - ); + data.set("profile", punks[index]); + if ($avatar) { + try { + let { hash } = JSON.parse( + await upload($avatar.file, $avatar.type, $avatar.progress, token) + ); - data.set('profile', hash); - await fetch(`/api/public/${hash}.webp`, { cache: 'reload', mode: 'no-cors' }); - } catch (e) { - console.log('problem uploading avatar', e); - } - } + data.set("profile", hash); + await fetch(`/api/public/${hash}.webp`, { + cache: "reload", + mode: "no-cors", + }); + } catch (e) { + console.log("problem uploading avatar", e); + } + } - const response = await fetch('/register', { - method: 'POST', - body: data - }); + const response = await fetch("/register", { + method: "POST", + body: data, + }); - const result = deserialize(await response.text()); + const result = deserialize(await response.text()); - if (result.type === 'success') { - await invalidateAll(); - } + if (result.type === "success") { + await invalidateAll(); + } - applyAction(result); - loading = false; - } + applyAction(result); + loading = false; + } - let avatarInput; - $: src = `/api/public/${punks[index]}.webp`; - let decr = () => (index = index <= 0 ? 63 : index - 1); - let incr = () => (index = index >= 63 ? 0 : index + 1); - let selectAvatar = () => avatarInput.click(); + let avatarInput; + $: src = `/api/public/${punks[index]}.webp`; + let decr = () => (index = index <= 0 ? 63 : index - 1); + let incr = () => (index = index >= 63 ? 0 : index + 1); + let selectAvatar = () => avatarInput.click(); - let progress; - let handleFile = async ({ target }) => { - let type = 'profile'; - let file = target.files[0]; - if (!file) return; + let progress; + let handleFile = async ({ target }) => { + let type = "profile"; + let file = target.files[0]; + if (!file) return; - if (file.size > 10000000) form.error = 'File too large'; - $avatar = { file, type, progress }; + if (file.size > 10000000) form.error = "File too large"; + $avatar = { file, type, progress }; - var reader = new FileReader(); - reader.onload = async (e) => { - src = e.target.result; - }; + var reader = new FileReader(); + reader.onload = async (e) => { + src = e.target.result; + }; - reader.readAsDataURL(file); - }; + reader.readAsDataURL(file); + }; </script> {#if need2fa} - <Pin bind:value={token} title="Enter 2FA Code" {cancel} notify={false} /> + <Pin bind:value={token} title="Enter 2FA Code" {cancel} notify={false} /> {/if} <div class="pt-4"> - <div class="w-[243px] mx-auto"> - <a href="/"> - <Icon icon="logo" /> - </a> - </div> + <div class="w-[243px] mx-auto"> + <a href="/"> + <Icon icon="logo" /> + </a> + </div> - <div class="flex justify-center items-center"> - <div class="shadow-xl rounded-3xl p-4 pt-0 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]"> - <input - type="file" - class="hidden" - bind:this={avatarInput} - on:change={(e) => handleFile(e, 'profile')} - /> + <div class="flex justify-center items-center"> + <div + class="shadow-xl rounded-3xl p-4 pt-0 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]" + > + <input + type="file" + class="hidden" + bind:this={avatarInput} + on:change={(e) => handleFile(e, "profile")} + /> - <div class="relative"> - <button class="absolute w-8 h-12 left-12 bg-white rounded top-12" on:click={decr}> - <Icon icon="chevron-left" style="w-8 " /> - </button> - <div class="relative w-32 mx-auto" on:click={selectAvatar}> - <button - class="w-32 h-32 rounded-full border-4 border-white overflow-hidden flex mx-auto relative" - > - <img - {src} - class="w-full h-full object-cover object-center overflow-hidden" - alt={username} - /> - </button> - <button - class="absolute bg-white rounded-full p-2 mx-auto right-0 bottom-0 z-10 bg-opacity-80" - > - <Icon icon="upload" style="w-8" /> - </button> - </div> - <button class="absolute w-8 h-12 right-12 bg-white rounded top-12" on:click={incr}> - <Icon icon="chevron-left" style="w-8 rotate-180" /> - </button> - </div> + <div class="relative"> + <button + class="absolute w-8 h-12 left-12 bg-white rounded top-12" + on:click={decr} + > + <Icon icon="chevron-left" style="w-8 " /> + </button> + <div class="relative w-32 mx-auto" on:click={selectAvatar}> + <button + class="w-32 h-32 rounded-full border-4 border-white overflow-hidden flex mx-auto relative" + > + <img + {src} + class="w-full h-full object-cover object-center overflow-hidden" + alt={username} + /> + </button> + <button + class="absolute bg-white rounded-full p-2 mx-auto right-0 bottom-0 z-10 bg-opacity-80" + > + <Icon icon="upload" style="w-8" /> + </button> + </div> + <button + class="absolute w-8 h-12 right-12 bg-white rounded top-12" + on:click={incr} + > + <Icon icon="chevron-left" style="w-8 rotate-180" /> + </button> + </div> - {#if form?.error} - <div class="text-red-600 text-center" in:fly> - {form.error} - </div> - {/if} + {#if form?.error} + <div class="text-red-600 text-center" in:fly> + {form.error} + </div> + {/if} - <form class="space-y-5" on:submit|preventDefault={handleSubmit} method="POST"> - <input - type="hidden" - name="loginRedirect" - value={$loginRedirect || $page.url.searchParams.get('redirect')} - /> - <input type="hidden" name="token" value={token} /> + <form + class="space-y-5" + on:submit|preventDefault={handleSubmit} + method="POST" + > + <input + type="hidden" + name="loginRedirect" + value={$loginRedirect || $page.url.searchParams.get("redirect")} + /> + <input type="hidden" name="token" value={token} /> - <div class="relative"> - <label for="username" class="font-semibold">{$t('login.username')}</label> - <input - use:focus - tabindex="1" - name="username" - type="text" - required - class="bg-primary" - bind:value={username} - on:focus={clear} - autocapitalize="none" - /> - <button type="button" on:click={refresh} class="absolute right-0 top-4" tabindex="5"> - <Icon icon="random" style="w-10 m-4" /> - </button> - </div> + <div class="relative"> + <label for="username" class="font-semibold" + >{$t("login.username")}</label + > + <input + use:focus + tabindex="1" + name="username" + type="text" + required + class="bg-primary" + bind:value={username} + on:focus={clear} + autocapitalize="none" + /> + <button + type="button" + on:click={refresh} + class="absolute right-0 top-4" + tabindex="5" + > + <Icon icon="random" style="w-10 m-4" /> + </button> + </div> - <div class="relative"> - <label for="password" class="block font-semibold">{$t('login.password')}</label> - {#if revealPassword} - <input - tabindex="2" - name="password" - type="text" - required - class="bg-primary" - bind:value={$password} - autocapitalize="none" - /> - {:else} - <input - tabindex="2" - name="password" - type="password" - required - class="bg-primary" - bind:value={$password} - autocapitalize="none" - on:focus={clear} - /> - {/if} - <button - type="button" - tabindex="4" - on:click={() => (revealPassword = !revealPassword)} - class="absolute right-0 top-4" - > - <Icon icon={revealPassword ? 'eye' : 'eye-off'} style="m-6" /> - </button> - </div> + <div class="relative"> + <label for="password" class="block font-semibold" + >{$t("login.password")}</label + > + {#if revealPassword} + <input + tabindex="2" + name="password" + type="text" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + /> + {:else} + <input + tabindex="2" + name="password" + type="password" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + on:focus={clear} + /> + {/if} + <button + type="button" + tabindex="4" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-0 top-4" + > + <Icon icon={revealPassword ? "eye" : "eye-off"} style="m-6" /> + </button> + </div> - <p class="text-secondary text-sm"> - {$t('login.passwordRecommendation')} - </p> + <p class="text-secondary text-sm"> + {$t("login.passwordRecommendation")} + </p> - <button - tabindex="3" - type="submit" - class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" - disabled={loading} - bind:this={btn} - > - {#if loading} - <Spinner /> - {:else} - {$t('login.register')} - {/if} - </button> - </form> + <button + tabindex="3" + type="submit" + class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" + disabled={loading} + bind:this={btn} + > + {#if loading} + <Spinner /> + {:else} + {$t("login.register")} + {/if} + </button> + </form> - <p class="text-secondary text-center font-medium"> - {$t('login.haveAccount')} - <a - href={'/login'} - class="block md:inline text-black underline underline-offset-4 hover:opacity-80" - > - {$t('login.signIn')} - </a> - </p> - </div> - </div> + <p class="text-secondary text-center font-medium"> + {$t("login.haveAccount")} + <a + href={"/login"} + class="block md:inline text-black underline underline-offset-4 hover:opacity-80" + > + {$t("login.signIn")} + </a> + </p> + </div> + </div> </div> diff --git a/src/routes/(app)/scan/+page.svelte b/src/routes/(app)/scan/+page.svelte index 8c5ea0a5..033eb28b 100644 --- a/src/routes/(app)/scan/+page.svelte +++ b/src/routes/(app)/scan/+page.svelte @@ -1,44 +1,51 @@ <script> - import { Icon } from '$comp'; - import QrScanner from 'qr-scanner'; - import { onMount, onDestroy } from 'svelte'; - import { back } from '$lib/utils'; - import { goto } from '$app/navigation'; + import { Icon } from "$comp"; + import QrScanner from "qr-scanner"; + import { onMount, onDestroy } from "svelte"; + import { back } from "$lib/utils"; + import { goto } from "$app/navigation"; - let scanner, vid; - onMount(() => { - scanner = new QrScanner(vid, ({ data }) => scanner.stop() || goto(`/send/${encodeURI(data)}`), { - highlightScanRegion: true, - highlightCodeOutline: true - }); - scanner.start(); - }); + let scanner, vid; + onMount(() => { + scanner = new QrScanner( + vid, + ({ data }) => scanner.stop() || goto(`/send/${encodeURI(data)}`), + { + highlightScanRegion: true, + highlightCodeOutline: true, + } + ); + scanner.start(); + }); - onDestroy(() => scanner?.stop()); + onDestroy(() => scanner?.stop()); </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" /> + <Icon icon="arrow-left" style="w-10" /> </button> <div class="flex w-full mb-4 px-4"> - <div class="bg-black mx-auto rounded-3xl"> - <video - bind:this={vid} - class="border-4 rounded-3xl border-black md:max-w-[600px] !max-h-[80vh] !min-w-[300px]" - /> - </div> + <div class="bg-black mx-auto rounded-3xl"> + <video + bind:this={vid} + class="border-4 rounded-3xl border-black md:max-w-[600px] !max-h-[80vh] !min-w-[300px]" + /> + </div> </div> <div class="flex w-full mb-4"> - <button class="mx-auto border rounded-full px-6 py-2 font-bold hover:opacity-80" on:click={back}> - Cancel - </button> + <button + class="mx-auto border rounded-full px-6 py-2 font-bold hover:opacity-80" + on:click={back} + > + Cancel + </button> </div> <style> - video { - transition: transform 0.3s ease-out; - transform: scale(1); - } + video { + transition: transform 0.3s ease-out; + transform: scale(1); + } </style> diff --git a/src/routes/(app)/send/+layout.svelte b/src/routes/(app)/send/+layout.svelte index 47c11f1c..1a587edf 100644 --- a/src/routes/(app)/send/+layout.svelte +++ b/src/routes/(app)/send/+layout.svelte @@ -1,16 +1,16 @@ <script> - import { goto } from '$app/navigation'; - import { Pin } from '$comp'; - import { pin } from '$lib/store'; + import { goto } from "$app/navigation"; + import { Pin } from "$comp"; + import { pin } from "$lib/store"; - export let data; + export let data; - let { user } = data; - let cancel = () => goto(`/${user.username}`); + let { user } = data; + let cancel = () => goto(`/${user.username}`); </script> {#if user.haspin && (!$pin || $pin?.length !== 6)} - <Pin bind:value={$pin} {cancel} /> + <Pin bind:value={$pin} {cancel} /> {/if} <slot /> diff --git a/src/routes/(app)/send/[...text]/+page.server.js b/src/routes/(app)/send/[...text]/+page.server.js index 3ca50bca..9a76f21d 100644 --- a/src/routes/(app)/send/[...text]/+page.server.js +++ b/src/routes/(app)/send/[...text]/+page.server.js @@ -1,100 +1,100 @@ -import { validate } from 'bitcoin-address-validation'; -import bip21 from 'bip21'; -import { auth, get, post } from '$lib/utils'; -import { fail, redirect } from '@sveltejs/kit'; -import { PUBLIC_DOMAIN } from '$env/static/public'; +import { validate } from "bitcoin-address-validation"; +import bip21 from "bip21"; +import { auth, get, post } from "$lib/utils"; +import { fail, redirect } from "@sveltejs/kit"; +import { PUBLIC_DOMAIN } from "$env/static/public"; let parse = async (t, host) => { - if (!t) return; + if (!t) return; - if (t.startsWith('http')) throw redirect(307, t); - if (t.startsWith(host)) throw redirect(307, `http://${t}`); + if (t.startsWith("http")) throw redirect(307, t); + if (t.startsWith(host)) throw redirect(307, `http://${t}`); - let amount, user, id; + let amount, user, id; - t = t.trim(); - t.toLowerCase().startsWith('bitcoin:') && - ({ - address: t, - options: { amount } - } = bip21.decode(t)); + t = t.trim(); + t.toLowerCase().startsWith("bitcoin:") && + ({ + address: t, + options: { amount }, + } = bip21.decode(t)); - if (t.endsWith(`@${PUBLIC_DOMAIN}`)) t = t.split('@')[0]; - if (t.includes('@') && t.includes('.')) { - try { - t = await get(`/encode?address=${t}`); - } catch (e) {} - } + if (t.endsWith(`@${PUBLIC_DOMAIN}`)) t = t.split("@")[0]; + if (t.includes("@") && t.includes(".")) { + try { + t = await get(`/encode?address=${t}`); + } catch (e) {} + } - if (t.endsWith('@classic')) { - ({ uuid: id } = await get(`/invoice/classic/${t.replace('@classic', '')}`)); - if (id) throw redirect(307, `/send/invoice/${id}`); - } + if (t.endsWith("@classic")) { + ({ uuid: id } = await get(`/invoice/classic/${t.replace("@classic", "")}`)); + if (id) throw redirect(307, `/send/invoice/${id}`); + } - if (t.includes('/fund')) throw redirect(307, t.substring(t.indexOf('/fund'))); + if (t.includes("/fund")) throw redirect(307, t.substring(t.indexOf("/fund"))); - if (t.toLowerCase().startsWith('lnurl')) throw redirect(307, `/ln/${t}`); - if (t.includes(':')) t = t.split(':')[1]; + if (t.toLowerCase().startsWith("lnurl")) throw redirect(307, `/ln/${t}`); + if (t.includes(":")) t = t.split(":")[1]; - // lightning - if (t.toLowerCase().startsWith('ln')) { - try { - ({ id } = await get(`/invoice/${t}`)); - } catch (e) { - throw redirect(307, `/send/lightning/${t}`); - } - } + // lightning + if (t.toLowerCase().startsWith("ln")) { + try { + ({ id } = await get(`/invoice/${t}`)); + } catch (e) { + throw redirect(307, `/send/lightning/${t}`); + } + } - // bitcoin - if (validate(t)) { - try { - ({ id, user } = await get(`/invoice/${t}`)); - } catch (e) { - let r = `/send/bitcoin/${t}`; - if (amount) r += '/' + amount; - throw redirect(307, r); - } + // bitcoin + if (validate(t)) { + try { + ({ id, user } = await get(`/invoice/${t}`)); + } catch (e) { + let r = `/send/bitcoin/${t}`; + if (amount) r += "/" + amount; + throw redirect(307, r); + } - if (user) throw redirect(307, `/send/${user.username}`); - else if (id) throw redirect(307, `/send/${id}`); - } + if (user) throw redirect(307, `/send/${user.username}`); + else if (id) throw redirect(307, `/send/${id}`); + } - // user - try { - user = await get(`/users/${t.split('/')[0]}`); - if (user.anon) user = null; - } catch (e) {} + // user + try { + user = await get(`/users/${t.split("/")[0]}`); + if (user.anon) user = null; + } catch (e) {} - if (user) throw redirect(307, `/send/user/${t}`); + if (user) throw redirect(307, `/send/user/${t}`); - let fund; - try { - fund = await get(`/fund/${t}`); - } catch (e) {} + let fund; + try { + fund = await get(`/fund/${t}`); + } catch (e) {} - if (fund) throw redirect(307, `/send/fund/${t}`); + if (fund) throw redirect(307, `/send/fund/${t}`); - // invoice - let invoice; - try { - invoice = await get(`/invoice/${t}`); - } catch (e) {} + // invoice + let invoice; + try { + invoice = await get(`/invoice/${t}`); + } catch (e) {} - if (invoice) throw redirect(307, `/send/invoice/${invoice.hash}`); + if (invoice) throw redirect(307, `/send/invoice/${invoice.hash}`); }; export async function load({ cookies, params, request, url }) { - let { text } = params; - await parse(text, url.host); - let contacts = await get('/contacts', auth(cookies)); - return { contacts }; + let { text } = params; + await parse(text, url.host); + let contacts = await get("/contacts", auth(cookies)); + return { contacts }; } export const actions = { - default: async ({ request, url }) => { - const form = await request.formData(); - let t = form.get('text'); - await parse(t, url.host); - return fail(400, { error: true }); - } + default: async ({ request, url }) => { + const form = await request.formData(); + let t = form.get("text"); + await parse(t, url.host); + return fail(400, { error: true }); + }, }; diff --git a/src/routes/(app)/send/[...text]/+page.svelte b/src/routes/(app)/send/[...text]/+page.svelte index 74afd846..67d4afaa 100644 --- a/src/routes/(app)/send/[...text]/+page.svelte +++ b/src/routes/(app)/send/[...text]/+page.svelte @@ -1,125 +1,129 @@ <script> - import { page } from '$app/stores'; - import { fly } from 'svelte/transition'; - import { enhance } from '$app/forms'; - import { tick } from 'svelte'; - import { browser } from '$app/environment'; - import { t } from '$lib/translations'; - import { AppHeader, Avatar, Icon, Spinner } from '$comp'; - import { back, fail } from '$lib/utils'; + import { page } from "$app/stores"; + import { fly } from "svelte/transition"; + import { enhance } from "$app/forms"; + import { tick } from "svelte"; + import { browser } from "$app/environment"; + import { t } from "$lib/translations"; + import { AppHeader, Avatar, Icon, Spinner } from "$comp"; + import { back, fail } from "$lib/utils"; - export let data; - export let form; - data.subject = data.user; + export let data; + export let form; + data.subject = data.user; - let { contacts } = data; - let w; + let { contacts } = data; + let w; - let el, textarea, text; - let placeholder = $t('user.send.placeholder'); + let el, textarea, text; + let placeholder = $t("user.send.placeholder"); - $: $page && setTimeout(() => w > 800 && textarea?.focus(), 0); - let keypress = (e) => e.key === 'Enter' && (e.preventDefault() || el.click()); + $: $page && setTimeout(() => w > 800 && textarea?.focus(), 0); + let keypress = (e) => e.key === "Enter" && (e.preventDefault() || el.click()); - let paste = async () => { - text = await navigator.clipboard.readText(); - }; + let paste = async () => { + text = await navigator.clipboard.readText(); + }; </script> <svelte:window bind:innerWidth={w} /> <AppHeader {data} /> <div class="container px-4 max-w-lg mx-auto space-y-5 mt-20"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> - {$t('payments.send')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> + {$t("payments.send")} + </h1> - <form method="POST" use:enhance> - {#if form?.error} - <div class="mb-5"> - <div class="text-red-600"> - {$t('error.unrecognizedInput')} - </div> - </div> - {/if} + <form method="POST" use:enhance> + {#if form?.error} + <div class="mb-5"> + <div class="text-red-600"> + {$t("error.unrecognizedInput")} + </div> + </div> + {/if} - <div class="mb-2"> - <input type="hidden" name="text" bind:value={text} /> + <div class="mb-2"> + <input type="hidden" name="text" bind:value={text} /> - <textarea - {placeholder} - on:keypress={keypress} - class="w-full p-4 border rounded-xl h-48" - bind:value={text} - bind:this={textarea} - /> - </div> + <textarea + {placeholder} + on:keypress={keypress} + class="w-full p-4 border rounded-xl h-48" + bind:value={text} + bind:this={textarea} + /> + </div> - <div class="flex justify-end"> - {#if text?.length} - <a href={`/qr/${encodeURIComponent(text)}`}> - <button - type="button" - class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" - > - <Icon icon="qr" style="mr-2 w-6 my-auto invert" /> - <div class="my-auto">{$t('user.send.makeQR')}</div> - </button> - </a> - {/if} + <div class="flex justify-end"> + {#if text?.length} + <a href={`/qr/${encodeURIComponent(text)}`}> + <button + type="button" + class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" + > + <Icon icon="qr" style="mr-2 w-6 my-auto invert" /> + <div class="my-auto">{$t("user.send.makeQR")}</div> + </button> + </a> + {/if} - <a href="/scan"> - <button - type="button" - class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" - > - <Icon icon="camera" style="mr-2 w-6 my-auto" /> - <div class="my-auto">{$t('user.send.scan')}</div> - </button> - </a> + <a href="/scan"> + <button + type="button" + class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" + > + <Icon icon="camera" style="mr-2 w-6 my-auto" /> + <div class="my-auto">{$t("user.send.scan")}</div> + </button> + </a> - <button - type="button" - class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" - on:click={paste} - > - <Icon icon="paste" style="mr-2 w-6 my-auto" /> - <div class="my-auto">{$t('user.send.paste')}</div> - </button> + <button + type="button" + class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" + on:click={paste} + > + <Icon icon="paste" style="mr-2 w-6 my-auto" /> + <div class="my-auto">{$t("user.send.paste")}</div> + </button> - <button - bind:this={el} - type="submit" - class="{!text - ? 'opacity-50' - : 'opacity-100 hover:opacity-80'} bg-black text-white border rounded-full px-6 py-2 font-bold" - > - {$t('user.send.next')} - </button> - </div> - </form> + <button + bind:this={el} + type="submit" + class="{!text + ? 'opacity-50' + : 'opacity-100 hover:opacity-80'} bg-black text-white border rounded-full px-6 py-2 font-bold" + > + {$t("user.send.next")} + </button> + </div> + </form> - {#if contacts.length} - <div class="space-y-5"> - <h1 class="px-3 md:px-0 text-xl font-semibold mt-10">{$t('user.send.contacts')}</h1> - <div> - {#each contacts as c} - <a href={`/send/${c.username}`}> - <div class="border-b p-2 last:border-b-0 hover:bg-gray-100 rounded-2xl"> - <div class="flex"> - <div> - <div class="flex"> - <Avatar user={c} size={20} disabled={true} /> - <div class="my-auto text-left"> - <p class="ml-1 text-lg break-words">{c.username}</p> - </div> - </div> - </div> - </div> - </div> - </a> - {/each} - </div> - </div> - {/if} + {#if contacts.length} + <div class="space-y-5"> + <h1 class="px-3 md:px-0 text-xl font-semibold mt-10"> + {$t("user.send.contacts")} + </h1> + <div> + {#each contacts as c} + <a href={`/send/${c.username}`}> + <div + class="border-b p-2 last:border-b-0 hover:bg-gray-100 rounded-2xl" + > + <div class="flex"> + <div> + <div class="flex"> + <Avatar user={c} size={20} disabled={true} /> + <div class="my-auto text-left"> + <p class="ml-1 text-lg break-words">{c.username}</p> + </div> + </div> + </div> + </div> + </div> + </a> + {/each} + </div> + </div> + {/if} </div> diff --git a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.server.js b/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.server.js index 92a754ae..5cb86aec 100644 --- a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.server.js +++ b/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.server.js @@ -1,33 +1,32 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { auth, post, fd } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { auth, post, fd } from "$lib/utils"; let seen = {}; export const actions = { - default: async ({ cookies, request }) => { - try { - let { address, amount, confirmed, feeRate, subtract, pin, stale, ts } = await await fd( - request - ); - if (seen[ts]) return fail(400, { message: 'Duplicate form submission' }); + default: async ({ cookies, request }) => { + try { + let { address, amount, confirmed, feeRate, subtract, pin, stale, ts } = + await await fd(request); + if (seen[ts]) return fail(400, { message: "Duplicate form submission" }); - let estimate = await post( - '/bitcoin/fee', - { address, amount, feeRate, subtract }, - auth(cookies) - ); + let estimate = await post( + "/bitcoin/fee", + { address, amount, feeRate, subtract }, + auth(cookies) + ); - let { tx } = estimate; + let { tx } = estimate; - if (stale || !confirmed) return fail(400, estimate); + if (stale || !confirmed) return fail(400, estimate); - seen[ts] = 1; - setTimeout(() => delete seen[ts], 30000); + seen[ts] = 1; + setTimeout(() => delete seen[ts], 30000); - await post('/bitcoin/send', { pin, subtract, tx }, auth(cookies)); - } catch (e) { - return fail(400, { message: e.message }); - } + await post("/bitcoin/send", { pin, subtract, tx }, auth(cookies)); + } catch (e) { + return fail(400, { message: e.message }); + } - throw redirect(307, '/sent'); - } + throw redirect(307, "/sent"); + }, }; diff --git a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte b/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte index 342eeebe..da11a22c 100644 --- a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte +++ b/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte @@ -1,142 +1,159 @@ <script> - import { tick } from 'svelte'; - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { Toggle, Slider, Icon, Numpad, Spinner } from '$comp'; - import { page } from '$app/stores'; - import { fiat as toFiat, f, back, s, sat } from '$lib/utils'; - import { pin } from '$lib/store'; + import { tick } from "svelte"; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { Toggle, Slider, Icon, Numpad, Spinner } from "$comp"; + import { page } from "$app/stores"; + import { fiat as toFiat, f, back, s, sat } from "$lib/utils"; + import { pin } from "$lib/store"; - export let data; - export let form; + export let data; + export let form; - let { rates } = data; - let { address, amount } = $page.params; - let { balance, currency } = data.user; - let loading, submit, fiat, confirmed, feeRate, min, max, fee, ourfee, stale, subtract, adjusting; + let { rates } = data; + let { address, amount } = $page.params; + let { balance, currency } = data.user; + let loading, + submit, + fiat, + confirmed, + feeRate, + min, + max, + fee, + ourfee, + stale, + subtract, + adjusting; - $: rate = rates[currency]; + $: rate = rates[currency]; - let toggle = () => (loading = true); - let toggleAdjusting = () => (adjusting = !adjusting); + let toggle = () => (loading = true); + let toggleAdjusting = () => (adjusting = !adjusting); - $: update(form); - let update = () => { - loading = false; - if (form?.message?.includes('pin')) $pin = undefined; - if (form) ({ min, max, feeRate, fee, ourfee, subtract } = form); - if (!ourfee) ourfee = 0; - if (feeRate) confirmed = true; - stale = false; - }; + $: update(form); + let update = () => { + loading = false; + if (form?.message?.includes("pin")) $pin = undefined; + if (form) ({ min, max, feeRate, fee, ourfee, subtract } = form); + if (!ourfee) ourfee = 0; + if (feeRate) confirmed = true; + stale = false; + }; - let setMax = () => { - fiat = false; - amount = balance; - subtract = true; - }; + let setMax = () => { + fiat = false; + amount = balance; + subtract = true; + }; - $: if (amount + fee > balance) subtract = true; + $: if (amount + fee > balance) subtract = true; - let timeout; - let handle = (e) => { - stale = true; - feeRate = e.target.value; - clearTimeout(timeout); - timeout = setTimeout(() => { - tick().then(() => submit.click()); - }, 200); - }; + let timeout; + let handle = (e) => { + stale = true; + feeRate = e.target.value; + clearTimeout(timeout); + timeout = setTimeout(() => { + tick().then(() => submit.click()); + }, 200); + }; </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" /> + <Icon icon="arrow-left" style="w-10" /> </button> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <div class="text-red-600 text-center"> + {form.message} + </div> {/if} <div class="container px-4 mt-20 max-w-xl mx-auto space-y-5 text-center"> - <h1 class="text-3xl md:text-4xl font-semibold mb-2">{$t('payments.send')}</h1> + <h1 class="text-3xl md:text-4xl font-semibold mb-2">{$t("payments.send")}</h1> - <div class="text-lg text-secondary break-all">{address}</div> + <div class="text-lg text-secondary break-all">{address}</div> - {#if confirmed} - <div class="text-center"> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(toFiat(amount, rate), currency)} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1">⚡️{s(amount)}</h3> - </div> + {#if confirmed} + <div class="text-center"> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(toFiat(amount, rate), currency)} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1">⚡️{s(amount)}</h3> + </div> - <div class="text-center"> - <h2 class="text-secondary mb-5 text-lg">with fee</h2> + <div class="text-center"> + <h2 class="text-secondary mb-5 text-lg">with fee</h2> - <div class="relative"> - <h2 class="text-xl font-semibold"> - {f(toFiat(fee + ourfee, rate), currency)} - </h2> - <h3 class="text-secondary mb-6 mt-1">⚡️{s(fee + ourfee)}</h3> - <button class="absolute right-1/4 top-0 text-secondary p-4" on:click={toggleAdjusting} - ><Icon icon="settings" style="opacity-30" /></button - > - </div> - </div> - {:else} - <Numpad bind:amount bind:fiat {currency} {submit} bind:rate /> - {/if} + <div class="relative"> + <h2 class="text-xl font-semibold"> + {f(toFiat(fee + ourfee, rate), currency)} + </h2> + <h3 class="text-secondary mb-6 mt-1">⚡️{s(fee + ourfee)}</h3> + <button + class="absolute right-1/4 top-0 text-secondary p-4" + on:click={toggleAdjusting} + ><Icon icon="settings" style="opacity-30" /></button + > + </div> + </div> + {:else} + <Numpad bind:amount bind:fiat {currency} {submit} bind:rate /> + {/if} - <form method="POST" use:enhance on:submit={toggle}> - <input name="ts" value={Date.now()} type="hidden" /> - <input name="address" value={address} type="hidden" /> - <input name="amount" value={amount} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> - <input name="confirmed" value={confirmed} type="hidden" /> - <input name="feeRate" value={feeRate} type="hidden" /> - <input name="stale" value={stale} type="hidden" /> - <input name="subtract" value={subtract} type="hidden" /> + <form method="POST" use:enhance on:submit={toggle}> + <input name="ts" value={Date.now()} type="hidden" /> + <input name="address" value={address} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> + <input name="confirmed" value={confirmed} type="hidden" /> + <input name="feeRate" value={feeRate} type="hidden" /> + <input name="stale" value={stale} type="hidden" /> + <input name="subtract" value={subtract} type="hidden" /> - {#if confirmed && adjusting} - <div class="relative mt-10 mb-20"> - <div class="absolute top-5 left-0 text-secondary">Slower (~40 blocks)</div> - <div class="absolute top-5 right-0 text-secondary">Faster (~1 block)</div> - <Slider bind:value={feeRate} {handle} {min} {max} /> - </div> - <div class="flex justify-center gap-2 mb-10"> - <div class="my-auto"> - <Toggle id="subtract" checked={subtract} bind:value={subtract} /> - </div> - <label for="subtract" class="text-secondary text-lg my-auto mb-2" - >Take fee from amount</label - > - </div> - {/if} + {#if confirmed && adjusting} + <div class="relative mt-10 mb-20"> + <div class="absolute top-5 left-0 text-secondary"> + Slower (~40 blocks) + </div> + <div class="absolute top-5 right-0 text-secondary"> + Faster (~1 block) + </div> + <Slider bind:value={feeRate} {handle} {min} {max} /> + </div> + <div class="flex justify-center gap-2 mb-10"> + <div class="my-auto"> + <Toggle id="subtract" checked={subtract} bind:value={subtract} /> + </div> + <label for="subtract" class="text-secondary text-lg my-auto mb-2" + >Take fee from amount</label + > + </div> + {/if} - <div class="flex justify-center gap-2"> - {#if !confirmed} - <button - type="button" - class="hover:opacity-80 bg-black text-white rounded-2xl py-3 px-4 mt-2 border font-bold" - on:click|preventDefault={setMax} - on:keydown={setMax}>Max ⚡️{s(balance)}</button - > - {/if} + <div class="flex justify-center gap-2"> + {#if !confirmed} + <button + type="button" + class="hover:opacity-80 bg-black text-white rounded-2xl py-3 px-4 mt-2 border font-bold" + on:click|preventDefault={setMax} + on:keydown={setMax}>Max ⚡️{s(balance)}</button + > + {/if} - <button - bind:this={submit} - type="submit" - class="opacity-100 hover:opacity-80 rounded-2xl border py-3 font-bold mt-2 bg-black text-white px-4 w-24" - disabled={loading} - > - {#if confirmed} - {$t('payments.send')} - {:else} - {$t('payments.next')} - {/if} - </button> - </div> - </form> + <button + bind:this={submit} + type="submit" + class="opacity-100 hover:opacity-80 rounded-2xl border py-3 font-bold mt-2 bg-black text-white px-4 w-24" + disabled={loading} + > + {#if confirmed} + {$t("payments.send")} + {:else} + {$t("payments.next")} + {/if} + </button> + </div> + </form> </div> diff --git a/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.js b/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.js index 2cb3c4a9..3318a60b 100644 --- a/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.js +++ b/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.js @@ -1,17 +1,17 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { auth, fd, post } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { auth, fd, post } from "$lib/utils"; export const actions = { - default: async ({ cookies, request }) => { - let body = await fd(request); - let p; + default: async ({ cookies, request }) => { + let body = await fd(request); + let p; - try { - p = await post('/payments', body, auth(cookies)); - } catch (e) { - return fail(400, { message: e.message }); - } + try { + p = await post("/payments", body, auth(cookies)); + } catch (e) { + return fail(400, { message: e.message }); + } - if (p) throw redirect(307, `/fund/${body.name}`); - } + if (p) throw redirect(307, `/fund/${body.name}`); + }, }; diff --git a/src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte b/src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte index 6aecf8f3..886e93d2 100644 --- a/src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte +++ b/src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte @@ -1,58 +1,58 @@ <script> - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { Icon, Numpad, Spinner } from '$comp'; - import { page } from '$app/stores'; - import { back } from '$lib/utils'; - import { pin } from '$lib/store'; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + 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; + export let data; + export let form; - let { amount, name } = $page.params; - let { rates } = data; - let { currency } = data.user; - let loading; + let { amount, name } = $page.params; + let { rates } = data; + let { currency } = data.user; + let loading; - $: rate = rates[currency]; + $: rate = rates[currency]; - let submit = () => (loading = true); - $: update(form); - let update = () => { - if (form?.message?.includes('pin')) $pin = undefined; - loading = false; - }; + 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" /> + <Icon icon="arrow-left" style="w-10" /> </button> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <div class="text-red-600 text-center"> + {form.message} + </div> {/if} <div class="container px-4 mt-20 max-w-xl mx-auto"> - <Numpad bind:amount {currency} bind:rate /> + <Numpad bind:amount {currency} bind:rate /> - <form method="POST" use:enhance on:submit={submit}> - <input name="name" value={name} type="hidden" /> - <input name="amount" value={amount} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> + <form method="POST" use:enhance on:submit={submit}> + <input name="name" value={name} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> - <div class="flex w-full"> - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.send')} - {/if} - </button> - </div> - </form> + <div class="flex w-full"> + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + </div> + </form> </div> diff --git a/src/routes/(app)/send/invoice/[id]/+page.server.js b/src/routes/(app)/send/invoice/[id]/+page.server.js index 4cbbb03f..aa284328 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.server.js +++ b/src/routes/(app)/send/invoice/[id]/+page.server.js @@ -1,37 +1,48 @@ -import { error, fail, redirect } from '@sveltejs/kit'; -import { fd, auth, get, post } from '$lib/utils'; +import { error, fail, redirect } from "@sveltejs/kit"; +import { fd, auth, get, post } from "$lib/utils"; export async function load({ params: { id }, parent, url }) { - let { user } = await parent(); + let { user } = await parent(); - let { - amount, - address, - currency, - hash, - rate, - prompt, - tip, - text, - user: recipient - } = await get(`/invoice/${id}`); + let { + amount, + address, + currency, + hash, + rate, + prompt, + tip, + text, + user: recipient, + } = await get(`/invoice/${id}`); - if (prompt && tip === null) throw redirect(307, `/${recipient.username}/invoice/${id}/tip`); - if (recipient.username === user.username) throw error(500, { message: 'Cannot send to self' }); - return { amount, address, currency, hash, tip, rate, payreq: text, recipient }; + if (prompt && tip === null) + throw redirect(307, `/${recipient.username}/invoice/${id}/tip`); + if (recipient.username === user.username) + throw error(500, { message: "Cannot send to self" }); + return { + amount, + address, + currency, + hash, + tip, + rate, + payreq: text, + recipient, + }; } export const actions = { - default: async ({ cookies, params: { id }, request }) => { - try { - let body = await fd(request); - body.hash = id; + default: async ({ cookies, params: { id }, request }) => { + try { + let body = await fd(request); + body.hash = id; - await post('/payments', body, auth(cookies)); - } catch (e) { - return fail(400, { message: e.message }); - } + await post("/payments", body, auth(cookies)); + } catch (e) { + return fail(400, { message: e.message }); + } - throw redirect(307, '/sent'); - } + throw redirect(307, "/sent"); + }, }; diff --git a/src/routes/(app)/send/invoice/[id]/+page.svelte b/src/routes/(app)/send/invoice/[id]/+page.svelte index 02ab9d41..d29f1c96 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.svelte +++ b/src/routes/(app)/send/invoice/[id]/+page.svelte @@ -1,154 +1,168 @@ <script> - import { onDestroy, onMount } from 'svelte'; - import { t } from '$lib/translations'; - import { goto, invalidateAll } from '$app/navigation'; - import { pin } from '$lib/store'; - import { enhance } from '$app/forms'; - import { Avatar, Icon, Numpad, Spinner } from '$comp'; - import { page } from '$app/stores'; - import { post, back, f, fiat as toFiat, s, sats } from '$lib/utils'; - export let data; - export let form; + import { onDestroy, onMount } from "svelte"; + import { t } from "$lib/translations"; + import { goto, invalidateAll } from "$app/navigation"; + import { pin } from "$lib/store"; + import { enhance } from "$app/forms"; + import { Avatar, Icon, Numpad, Spinner } from "$comp"; + import { page } from "$app/stores"; + import { post, back, f, fiat as toFiat, s, sats } from "$lib/utils"; + export let data; + export let form; - let { address, hash, rate, payreq, recipient, tip, rates, user } = data; - let { currency } = user; + let { address, hash, rate, payreq, recipient, tip, rates, user } = data; + let { currency } = user; - let amount = form?.amount || data.amount; - let a, - af, - amountFiat = amount * (rate / sats), - fiat = !amount; + let amount = form?.amount || data.amount; + let a, + af, + amountFiat = amount * (rate / sats), + fiat = !amount; - let setAmount = () => { - amount = a; - amountFiat = af; - }; + let setAmount = () => { + amount = a; + amountFiat = af; + }; - let loading; - let submit = () => (loading = true); + let loading; + let submit = () => (loading = true); - let external = async () => { - let invoice = { type: 'lightning', amount, currency }; - let { id } = await post(`/${recipient.username}/invoice`, { - invoice, - user: { username: recipient.username } - }); + let external = async () => { + let invoice = { type: "lightning", amount, currency }; + let { id } = await post(`/${recipient.username}/invoice`, { + invoice, + user: { username: recipient.username }, + }); - goto(`/${recipient.username}/invoice/${id}?options=true`, { invalidateAll: true }); - }; + goto(`/${recipient.username}/invoice/${id}?options=true`, { + invalidateAll: true, + }); + }; - $: rate = data.rate * (rates[user.currency] / rates[data.currency]); + $: rate = data.rate * (rates[user.currency] / rates[data.currency]); - $: update(form); - let update = () => { - if (form?.message.includes('pin')) $pin = undefined; - loading = false; - }; + $: update(form); + let update = () => { + if (form?.message.includes("pin")) $pin = undefined; + loading = false; + }; - // onMount(async () => { - // if (browser && window.NDEFReader) { - // try { - // let ndef = new NDEFReader(); - // await ndef.scan(); - // - // ndef.addEventListener('readingerror', (e) => { - // console.log('nfc error', e); - // }); - // - // ndef.addEventListener('reading', ({ message, serialNumber }) => { - // console.log(message, serialNumber); - // }); - // } catch (e) { - // console.log('NFC error', e); - // } - // } - // }); + // onMount(async () => { + // if (browser && window.NDEFReader) { + // try { + // let ndef = new NDEFReader(); + // await ndef.scan(); + // + // ndef.addEventListener('readingerror', (e) => { + // console.log('nfc error', e); + // }); + // + // ndef.addEventListener('reading', ({ message, serialNumber }) => { + // console.log(message, serialNumber); + // }); + // } catch (e) { + // console.log('NFC error', e); + // } + // } + // }); </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" /> + <Icon icon="arrow-left" style="w-10" /> </button> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <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"> - <h1 class="text-xl md:text-2xl text-secondary mb-2"> - {$t('payments.send')} - </h1> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(toFiat(amount, rate), currency)} - {#if tip} - <span class="text-lg"> - + {f(toFiat(tip, rate), currency)} - </span> - {/if} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1"> - ⚡️{s(amount)} + {#if amount} + <div class="text-center mb-8"> + <h1 class="text-xl md:text-2xl text-secondary mb-2"> + {$t("payments.send")} + </h1> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(toFiat(amount, rate), currency)} + {#if tip} + <span class="text-lg"> + + {f(toFiat(tip, rate), currency)} + </span> + {/if} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1"> + ⚡️{s(amount)} - {#if tip} - <span class="text-lg"> - + ⚡️{s(tip)} - </span> - {/if} - </h3> + {#if tip} + <span class="text-lg"> + + ⚡️{s(tip)} + </span> + {/if} + </h3> - <h1 class="text-xl md:text-2xl text-secondary mb-2">{$t('payments.to')}</h1> + <h1 class="text-xl md:text-2xl text-secondary mb-2"> + {$t("payments.to")} + </h1> - <div class="flex p-1 gap-2 justify-center"> - {#if recipient.username.includes('@classic')} - <img - src="/images/classic.png" - class="w-24 border-4 border-transparent mr-1" - alt="Bitcoin" - /> - {:else} - <Avatar user={recipient} size={'20'} /> - {/if} - <p class="text-4xl break-words my-auto">{recipient.username.replace('@classic', '')}</p> - </div> - </div> - {:else} - <Numpad bind:amount={a} bind:amountFiat={af} {currency} bind:fiat bind:rate /> - {/if} + <div class="flex p-1 gap-2 justify-center"> + {#if recipient.username.includes("@classic")} + <img + src="/images/classic.png" + class="w-24 border-4 border-transparent mr-1" + alt="Bitcoin" + /> + {:else} + <Avatar user={recipient} size={"20"} /> + {/if} + <p class="text-4xl break-words my-auto"> + {recipient.username.replace("@classic", "")} + </p> + </div> + </div> + {:else} + <Numpad + bind:amount={a} + bind:amountFiat={af} + {currency} + bind:fiat + bind:rate + /> + {/if} - <form method="POST" use:enhance on:submit={submit}> - <input name="address" value={address} type="hidden" /> - <input name="amount" value={amount} type="hidden" /> - <input name="payreq" value={payreq} type="hidden" /> - <input name="username" value={recipient.username} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> + <form method="POST" use:enhance on:submit={submit}> + <input name="address" value={address} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <input name="payreq" value={payreq} type="hidden" /> + <input name="username" value={recipient.username} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> - <div class="flex w-full"> - {#if amount} - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.send')} - {/if} - </button> - {:else} - <button - type="button" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - on:click={setAmount} - > - {$t('payments.next')} - </button> - {/if} - </div> - </form> - <div class="flex my-20"> - <button class="mx-auto" on:click={external}>{$t('payments.moreOptions')}</button> - </div> + <div class="flex w-full"> + {#if amount} + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + {:else} + <button + type="button" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + on:click={setAmount} + > + {$t("payments.next")} + </button> + {/if} + </div> + </form> + <div class="flex my-20"> + <button class="mx-auto" on:click={external} + >{$t("payments.moreOptions")}</button + > + </div> </div> diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.server.js b/src/routes/(app)/send/lightning/[payreq]/+page.server.js index d358bbca..d9e791e1 100644 --- a/src/routes/(app)/send/lightning/[payreq]/+page.server.js +++ b/src/routes/(app)/send/lightning/[payreq]/+page.server.js @@ -1,24 +1,24 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { fd, auth, post } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { fd, auth, post } from "$lib/utils"; export async function load({ params }) { - return post('/parse', params); + return post("/parse", params); } export const actions = { - setAmount: async ({ cookies, request }) => fd(request), + setAmount: async ({ cookies, request }) => fd(request), - send: async ({ cookies, request }) => { - try { - let body = await fd(request); + send: async ({ cookies, request }) => { + try { + let body = await fd(request); - await post('/payments', body, auth(cookies)); - } catch (e) { - if (e.message.includes('unusable')) - e.message = 'Failed to route payment, try sending a lower amount'; - return fail(400, { message: e.message }); - } + await post("/payments", body, auth(cookies)); + } catch (e) { + if (e.message.includes("unusable")) + e.message = "Failed to route payment, try sending a lower amount"; + return fail(400, { message: e.message }); + } - throw redirect(307, '/sent'); - } + throw redirect(307, "/sent"); + }, }; diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.svelte b/src/routes/(app)/send/lightning/[payreq]/+page.svelte index e232ef05..185597c7 100644 --- a/src/routes/(app)/send/lightning/[payreq]/+page.svelte +++ b/src/routes/(app)/send/lightning/[payreq]/+page.svelte @@ -1,105 +1,120 @@ <script> - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { Icon, Numpad, Spinner } from '$comp'; - import { page } from '$app/stores'; - import { back, s } from '$lib/utils'; - import { pin } from '$lib/store'; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + 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; + export let data; + export let form; - let { payreq } = $page.params; - let { alias, rates } = data; + let { payreq } = $page.params; + let { alias, rates } = data; - let { currency } = data.user; - let a; - let maxfee = 500; + let { currency } = data.user; + let a; + let maxfee = 500; - $: amount = form?.amount || data.amount; - $: rate = rates[currency]; + $: amount = form?.amount || data.amount; + $: rate = rates[currency]; - let loading; - let submit = () => (loading = true); - $: update(form); - let update = () => { - if (form?.message?.includes('pin')) $pin = undefined; - loading = false; - }; + let loading; + let submit = () => (loading = true); + $: update(form); + let update = () => { + if (form?.message?.includes("pin")) $pin = undefined; + loading = false; + }; - let show; - let toggle = () => (show = !show); + let show; + let toggle = () => (show = !show); </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" /> + <Icon icon="arrow-left" style="w-10" /> </button> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <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"> - <h1 class="text-xl md:text-2xl text-secondary mb-2">{$t('payments.send')}</h1> - <p class="text-6xl break-words mb-4"> - ⚡️{s(amount)} - </p> - <h1 class="text-xl md:text-2xl text-secondary mb-2">{$t('payments.to')}</h1> - <p class="text-4xl break-words">{alias}</p> - </div> + {#if amount} + <div class="text-center mb-8"> + <h1 class="text-xl md:text-2xl text-secondary mb-2"> + {$t("payments.send")} + </h1> + <p class="text-6xl break-words mb-4"> + ⚡️{s(amount)} + </p> + <h1 class="text-xl md:text-2xl text-secondary mb-2"> + {$t("payments.to")} + </h1> + <p class="text-4xl break-words">{alias}</p> + </div> - <form method="POST" use:enhance on:submit={submit} action="?/send" class="space-y-5"> - <input name="payreq" value={payreq} type="hidden" /> - <input name="amount" value={amount} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> - <input name="maxfee" value={maxfee} type="hidden" /> + <form + method="POST" + use:enhance + on:submit={submit} + action="?/send" + class="space-y-5" + > + <input name="payreq" value={payreq} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> + <input name="maxfee" value={maxfee} type="hidden" /> - <div class="flex w-full"> - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - disabled={loading} - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.send')} - {/if} - </button> - </div> - </form> + <div class="flex w-full"> + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + disabled={loading} + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + </div> + </form> - {#if show} - <div class="relative w-96 mx-auto"> - <label for="maxfee">Max fee</label> - <input name="maxfee" bind:value={maxfee} /> + {#if show} + <div class="relative w-96 mx-auto"> + <label for="maxfee">Max fee</label> + <input name="maxfee" bind:value={maxfee} /> - <div - class="absolute right-[2px] top-[25px] text-gray-600 rounded-r-2xl p-4 h-[54px] my-auto border-l " - > - ⚡️ - </div> - </div> - {:else} - <div class="flex w-full mt-5"> - <button class="mx-auto" on:click={toggle}>Settings</button> - </div> - {/if} - {:else} - <form method="POST" action="?/setAmount" class="w-[300px] mx-auto" use:enhance> - <input type="hidden" value={a} name="amount" /> - <Numpad bind:amount={a} {currency} {rate} /> - <button - type="submit" - class="bg-black text-white rounded-xl h-[48px] flex w-full justify-center items-center font-semibold + <div + class="absolute right-[2px] top-[25px] text-gray-600 rounded-r-2xl p-4 h-[54px] my-auto border-l" + > + ⚡️ + </div> + </div> + {:else} + <div class="flex w-full mt-5"> + <button class="mx-auto" on:click={toggle}>Settings</button> + </div> + {/if} + {:else} + <form + method="POST" + action="?/setAmount" + class="w-[300px] mx-auto" + use:enhance + > + <input type="hidden" value={a} name="amount" /> + <Numpad bind:amount={a} {currency} {rate} /> + <button + type="submit" + class="bg-black text-white rounded-xl h-[48px] flex w-full justify-center items-center font-semibold opacity-100 hover:opacity-80" - > - Next - </button> - </form> - {/if} + > + Next + </button> + </form> + {/if} </div> diff --git a/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js b/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js index a01cdcfc..1f3a0420 100644 --- a/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js +++ b/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js @@ -1,46 +1,47 @@ -import { error, fail, redirect } from '@sveltejs/kit'; -import { types, sats, fd, auth, get, post } from '$lib/utils'; +import { error, fail, redirect } from "@sveltejs/kit"; +import { types, sats, fd, auth, get, post } from "$lib/utils"; export async function load({ cookies, params, parent }) { - let subject = await get(`/users/${params.username}`); + let subject = await get(`/users/${params.username}`); - let { rates, user } = await parent(); - let { username } = params; - let [amount, currency] = params.amount.split('/'); + let { rates, user } = await parent(); + let { username } = params; + let [amount, currency] = params.amount.split("/"); - if (subject.username === user?.username) throw error(500, { message: 'Cannot send to self' }); + if (subject.username === user?.username) + throw error(500, { message: "Cannot send to self" }); - let rate; - if (amount) { - rate = rates[currency ? currency.toUpperCase() : subject.currency]; - if (!rate) throw error(500, 'Invalid currency symbol'); - if (currency) amount = (amount * sats) / rate; + let rate; + if (amount) { + rate = rates[currency ? currency.toUpperCase() : subject.currency]; + if (!rate) throw error(500, "Invalid currency symbol"); + if (currency) amount = (amount * sats) / rate; - let { hash } = await post( - '/invoice', - { - invoice: { amount, prompt: false, type: types.lightning }, - user: { username } - }, - auth(cookies) - ); + let { hash } = await post( + "/invoice", + { + invoice: { amount, prompt: false, type: types.lightning }, + user: { username }, + }, + auth(cookies) + ); - throw redirect(307, `/${username}/invoice/${hash}`); - } + throw redirect(307, `/${username}/invoice/${hash}`); + } - return { amount, rate, subject }; + return { amount, rate, subject }; } export const actions = { - default: async ({ cookies, request }) => { - try { - let body = await fd(request); - await post('/payments', body, auth(cookies)); - } catch (e) { - console.log(e); - return fail(400, { message: e.message }); - } + default: async ({ cookies, request }) => { + try { + let body = await fd(request); + await post("/payments", body, auth(cookies)); + } catch (e) { + console.log(e); + return fail(400, { message: e.message }); + } - throw redirect(307, '/sent'); - } + throw redirect(307, "/sent"); + }, }; diff --git a/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte b/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte index 32a13939..cb458323 100644 --- a/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte +++ b/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte @@ -1,103 +1,103 @@ <script> - import { t } from '$lib/translations'; - import { goto } from '$app/navigation'; - import { pin } from '$lib/store'; - import { enhance } from '$app/forms'; - import { AppHeader, Avatar, Icon, Numpad, Spinner } from '$comp'; - import { page } from '$app/stores'; - import { f, post, s, sat, sats } from '$lib/utils'; + import { t } from "$lib/translations"; + import { goto } from "$app/navigation"; + import { pin } from "$lib/store"; + import { enhance } from "$app/forms"; + import { AppHeader, Avatar, Icon, Numpad, Spinner } from "$comp"; + import { page } from "$app/stores"; + import { f, post, s, sat, sats } from "$lib/utils"; - export let data; - export let form; + export let data; + export let form; - let { subject, rate, rates, user } = data; - let currency = user.currency || subject.currency || 'USD'; - let next; + let { subject, rate, rates, user } = data; + let currency = user.currency || subject.currency || "USD"; + let next; - let amount = form?.amount || data.amount; - let a, - af, - fiat = !amount, - hash, - amountFiat = 0; + let amount = form?.amount || data.amount; + let a, + af, + fiat = !amount, + hash, + amountFiat = 0; - $: r = rate || rates[user.currency || subject.currency]; + $: r = rate || rates[user.currency || subject.currency]; - let setAmount = async () => { - amount = a; - amountFiat = parseFloat(af).toFixed(2); - rate = fiat ? (sats * amountFiat) / amount : r; - ({ hash } = await post(`/${subject.username}/invoice`, { - invoice: { - amount, - hash: 'internal', - rate: rates[subject.currency], - prompt: subject.prompt, - type: 'internal' - }, - user: subject - })); + let setAmount = async () => { + amount = a; + amountFiat = parseFloat(af).toFixed(2); + rate = fiat ? (sats * amountFiat) / amount : r; + ({ hash } = await post(`/${subject.username}/invoice`, { + invoice: { + amount, + hash: "internal", + rate: rates[subject.currency], + prompt: subject.prompt, + type: "internal", + }, + user: subject, + })); - goto(`/${subject.username}/invoice/${hash}`); - }; + goto(`/${subject.username}/invoice/${hash}`); + }; - let loading; - let submit = () => (loading = true); + let loading; + let submit = () => (loading = true); - $: update(form); - let update = () => { - if (form?.message?.includes('pin')) $pin = undefined; - loading = false; - }; + $: update(form); + let update = () => { + if (form?.message?.includes("pin")) $pin = undefined; + loading = false; + }; </script> <AppHeader {data} /> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <div class="text-red-600 text-center"> + {form.message} + </div> {/if} <div class="container px-4 mt-20 max-w-xl mx-auto space-y-8"> - <Numpad - bind:amount={a} - bind:amountFiat={af} - {currency} - bind:fiat - bind:rate={r} - bind:submit={next} - /> + <Numpad + bind:amount={a} + bind:amountFiat={af} + {currency} + bind:fiat + bind:rate={r} + bind:submit={next} + /> - <form method="POST" use:enhance on:submit={submit}> - <input name="amount" value={amount} type="hidden" /> - <input name="username" value={subject.username} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> - <input name="hash" value={hash} type="hidden" /> + <form method="POST" use:enhance on:submit={submit}> + <input name="amount" value={amount} type="hidden" /> + <input name="username" value={subject.username} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> + <input name="hash" value={hash} type="hidden" /> - <div class="flex w-full"> - {#if amount} - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.send')} - {/if} - </button> - {:else} - <button - type="button" - bind:this={next} - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - on:click={setAmount} - on:keydown={setAmount} - > - Next - </button> - {/if} - </div> - </form> + <div class="flex w-full"> + {#if amount} + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + {:else} + <button + type="button" + bind:this={next} + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + on:click={setAmount} + on:keydown={setAmount} + > + Next + </button> + {/if} + </div> + </form> </div> diff --git a/src/routes/(app)/sent/+page.server.js b/src/routes/(app)/sent/+page.server.js index 381ff32f..62a4d9b4 100644 --- a/src/routes/(app)/sent/+page.server.js +++ b/src/routes/(app)/sent/+page.server.js @@ -1,6 +1,6 @@ -import { get, auth } from '$lib/utils'; +import { get, auth } from "$lib/utils"; export let load = async ({ cookies }) => { - let { payments } = await get('/payments', auth(cookies)); - return { payments }; + let { payments } = await get("/payments", auth(cookies)); + return { payments }; }; diff --git a/src/routes/(app)/sent/+page.svelte b/src/routes/(app)/sent/+page.svelte index dbd18c2e..400e8904 100644 --- a/src/routes/(app)/sent/+page.svelte +++ b/src/routes/(app)/sent/+page.svelte @@ -1,47 +1,52 @@ <script> - import { fiat, f, s, sat, sats } from '$lib/utils'; - import { scale } from 'svelte/transition'; - import { Icon } from '$comp'; - import { toast } from '@zerodevx/svelte-toast'; - import { t } from '$lib/translations'; + import { fiat, f, s, sat, sats } from "$lib/utils"; + import { scale } from "svelte/transition"; + import { Icon } from "$comp"; + import { toast } from "@zerodevx/svelte-toast"; + import { t } from "$lib/translations"; - export let data; - let { payments, user } = data; - let { currency, username } = user; + export let data; + let { payments, user } = data; + let { currency, username } = user; - let payment = payments[0]; - let { amount, rate, tip } = payment; - amount = Math.abs(amount); + let payment = payments[0]; + let { amount, rate, tip } = payment; + amount = Math.abs(amount); - toast.pop(0); + toast.pop(0); </script> <div class="text-center mt-20 md:mt-0"> - <div class="flex w-full max-w-[200px] mx-auto py-20" in:scale={{ start: 0.5 }}> - <Icon icon="check" style="mx-auto" /> - </div> - <h1 class="text-3xl md:text-4xl font-bold mb-6">{$t('payments.sent')}!</h1> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(fiat(amount, rate), currency)} - {#if tip} - <span class="text-lg"> - + {f(fiat(tip, rate), currency)} - </span> - {/if} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1"> - ⚡️{s(amount)} + <div + class="flex w-full max-w-[200px] mx-auto py-20" + in:scale={{ start: 0.5 }} + > + <Icon icon="check" style="mx-auto" /> + </div> + <h1 class="text-3xl md:text-4xl font-bold mb-6">{$t("payments.sent")}!</h1> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(fiat(amount, rate), currency)} + {#if tip} + <span class="text-lg"> + + {f(fiat(tip, rate), currency)} + </span> + {/if} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1"> + ⚡️{s(amount)} - {#if tip} - <span class="text-lg"> - + ⚡️{s(tip)} - </span> - {/if} - </h3> + {#if tip} + <span class="text-lg"> + + ⚡️{s(tip)} + </span> + {/if} + </h3> </div> <a href={`/${username}/payments`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> </a> -<div class="fixed bottom-10 w-full text-center">{$t('payments.tapAnywhere')}</div> +<div class="fixed bottom-10 w-full text-center"> + {$t("payments.tapAnywhere")} +</div> diff --git a/src/routes/(app)/support/+page.svelte b/src/routes/(app)/support/+page.svelte index b2f82e89..b31b4389 100644 --- a/src/routes/(app)/support/+page.svelte +++ b/src/routes/(app)/support/+page.svelte @@ -1,122 +1,138 @@ <script> - import { onDestroy } from 'svelte'; - import { fail, success, post } from '$lib/utils'; - import { scale } from 'svelte/transition'; - import { AppHeader, Icon } from '$comp'; - import { t } from '$lib/translations'; + import { onDestroy } from "svelte"; + import { fail, success, post } from "$lib/utils"; + import { scale } from "svelte/transition"; + import { AppHeader, Icon } from "$comp"; + import { t } from "$lib/translations"; - export let data; + export let data; - let { user } = data; - $: if (user) { - data.subject = { ...user }; - } + let { user } = data; + $: if (user) { + data.subject = { ...user }; + } - let email; - let message; - let sent; - let recaptchaSiteKey = '6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM'; + let email; + let message; + let sent; + let recaptchaSiteKey = "6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM"; - let submit = (e) => { - e.preventDefault(); - grecaptcha.ready(() => { - grecaptcha.execute(recaptchaSiteKey, { action: 'submit' }).then((token) => - post('/support', { username: user?.username, email, message, token }) - .then(() => (sent = true)) - .catch(() => fail('problem submitting')) - ); - }); - }; + let submit = (e) => { + e.preventDefault(); + grecaptcha.ready(() => { + grecaptcha.execute(recaptchaSiteKey, { action: "submit" }).then((token) => + post("/support", { username: user?.username, email, message, token }) + .then(() => (sent = true)) + .catch(() => fail("problem submitting")) + ); + }); + }; - onDestroy(() => { - const nodeBadge = document.querySelector('.grecaptcha-badge'); - if (nodeBadge) { - document.body.removeChild(nodeBadge.parentNode); - } + onDestroy(() => { + const nodeBadge = document.querySelector(".grecaptcha-badge"); + if (nodeBadge) { + document.body.removeChild(nodeBadge.parentNode); + } - const scriptSelector = - "script[src='https://www.google.com/recaptcha/api.js?render=" + recaptchaSiteKey + "']"; - const script = document.querySelector(scriptSelector); - if (script) { - script.remove(); - } - }); + const scriptSelector = + "script[src='https://www.google.com/recaptcha/api.js?render=" + + recaptchaSiteKey + + "']"; + const script = document.querySelector(scriptSelector); + if (script) { + script.remove(); + } + }); </script> <svelte:head> - <script - src="https://www.google.com/recaptcha/api.js?render=6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM" - ></script> + <script + src="https://www.google.com/recaptcha/api.js?render=6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM" + ></script> </svelte:head> {#if user} - <AppHeader {data} /> + <AppHeader {data} /> {/if} <div class="container px-4 max-w-lg mx-auto mt-20 space-y-8"> - {#if sent} - <h1 class="text-center text-3xl md:text-4xl font-semibold">Thank you!</h1> - <div class="text-center text-lg text-secondary">Someone will be in touch shortly.</div> + {#if sent} + <h1 class="text-center text-3xl md:text-4xl font-semibold">Thank you!</h1> + <div class="text-center text-lg text-secondary"> + Someone will be in touch shortly. + </div> - <div> - <a href={user ? `/${user.username}` : '/'}> - <button - class="rounded-full border py-2 px-5 font-bold hover:opacity-80 w-full bg-black text-white" - >Ok</button - > - </a> - </div> - {:else} - <h1 class="text-center text-3xl md:text-4xl font-semibold mb-8"> - {$t('user.support.header')} - </h1> + <div> + <a href={user ? `/${user.username}` : "/"}> + <button + class="rounded-full border py-2 px-5 font-bold hover:opacity-80 w-full bg-black text-white" + >Ok</button + > + </a> + </div> + {:else} + <h1 class="text-center text-3xl md:text-4xl font-semibold mb-8"> + {$t("user.support.header")} + </h1> - <p class="text-secondary text-lg"> - Fill out this form or email us directly at <a - class="underline" - href="mailto:[email protected]">[email protected]</a - > and we'll do our best to get back to you in a timely manner. - </p> + <p class="text-secondary text-lg"> + Fill out this form or email us directly at <a + class="underline" + href="mailto:[email protected]">[email protected]</a + > and we'll do our best to get back to you in a timely manner. + </p> - <form on:submit={submit}> - <div class="mb-4"> - <label for="account" class="font-semibold">{$t('user.support.accountName')}</label> - <input - class="bg-primary" - type="text" - name="account" - required - value={user?.username || ''} - /> - </div> + <form on:submit={submit}> + <div class="mb-4"> + <label for="account" class="font-semibold" + >{$t("user.support.accountName")}</label + > + <input + class="bg-primary" + type="text" + name="account" + required + value={user?.username || ""} + /> + </div> - <div class="mb-4"> - <label for="email" class="font-semibold">{$t('user.support.email')}</label> - <input class="bg-primary" type="email" name="email" bind:value={email} required /> - </div> + <div class="mb-4"> + <label for="email" class="font-semibold" + >{$t("user.support.email")}</label + > + <input + class="bg-primary" + type="email" + name="email" + bind:value={email} + required + /> + </div> - <div class="mb-4"> - <label for="message" class="font-semibold">{$t('user.support.message')}</label> - <textarea - rows={5} - class="bg-primary" - type="text" - name="message" - bind:value={message} - required - /> - </div> + <div class="mb-4"> + <label for="message" class="font-semibold" + >{$t("user.support.message")}</label + > + <textarea + rows={5} + class="bg-primary" + type="text" + name="message" + bind:value={message} + required + /> + </div> - <button - type="submit" - disabled={!email || !message} - class="g-recaptcha {!email || !message - ? 'opacity-50' - : 'opacity-100'} bg-black text-white font-bold rounded-xl py-3 w-full mx-auto {email && - message - ? 'hover:opacity-80' - : ''}">{$t('user.support.send')}</button - > - </form> - {/if} + <button + type="submit" + disabled={!email || !message} + class="g-recaptcha {!email || !message + ? 'opacity-50' + : 'opacity-100'} bg-black text-white font-bold rounded-xl py-3 w-full mx-auto {email && + message + ? 'hover:opacity-80' + : ''}">{$t("user.support.send")}</button + > + </form> + {/if} </div> diff --git a/src/routes/(app)/support/+server.js b/src/routes/(app)/support/+server.js index 6b1778a8..e9a16cfc 100644 --- a/src/routes/(app)/support/+server.js +++ b/src/routes/(app)/support/+server.js @@ -1,9 +1,9 @@ -import { json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { json } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export const prerender = false; export async function POST({ cookies, request }) { - let result = await post('/email', await request.json(), auth(cookies)); - return json(result); + let result = await post("/email", await request.json(), auth(cookies)); + return json(result); } diff --git a/src/routes/(app)/withdraw/+server.js b/src/routes/(app)/withdraw/+server.js index 7b1ff4c6..a75a72f8 100644 --- a/src/routes/(app)/withdraw/+server.js +++ b/src/routes/(app)/withdraw/+server.js @@ -1,9 +1,10 @@ -import { auth, post } from '$lib/utils'; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - let body = await request.json(); - await post('/payments', body, auth(cookies)); + let body = await request.json(); + await post("/payments", body, auth(cookies)); - return new Response(JSON.stringify('ok'), { headers: { 'content-type': 'application/json' } }); + return new Response(JSON.stringify("ok"), { + headers: { "content-type": "application/json" }, + }); } - diff --git a/src/routes/(static)/+layout.server.js b/src/routes/(static)/+layout.server.js index 2deeebc5..81d1d3dc 100644 --- a/src/routes/(static)/+layout.server.js +++ b/src/routes/(static)/+layout.server.js @@ -1,16 +1,16 @@ -import { get, post, auth, protectedRoutes } from '$lib/utils'; +import { get, post, auth, protectedRoutes } from "$lib/utils"; export async function load({ cookies, request, url, params }) { - let token = cookies.get('token'); - let rate, - rates = { USD: 1 }; + let token = cookies.get("token"); + let rate, + rates = { USD: 1 }; - let user; - if (token) { - try { - user = await get('/me', auth(cookies)); - } catch (e) {} - } + let user; + if (token) { + try { + user = await get("/me", auth(cookies)); + } catch (e) {} + } - return { user }; + return { user }; } diff --git a/src/routes/(static)/+layout.svelte b/src/routes/(static)/+layout.svelte index 3d596dff..29089dff 100644 --- a/src/routes/(static)/+layout.svelte +++ b/src/routes/(static)/+layout.svelte @@ -1,13 +1,13 @@ <script> - import { SvelteToast } from '@zerodevx/svelte-toast'; - import { Footer, LandingHeader } from '$comp'; - export let data; - $: ({ user } = data); + import { SvelteToast } from "@zerodevx/svelte-toast"; + import { Footer, LandingHeader } from "$comp"; + export let data; + $: ({ user } = data); </script> <SvelteToast options={{ reversed: true, intro: { y: 192 } }} /> <LandingHeader {user} /> <main class="container flex px-10 md:px-40 mx-auto min-h-[600px] pb-20"> - <slot /> + <slot /> </main> <Footer /> diff --git a/src/routes/(static)/bounties/+page.svelte b/src/routes/(static)/bounties/+page.svelte index 729cf5dd..b53dc85d 100644 --- a/src/routes/(static)/bounties/+page.svelte +++ b/src/routes/(static)/bounties/+page.svelte @@ -1,102 +1,108 @@ <div class="space-y-5"> - <h1 class="text-3xl mb-6 pt-28 md:pt-12">Bounties</h1> + <h1 class="text-3xl mb-6 pt-28 md:pt-12">Bounties</h1> - <p> - We're offering ⚡️1,000,000 to the first 10 people that help us onboard these businesses in - Vancouver to take bitcoin payments. - </p> + <p> + We're offering ⚡️1,000,000 to the first 10 people that help us onboard + these businesses in Vancouver to take bitcoin payments. + </p> - <ul class="text-center text-lg space-y-3"> - <li>Grocery / Produce Store</li> - <li class="leading-3"> - <span class="line-through">Cafe</span><br /> - <span class="text-xs" - ><a href="/reverendhodl">ReverendHodl</a> for - <a href="/laughingbean">Laughing Bean Coffee Co.</a></span - > - </li> - <li>Bakery</li> - <li class="leading-3"> - <span class="line-through">Butcher Shop</span><br /> - <span class="text-xs" - ><a href="/danielle">danielle</a> for - <a href="/butchersguild">All Natural & Exotic Meats</a></span - > - </li> - <li>Laundromat / Dry Cleaner</li> - <li>Dollar Store / Book Store / Gift Shop</li> - <li class="leading-3"> - <span class="line-through">Sushi Restaurant</span><br /> - <span class="text-xs" - ><a href="/ica547">ica547</a> for <a href="/metallmash">VanLove Sushi</a></span - > - </li> - <li>Pizza by the Slice</li> - <li>Liquor Store / Gas Station</li> - <li>Bubble Tea / Ice Cream Parlour</li> - </ul> + <ul class="text-center text-lg space-y-3"> + <li>Grocery / Produce Store</li> + <li class="leading-3"> + <span class="line-through">Cafe</span><br /> + <span class="text-xs" + ><a href="/reverendhodl">ReverendHodl</a> for + <a href="/laughingbean">Laughing Bean Coffee Co.</a></span + > + </li> + <li>Bakery</li> + <li class="leading-3"> + <span class="line-through">Butcher Shop</span><br /> + <span class="text-xs" + ><a href="/danielle">danielle</a> for + <a href="/butchersguild">All Natural & Exotic Meats</a></span + > + </li> + <li>Laundromat / Dry Cleaner</li> + <li>Dollar Store / Book Store / Gift Shop</li> + <li class="leading-3"> + <span class="line-through">Sushi Restaurant</span><br /> + <span class="text-xs" + ><a href="/ica547">ica547</a> for + <a href="/metallmash">VanLove Sushi</a></span + > + </li> + <li>Pizza by the Slice</li> + <li>Liquor Store / Gas Station</li> + <li>Bubble Tea / Ice Cream Parlour</li> + </ul> - <p>Eligible businesses:</p> + <p>Eligible businesses:</p> - <ul class="text-sm ml-4 space-y-2"> - <li> - Have a physical location in Vancouver, North Vancouver, West Vancouver, Burnaby, New - Westminster, or Richmond - </li> - <li>Take walk-in customers</li> - <li>Have regular hours and a website with contact info</li> - <li>Have a 3 star or better rating on Google</li> - <li> - Accept bitcoin during their posted hours without needing to make any special arrangements - </li> - <li>Use Coinos</li> - </ul> + <ul class="text-sm ml-4 space-y-2"> + <li> + Have a physical location in Vancouver, North Vancouver, West Vancouver, + Burnaby, New Westminster, or Richmond + </li> + <li>Take walk-in customers</li> + <li>Have regular hours and a website with contact info</li> + <li>Have a 3 star or better rating on Google</li> + <li> + Accept bitcoin during their posted hours without needing to make any + special arrangements + </li> + <li>Use Coinos</li> + </ul> - <p>Each business category can be claimed only once</p> - <p>One bounty per person</p> + <p>Each business category can be claimed only once</p> + <p>One bounty per person</p> - <p>The businesses can expect free ongoing support and promotion from us and our community.</p> + <p> + The businesses can expect free ongoing support and promotion from us and our + community. + </p> - <p>The prize money will be offered to the person who makes the referral.</p> + <p>The prize money will be offered to the person who makes the referral.</p> - <p> - <b>⚡️250,000</b> will be awarded once the business has met with us and is ready to take payments - </p> - <p> - <b>⚡️750,000</b> will be awarded once the merchant has accepted bitcoin for 6 months. We'll be - sending - <a - href="https://btcmap.org/leaderboard" - class="text-black hover:underline" - target="_blank" - rel="noreferrer">shadowy supertaggers</a - > to check up on them periodically. - </p> + <p> + <b>⚡️250,000</b> will be awarded once the business has met with us and is ready + to take payments + </p> + <p> + <b>⚡️750,000</b> will be awarded once the merchant has accepted bitcoin for + 6 months. We'll be sending + <a + href="https://btcmap.org/leaderboard" + class="text-black hover:underline" + target="_blank" + rel="noreferrer">shadowy supertaggers</a + > to check up on them periodically. + </p> - <p> - Bounties will be awarded solely at our discretion and we reserve the right to refuse any - applicants without cause - </p> + <p> + Bounties will be awarded solely at our discretion and we reserve the right + to refuse any applicants without cause + </p> - <h2 class="text-xl">Ready to begin!?</h2> + <h2 class="text-xl">Ready to begin!?</h2> - <p> - To make a referral, reach out on one of our socials or email us at <a - href="mailto:[email protected]" - class="text-black hover:underline">[email protected]</a - > and we'll set up an appointment with you and the business owner to get started. - </p> + <p> + To make a referral, reach out on one of our socials or email us at <a + href="mailto:[email protected]" + class="text-black hover:underline">[email protected]</a + > and we'll set up an appointment with you and the business owner to get started. + </p> - <p> - We'll update this page as the bounties are claimed so you'll be able to see which ones are still - available. - </p> + <p> + We'll update this page as the bounties are claimed so you'll be able to see + which ones are still available. + </p> - <p>Thanks for participating and helping to orange pill Vancouver!</p> + <p>Thanks for participating and helping to orange pill Vancouver!</p> </div> <style> - a { - @apply text-blue-400; - } + a { + @apply text-blue-400; + } </style> diff --git a/src/routes/(static)/docs/+page.server.js b/src/routes/(static)/docs/+page.server.js index e80365fa..41176b14 100644 --- a/src/routes/(static)/docs/+page.server.js +++ b/src/routes/(static)/docs/+page.server.js @@ -1,4 +1,4 @@ export let load = async ({ cookies }) => { - let token = cookies.get('token'); - return { token }; + let token = cookies.get("token"); + return { token }; }; diff --git a/src/routes/(static)/docs/+page.svelte b/src/routes/(static)/docs/+page.svelte index 63173173..6e08c2e8 100644 --- a/src/routes/(static)/docs/+page.svelte +++ b/src/routes/(static)/docs/+page.svelte @@ -1,121 +1,130 @@ <script> - import { copyNoNewlines as copy } from '$lib/utils'; - import { Code, Icon } from '$comp'; - import { PUBLIC_COINOS_URL } from '$env/static/public'; - export let data; - let { user, token } = data; - let api = 'https://coinos.io/api'; + import { copyNoNewlines as copy } from "$lib/utils"; + import { Code, Icon } from "$comp"; + import { PUBLIC_COINOS_URL } from "$env/static/public"; + export let data; + let { user, token } = data; + let api = "https://coinos.io/api"; - let tokenSample = token ? `export token="${token}"` : `export token=<your auth token>`; + let tokenSample = token + ? `export token="${token}"` + : `export token=<your auth token>`; </script> <div class="space-y-8 lg:text-xl mt-20 pt-20 md:pt-0 w-full max-w-full"> - <h1 class="text-4xl">Documentation</h1> + <h1 class="text-4xl">Documentation</h1> - <p class="text-secondary"> - Coinos has a simple REST API that can be used to register accounts and make payments and - queries. The following examples show how you can call the API with <a - href="https://curl.se/" - class="font-bold">curl</a - > from your command line. - </p> + <p class="text-secondary"> + Coinos has a simple REST API that can be used to register accounts and make + payments and queries. The following examples show how you can call the API + with <a href="https://curl.se/" class="font-bold">curl</a> from your command + line. + </p> - <h2 class="text-2xl">API Base URL</h2> - <div class="bg-black text-white rounded-lg p-4 flex gap-4"> - <div>{api}</div> - <button class="ml-auto my-auto invert opacity-90" on:click={() => copy(api)} - ><Icon icon="copy" style="w-10 max-w-none" /></button - > - </div> + <h2 class="text-2xl">API Base URL</h2> + <div class="bg-black text-white rounded-lg p-4 flex gap-4"> + <div>{api}</div> + <button class="ml-auto my-auto invert opacity-90" on:click={() => copy(api)} + ><Icon icon="copy" style="w-10 max-w-none" /></button + > + </div> - <h2 class="text-2xl">Auth Token</h2> - <p class="text-secondary"> - {#if user && token} - This token authorizes you to use the API as <b>{user.username}</b>. - {:else} - <a href="/login" class="font-bold">Sign in</a> to view your auth token here, or get one from the - /login endpoint. - {/if} + <h2 class="text-2xl">Auth Token</h2> + <p class="text-secondary"> + {#if user && token} + This token authorizes you to use the API as <b>{user.username}</b>. + {:else} + <a href="/login" class="font-bold">Sign in</a> to view your auth token here, + or get one from the /login endpoint. + {/if} - Save it in a variable called <b>$token</b> to run the examples. - </p> + Save it in a variable called <b>$token</b> to run the examples. + </p> - <div class="bg-black text-white rounded-lg p-4 flex gap-4"> - <div class="w-full break-all">{tokenSample}</div> - <button class="ml-auto my-auto invert opacity-90" on:click={() => copy(tokenSample)} - ><Icon icon="copy" style="w-10 max-w-none" /></button - > - </div> + <div class="bg-black text-white rounded-lg p-4 flex gap-4"> + <div class="w-full break-all">{tokenSample}</div> + <button + class="ml-auto my-auto invert opacity-90" + on:click={() => copy(tokenSample)} + ><Icon icon="copy" style="w-10 max-w-none" /></button + > + </div> - <h2 class="text-2xl">POST /register</h2> - <p class="text-secondary">Register a new user account with a username and password</p> - <Code sample="register" /> + <h2 class="text-2xl">POST /register</h2> + <p class="text-secondary"> + Register a new user account with a username and password + </p> + <Code sample="register" /> - <h2 class="text-2xl">POST /login</h2> - <p class="text-secondary">Login to an account to get its auth token</p> - <Code sample="login" /> + <h2 class="text-2xl">POST /login</h2> + <p class="text-secondary">Login to an account to get its auth token</p> + <Code sample="login" /> - <h2 class="text-2xl">POST /invoice</h2> - <p class="text-secondary">Create an invoice.</p> + <h2 class="text-2xl">POST /invoice</h2> + <p class="text-secondary">Create an invoice.</p> - <div class="text-secondary"> - <div>Request params</div> - <div class="grid grid-cols-3 ml-2"> - <div class="font-bold">type</div> - <div class="col-span-2">bitcoin or lightning</div> - <div class="font-bold">amount</div> - <div class="col-span-2">amount in satoshis</div> - <div class="font-bold">webhook</div> - <div class="col-span-2">(optional) endpoint to hit when the invoice is paid</div> - <div class="font-bold">secret</div> - <div class="col-span-2">a secret for the webhook to check</div> - </div> - </div> + <div class="text-secondary"> + <div>Request params</div> + <div class="grid grid-cols-3 ml-2"> + <div class="font-bold">type</div> + <div class="col-span-2">bitcoin or lightning</div> + <div class="font-bold">amount</div> + <div class="col-span-2">amount in satoshis</div> + <div class="font-bold">webhook</div> + <div class="col-span-2"> + (optional) endpoint to hit when the invoice is paid + </div> + <div class="font-bold">secret</div> + <div class="col-span-2">a secret for the webhook to check</div> + </div> + </div> - <p>Get a lightning invoice to receive funds</p> - <Code sample="lightningInvoice" /> + <p>Get a lightning invoice to receive funds</p> + <Code sample="lightningInvoice" /> - <p>Get a bitcoin address to receive funds</p> - <Code sample="bitcoinAddress" /> + <p>Get a bitcoin address to receive funds</p> + <Code sample="bitcoinAddress" /> - <p>Sample response</p> - <Code sample="invoiceResponse" /> - <p>You can check the <b>received</b> field to see how much has been paid</p> + <p>Sample response</p> + <Code sample="invoiceResponse" /> + <p>You can check the <b>received</b> field to see how much has been paid</p> - <p>Specify a webhook to be called when an invoice is paid</p> - <Code sample="webhook" /> + <p>Specify a webhook to be called when an invoice is paid</p> + <Code sample="webhook" /> - <h2 class="text-2xl">GET /invoice/:hash</h2> - <p class="text-secondary"> - Fetch an invoice by passing a bitcoin address or lightning payment hash - </p> - <Code sample="fetchInvoice" /> + <h2 class="text-2xl">GET /invoice/:hash</h2> + <p class="text-secondary"> + Fetch an invoice by passing a bitcoin address or lightning payment hash + </p> + <Code sample="fetchInvoice" /> - <h2 class="text-2xl">POST /payments</h2> - <p class="text-secondary">Send a lightning payment</p> - <Code sample="lightning" /> + <h2 class="text-2xl">POST /payments</h2> + <p class="text-secondary">Send a lightning payment</p> + <Code sample="lightning" /> - <p class="text-secondary">Send a bitcoin payment</p> - <Code sample="bitcoin" /> + <p class="text-secondary">Send a bitcoin payment</p> + <Code sample="bitcoin" /> - <p class="text-secondary">Send an internal payment to another user</p> - <Code sample="internal" /> + <p class="text-secondary">Send an internal payment to another user</p> + <Code sample="internal" /> - <h2 class="text-2xl">GET /payments</h2> - <p class="text-secondary">Get all payments sent or received by the current user</p> + <h2 class="text-2xl">GET /payments</h2> + <p class="text-secondary"> + Get all payments sent or received by the current user + </p> - <div class="text-secondary"> - <div>Query params</div> - <div class="grid grid-cols-3 ml-2"> - <div class="font-bold">start</div> - <div class="col-span-2">only payments after this unix time</div> - <div class="font-bold">end</div> - <div class="col-span-2">only payments before this unix time</div> - <div class="font-bold">limit</div> - <div class="col-span-2">limit to this integer number of results</div> - <div class="font-bold">offset</div> - <div class="col-span-2">start limiting from this integer offset</div> - </div> - </div> - <Code sample="payments" /> + <div class="text-secondary"> + <div>Query params</div> + <div class="grid grid-cols-3 ml-2"> + <div class="font-bold">start</div> + <div class="col-span-2">only payments after this unix time</div> + <div class="font-bold">end</div> + <div class="col-span-2">only payments before this unix time</div> + <div class="font-bold">limit</div> + <div class="col-span-2">limit to this integer number of results</div> + <div class="font-bold">offset</div> + <div class="col-span-2">start limiting from this integer offset</div> + </div> + </div> + <Code sample="payments" /> </div> diff --git a/src/routes/+layout.server.js b/src/routes/+layout.server.js index 60a2f6c6..5273d372 100644 --- a/src/routes/+layout.server.js +++ b/src/routes/+layout.server.js @@ -1,17 +1,17 @@ import { auth, get } from "$lib/utils"; -import { loadTranslations, locale } from '$lib/translations'; +import { loadTranslations, locale } from "$lib/translations"; export const load = async ({ cookies, url }) => { - let user; - let token = cookies.get('token'); - if (token) { - try { - user = await get('/me', auth(cookies)); - } catch (e) {} - } + let user; + let token = cookies.get("token"); + if (token) { + try { + user = await get("/me", auth(cookies)); + } catch (e) {} + } - const { host, pathname } = url; - const defaultLocale = user?.language || 'en'; // get from cookie, user session, ... - const initLocale = locale.get() || defaultLocale; // set default if no locale already set - await loadTranslations(initLocale, pathname); // keep this just before the `return` - return { host, pathname }; + const { host, pathname } = url; + const defaultLocale = user?.language || "en"; // get from cookie, user session, ... + const initLocale = locale.get() || defaultLocale; // set default if no locale already set + await loadTranslations(initLocale, pathname); // keep this just before the `return` + return { host, pathname }; }; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 32d31fa8..182be1cc 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,44 +1,47 @@ <script> - import { PUBLIC_DOMAIN } from '$env/static/public'; - import '../app.css'; - import { loading, t } from '$lib/translations'; + import { PUBLIC_DOMAIN } from "$env/static/public"; + import "../app.css"; + import { loading, t } from "$lib/translations"; - export let data; - let { pathname } = data; - let host = PUBLIC_DOMAIN.includes("localhost") ? `http://${PUBLIC_DOMAIN}` : `https://${PUBLIC_DOMAIN}` + export let data; + let { pathname } = data; + + let host = PUBLIC_DOMAIN.includes("localhost") + ? `http://${PUBLIC_DOMAIN}` + : `https://${PUBLIC_DOMAIN}`; </script> <svelte:head> - <title>coinos</title> - <meta property="og:title" content="coinos" /> - <meta name="twitter:title" content="coinos" /> + <title>coinos</title> + <meta property="og:title" content="coinos" /> + <meta name="twitter:title" content="coinos" /> - <meta property="og:image" content={`${host}/images/logo.png`} /> + <meta property="og:image" content={`${host}/images/logo.png`} /> - <meta property="og:type" content="website" /> - <meta property="og:description" content="An easy to use bitcoin web wallet" /> - <meta name="description" content="An easy to use bitcoin web wallet" /> + <meta property="og:type" content="website" /> + <meta property="og:description" content="An easy to use bitcoin web wallet" /> + <meta name="description" content="An easy to use bitcoin web wallet" /> - <meta name="keywords" content="coinos easy bitcoin web wallet" /> + <meta name="keywords" content="coinos easy bitcoin web wallet" /> - <meta name="twitter:image" content={`${host}/images/logo.png`} /> - <meta name="twitter:card" content="summary_large_image" /> + <meta name="twitter:image" content={`${host}/images/logo.png`} /> + <meta name="twitter:card" content="summary_large_image" /> - <meta property="og:url" content={host + pathname} /> - <meta name="twitter:site" content="@coinoswallet" /> - <meta name="twitter:creator" content="@coinoswallet" /> + <meta property="og:url" content={host + pathname} /> + <meta name="twitter:site" content="@coinoswallet" /> + <meta name="twitter:creator" content="@coinoswallet" /> </svelte:head> {#if !$loading} - <slot /> + <slot /> {/if} <style global> - :root { - --toastContainerTop: auto; - --toastContainerRight: auto; - --toastContainerBottom: 8rem; - --toastContainerLeft: calc(50vw - 8rem); - --toastBackground: #292929; - } + :root { + --toastContainerTop: auto; + --toastContainerRight: auto; + --toastContainerBottom: 8rem; + --toastContainerLeft: calc(50vw - 8rem); + --toastBackground: #292929; + } </style> diff --git a/src/routes/+page.server.js b/src/routes/+page.server.js index dbd7fdf7..904e543d 100644 --- a/src/routes/+page.server.js +++ b/src/routes/+page.server.js @@ -1,4 +1,4 @@ // export const prerender = true; export let load = async () => { - return { faqs: ['cost', 'compatibility', 'safety', 'pos', 'bitcoin'] }; + return { faqs: ["cost", "compatibility", "safety", "pos", "bitcoin"] }; }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 59d56bf1..a90eb4ae 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,114 +1,118 @@ <script> - import { - LandingHeader, - LandingHero, - LandingInfoCard, - HowItWorksCard, - FaqCard, - Image, - About, - Footer - } from '$comp'; + import { + LandingHeader, + LandingHero, + LandingInfoCard, + HowItWorksCard, + FaqCard, + Image, + About, + Footer, + } from "$comp"; - import { t } from '$lib/translations'; - import { onDestroy, onMount, tick } from 'svelte'; + import { t } from "$lib/translations"; + import { onDestroy, onMount, tick } from "svelte"; - export let data; - let { faqs, locations, user } = data; - let update = (data) => ({ faqs, locations, user } = data); - $: update(data); + export let data; + let { faqs, locations, user } = data; + let update = (data) => ({ faqs, locations, user } = data); + $: update(data); - let howItWorks; - let faq; - let about; + let howItWorks; + let faq; + let about; - let log = console.log; + let log = console.log; - const howItWorksSteps = [ - { image: 'hand', stepID: 'step1' }, - { image: 'bolt', stepID: 'step2' }, - { image: 'smile', stepID: 'step3' } - ]; + const howItWorksSteps = [ + { image: "hand", stepID: "step1" }, + { image: "bolt", stepID: "step2" }, + { image: "smile", stepID: "step3" }, + ]; - let loaded; + let loaded; - let observer; - let isIntersecting = false; + let observer; + let isIntersecting = false; - onMount(async () => { - observer = new IntersectionObserver((entries) => { - isIntersecting = isIntersecting || entries[0].isIntersecting; - }); + onMount(async () => { + observer = new IntersectionObserver((entries) => { + isIntersecting = isIntersecting || entries[0].isIntersecting; + }); - loaded = true; - await tick(); - observer.observe(document.getElementById('faq')); - }); + loaded = true; + await tick(); + observer.observe(document.getElementById("faq")); + }); - onDestroy(() => (loaded = false)); + onDestroy(() => (loaded = false)); </script> <svelte:head> - <script src="https://stamen-maps.a.ssl.fastly.net/js/tile.stamen.js"></script> + <script src="https://stamen-maps.a.ssl.fastly.net/js/tile.stamen.js"></script> </svelte:head> {#if loaded} - <LandingHeader {howItWorks} {faq} {about} {user} /> + <LandingHeader {howItWorks} {faq} {about} {user} /> - <main class="space-y-40 py-20 md:py-32 lg:py-36 xl:py-40 px-5 md:px-0"> - <LandingHero /> - <LandingInfoCard - image="lightning-qr" - title={$t('landing.info1.title')} - description={$t('landing.info1.description')} - /> + <main class="space-y-40 py-20 md:py-32 lg:py-36 xl:py-40 px-5 md:px-0"> + <LandingHero /> + <LandingInfoCard + image="lightning-qr" + title={$t("landing.info1.title")} + description={$t("landing.info1.description")} + /> - <LandingInfoCard image="phone-checkout" title={$t('landing.info2.title')} order="reverse"> - {$t('landing.info2.description1')} - <br /><br /> - {$t('landing.info2.description2')} - <br /><br /> - {@html $t('landing.info2.description3')} - </LandingInfoCard> + <LandingInfoCard + image="phone-checkout" + title={$t("landing.info2.title")} + order="reverse" + > + {$t("landing.info2.description1")} + <br /><br /> + {$t("landing.info2.description2")} + <br /><br /> + {@html $t("landing.info2.description3")} + </LandingInfoCard> - <div> - <LandingInfoCard - image="customize" - title={$t('landing.info3.title')} - description={$t('landing.info3.description')} - /> - <div bind:this={howItWorks} /> - <div id="about" /> - </div> + <div> + <LandingInfoCard + image="customize" + title={$t("landing.info3.title")} + description={$t("landing.info3.description")} + /> + <div bind:this={howItWorks} /> + <div id="about" /> + </div> - <div> - <h3 class="text-5xl font-medium mb-10 text-center"> - {$t('howItWorks.header')} - </h3> - <div class="grid lg:grid-cols-3 space-y-10 lg:space-y-0 text-center"> - {#each howItWorksSteps as step} - <HowItWorksCard image={step.image} stepID={step.stepID} /> - {/each} - </div> - <div bind:this={faq} /> - </div> + <div> + <h3 class="text-5xl font-medium mb-10 text-center"> + {$t("howItWorks.header")} + </h3> + <div class="grid lg:grid-cols-3 space-y-10 lg:space-y-0 text-center"> + {#each howItWorksSteps as step} + <HowItWorksCard image={step.image} stepID={step.stepID} /> + {/each} + </div> + <div bind:this={faq} /> + </div> - <div> - <div class="space-y-10"> - <h3 class="text-5xl font-medium text-center pt-40 -mt-40" id="faq"> - {$t('faq.header')} - </h3> - {#each faqs as f} - <FaqCard questionID={f} /> - {/each} - </div> - <div bind:this={about} /> - </div> + <div> + <div class="space-y-10"> + <h3 class="text-5xl font-medium text-center pt-40 -mt-40" id="faq"> + {$t("faq.header")} + </h3> + {#each faqs as f} + <FaqCard questionID={f} /> + {/each} + </div> + <div bind:this={about} /> + </div> - {#if isIntersecting} - <About /> - {/if} - </main> + {#if isIntersecting} + <About /> + {/if} + </main> - <Footer /> + <Footer /> {/if} diff --git a/src/routes/about/+server.js b/src/routes/about/+server.js index f8a37fb5..b6912cb4 100644 --- a/src/routes/about/+server.js +++ b/src/routes/about/+server.js @@ -1,5 +1,5 @@ -import { redirect } from '@sveltejs/kit'; +import { redirect } from "@sveltejs/kit"; export async function GET() { - throw new redirect(307, '/#about'); + throw new redirect(307, "/#about"); } diff --git a/src/routes/map/+page.server.js b/src/routes/map/+page.server.js index 7936e88b..616d28f6 100644 --- a/src/routes/map/+page.server.js +++ b/src/routes/map/+page.server.js @@ -1,12 +1,12 @@ -import { get } from '$lib/utils'; +import { get } from "$lib/utils"; // export const prerender = true; export let load = async () => { - let data = { locations: [] }; + let data = { locations: [] }; - try { - data = await get('/locations'); - } catch (e) {} + try { + data = await get("/locations"); + } catch (e) {} - return data; + return data; }; diff --git a/src/routes/map/+page.svelte b/src/routes/map/+page.svelte index baf067d7..8c5fc3f8 100644 --- a/src/routes/map/+page.svelte +++ b/src/routes/map/+page.svelte @@ -1,18 +1,18 @@ <script> - import { Icon, LocationsMap } from '$comp'; - export let data; + import { Icon, LocationsMap } from "$comp"; + export let data; - let { locations } = data; + let { locations } = data; </script> <div class="pt-4"> - <div class="w-[243px] mx-auto mb-4"> - <a href="/"> - <Icon icon="logo" /> - </a> - </div> + <div class="w-[243px] mx-auto mb-4"> + <a href="/"> + <Icon icon="logo" /> + </a> + </div> - <div class="flex justify-center items-center"> - <LocationsMap {locations} /> - </div> + <div class="flex justify-center items-center"> + <LocationsMap {locations} /> + </div> </div> diff --git a/static/lottie/success.json b/static/lottie/success.json index 6b01e066..adef99b9 100644 --- a/static/lottie/success.json +++ b/static/lottie/success.json @@ -1,1313 +1,1351 @@ { - "v": "5.7.1", - "fr": 30, - "ip": 0, - "op": 60, - "w": 300, - "h": 300, - "nm": "Comp 1", - "ddd": 0, - "assets": [], - "layers": [ - { - "ddd": 0, - "ind": 1, - "ty": 4, - "nm": "4", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [151, 151, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [-22.5, -7.5], - [0, 0] - ], - "o": [ - [0, 0], - [22.5, 7.5], - [0, 0] - ], - "v": [ - [61, 78.5], - [78, 100.5], - [94, 115.5] - ], - "c": false - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "st", - "c": { - "a": 0, - "k": [0.12941176470588237, 0.19607843137254902, 0.3843137254901961, 1], - "ix": 3 - }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 5, "ix": 5 }, - "lc": 2, - "lj": 1, - "ml": 4, - "bm": 0, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Shape 1", - "np": 3, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "tm", - "s": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 28.254, - "s": [0] - }, - { "t": 51, "s": [100] } - ], - "ix": 1 - }, - "e": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 17, - "s": [0] - }, - { "t": 46.2109375, "s": [100] } - ], - "ix": 2 - }, - "o": { "a": 0, "k": 0, "ix": 3 }, - "m": 1, - "ix": 2, - "nm": "Trim Paths 1", - "mn": "ADBE Vector Filter - Trim", - "hd": false - } - ], - "ip": 17, - "op": 617, - "st": 17, - "bm": 0 - }, - { - "ddd": 0, - "ind": 2, - "ty": 4, - "nm": "3", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 203, "ix": 10 }, - "p": { "a": 0, "k": [251.408, 59.449, 0], "ix": 2 }, - "a": { "a": 0, "k": [-103.936, 89.696, 0], "ix": 1 }, - "s": { "a": 0, "k": [91.456, 89.381, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [29.459, 25.084], - [5, -25.75], - [5.75, -0.75] - ], - "o": [ - [0, 0], - [-25.25, -21.5], - [-5.515, 28.405], - [-5.75, 0.75] - ], - "v": [ - [-75, 68.75], - [-107.25, 86.5], - [-94, 84], - [-128.75, 113] - ], - "c": false - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "st", - "c": { "a": 0, "k": [1, 0.713725490196, 0.211764720842, 1], "ix": 3 }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 5, "ix": 5 }, - "lc": 2, - "lj": 1, - "ml": 4, - "bm": 0, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Shape 1", - "np": 3, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "tm", - "s": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 28.254, - "s": [0] - }, - { "t": 51, "s": [100] } - ], - "ix": 1 - }, - "e": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 17, - "s": [0] - }, - { "t": 46.2109375, "s": [100] } - ], - "ix": 2 - }, - "o": { "a": 0, "k": 0, "ix": 3 }, - "m": 1, - "ix": 2, - "nm": "Trim Paths 1", - "mn": "ADBE Vector Filter - Trim", - "hd": false - } - ], - "ip": 17, - "op": 617, - "st": 17, - "bm": 0 - }, - { - "ddd": 0, - "ind": 3, - "ty": 4, - "nm": "2", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [-21, 347, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [20.5, 4.5], - [0, 0], - [18.25, 9.25], - [3.5, -3.25] - ], - "o": [ - [0, 0], - [-20.5, -4.5], - [0, 0], - [-18.25, -9.25], - [-3.5, 3.25] - ], - "v": [ - [90.75, -122.25], - [83.5, -110.5], - [81.25, -97.25], - [69.75, -93.25], - [61, -75.75] - ], - "c": false - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "st", - "c": { "a": 0, "k": [0.745098039216, 0.643137254902, 1, 1], "ix": 3 }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 5, "ix": 5 }, - "lc": 2, - "lj": 1, - "ml": 4, - "bm": 0, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Shape 1", - "np": 3, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "tm", - "s": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 28.254, - "s": [0] - }, - { "t": 51, "s": [100] } - ], - "ix": 1 - }, - "e": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 17, - "s": [0] - }, - { "t": 46.2109375, "s": [100] } - ], - "ix": 2 - }, - "o": { "a": 0, "k": 0, "ix": 3 }, - "m": 1, - "ix": 2, - "nm": "Trim Paths 1", - "mn": "ADBE Vector Filter - Trim", - "hd": false - } - ], - "ip": 17, - "op": 617, - "st": 17, - "bm": 0 - }, - { - "ddd": 0, - "ind": 4, - "ty": 4, - "nm": "1", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [147, 146, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [109.756, 101.856, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [33.75, 1] - ], - "o": [ - [0, 0], - [-33.75, -1] - ], - "v": [ - [-57.75, -75.75], - [-90.75, -107.5] - ], - "c": false - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "st", - "c": { "a": 0, "k": [1, 0.431372578939, 0.513725490196, 1], "ix": 3 }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 5, "ix": 5 }, - "lc": 2, - "lj": 1, - "ml": 4, - "bm": 0, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [-1.5, -0.25], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Shape 1", - "np": 3, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "tm", - "s": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 28.254, - "s": [0] - }, - { "t": 51, "s": [100] } - ], - "ix": 1 - }, - "e": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 17, - "s": [0] - }, - { "t": 46.2109375, "s": [100] } - ], - "ix": 2 - }, - "o": { "a": 0, "k": 0, "ix": 3 }, - "m": 1, - "ix": 2, - "nm": "Trim Paths 1", - "mn": "ADBE Vector Filter - Trim", - "hd": false - } - ], - "ip": 17, - "op": 617, - "st": 17, - "bm": 0 - }, - { - "ddd": 0, - "ind": 5, - "ty": 4, - "nm": "checkmark/success Outlines", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [150.06, 150.061, 0], "ix": 2 }, - "a": { "a": 0, "k": [99, 99, 0], "ix": 1 }, - "s": { - "a": 1, - "k": [ - { - "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, - "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, - "t": 0, - "s": [100, 100, 100] - }, - { - "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, - "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, - "t": 12, - "s": [27, 27, 100] - }, - { "t": 21, "s": [100, 100, 100] } - ], - "ix": 6 - } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [0.831, 1.409], - [0, 0], - [-0.622, 2.004], - [-1.844, 1.043], - [-2.083, -0.474], - [-1.215, -1.74], - [0, 0], - [-0.949, 0.537], - [0, 0], - [-2.041, -0.632], - [-1.075, -1.826], - [0.463, -2.047], - [1.756, -1.181] - ], - "o": [ - [-1.424, 0.805], - [0, 0], - [-0.93, -1.899], - [0.622, -2.006], - [1.844, -1.042], - [2.084, 0.474], - [0, 0], - [0.553, 0.939], - [0, 0], - [1.921, -0.899], - [2.042, 0.631], - [1.076, 1.823], - [-0.463, 2.047], - [0, 0] - ], - "v": [ - [-26.138, 30.587], - [-30.199, 29.498], - [-58.899, -19.189], - [-59.378, -25.261], - [-55.542, -30.001], - [-49.433, -30.884], - [-44.302, -27.441], - [-23.618, 7.647], - [-20.911, 8.371], - [47.571, -30.344], - [53.736, -30.76], - [58.584, -26.94], - [59.537, -20.918], - [56.086, -15.898] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [99.943, 103.024], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [83.631, 83.631], "ix": 3 }, - "r": { "a": 0, "k": -12.6, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [-47.479, -12.722], - [12.721, -47.478], - [47.478, 12.722], - [-12.723, 47.478] - ], - "o": [ - [47.479, 12.722], - [-12.723, 47.479], - [-47.478, -12.723], - [12.721, -47.479] - ], - "v": [ - [23.036, -85.967], - [85.969, 23.035], - [-23.035, 85.967], - [-85.966, -23.035] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { - "a": 1, - "k": [ - { - "i": { "x": [0.833], "y": [0.833] }, - "o": { "x": [0.167], "y": [0.167] }, - "t": 0, - "s": [0.901960790157, 0.901960790157, 0.901960790157, 1] - }, - { - "i": { "x": [0.833], "y": [0.833] }, - "o": { "x": [0.167], "y": [0.167] }, - "t": 10, - "s": [0.901960790157, 0.901960790157, 0.901960790157, 1] - }, - { "t": 29, "s": [0.262745112181, 0.823529422283, 0.588235318661, 1] } - ], - "ix": 4 - }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [98.939, 98.939], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 2", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 2, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 600, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 6, - "ty": 4, - "nm": "large-circles/success Outlines", - "sr": 1, - "ks": { - "o": { - "a": 1, - "k": [ - { - "i": { "x": [0.833], "y": [0.833] }, - "o": { "x": [0.167], "y": [0.167] }, - "t": 42, - "s": [100] - }, - { "t": 52, "s": [2] } - ], - "ix": 11 - }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [148, 146, 0], "ix": 2 }, - "a": { "a": 0, "k": [95.5, 98, 0], "ix": 1 }, - "s": { - "a": 1, - "k": [ - { - "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, - "o": { "x": [0.009, 0.009, 0.333], "y": [1.621, 1.621, 0] }, - "t": 17, - "s": [62, 62, 100] - }, - { "t": 50, "s": [124, 124, 100] } - ], - "ix": 6 - } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [-6.209, 1.338], - [-1.337, -6.209], - [6.209, -1.337], - [1.338, 6.209] - ], - "o": [ - [6.208, -1.337], - [1.338, 6.209], - [-6.209, 1.338], - [-1.337, -6.209] - ], - "v": [ - [-2.421, -11.243], - [11.242, -2.422], - [2.421, 11.241], - [-11.243, 2.421] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [0.745000023935, 0.642999985639, 1, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [173.998, 145.423], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [71, 71], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [-6.209, 1.337], - [-1.338, -6.209], - [6.209, -1.338], - [1.338, 6.209] - ], - "o": [ - [6.209, -1.338], - [1.337, 6.209], - [-6.209, 1.337], - [-1.337, -6.208] - ], - "v": [ - [-2.421, -11.242], - [11.242, -2.422], - [2.422, 11.242], - [-11.243, 2.422] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { - "a": 0, - "k": [0.011764705882352941, 0.45098039215686275, 0.8901960784313725, 1], - "ix": 4 - }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [63.997, 183.011], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [70, 70], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 2", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 2, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [-6.209, 1.337], - [-1.337, -6.208], - [6.209, -1.337], - [1.338, 6.209] - ], - "o": [ - [6.209, -1.337], - [1.338, 6.209], - [-6.209, 1.338], - [-1.337, -6.209] - ], - "v": [ - [-2.421, -11.243], - [11.242, -2.423], - [2.421, 11.242], - [-11.243, 2.421] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [1, 0.713999968884, 0.211999990426, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [103.291, 12.83], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [70, 70], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 3", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 3, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [-6.208, 1.337], - [-1.337, -6.209], - [6.209, -1.338], - [1.338, 6.209] - ], - "o": [ - [6.209, -1.337], - [1.338, 6.209], - [-6.209, 1.337], - [-1.337, -6.209] - ], - "v": [ - [-2.423, -11.242], - [11.241, -2.422], - [2.421, 11.242], - [-11.243, 2.422] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { - "a": 0, - "k": [0.011764705882352941, 0.45098039215686275, 0.8901960784313725, 1], - "ix": 4 - }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [178.004, 59.795], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [67, 67], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 4", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 4, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [-6.209, 1.337], - [-1.338, -6.208], - [6.209, -1.337], - [1.338, 6.209] - ], - "o": [ - [6.209, -1.337], - [1.337, 6.209], - [-6.209, 1.338], - [-1.337, -6.209] - ], - "v": [ - [-2.421, -11.243], - [11.243, -2.423], - [2.422, 11.242], - [-11.243, 2.421] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [1, 0.430999995213, 0.513999968884, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [12.829, 84.352], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [68, 68], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 5", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 5, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 17, - "op": 617, - "st": 17, - "bm": 0 - }, - { - "ddd": 0, - "ind": 7, - "ty": 4, - "nm": "checkmark/success Outlines", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [150.073, 150.073, 0], "ix": 2 }, - "a": { "a": 0, "k": [99, 99, 0], "ix": 1 }, - "s": { - "a": 1, - "k": [ - { - "i": { "x": [0.203, 0.203, 0.667], "y": [0.507, 0.507, 1] }, - "o": { "x": [0.915, 0.915, 0.333], "y": [0.651, 0.651, 0] }, - "t": 50, - "s": [93.357, 93.357, 100] - }, - { "t": 59, "s": [121.357, 121.357, 100] } - ], - "ix": 6 - } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [0.831, 1.409], - [0, 0], - [-0.622, 2.004], - [-1.844, 1.043], - [-2.083, -0.474], - [-1.215, -1.74], - [0, 0], - [-0.949, 0.537], - [0, 0], - [-2.041, -0.632], - [-1.075, -1.826], - [0.463, -2.047], - [1.756, -1.181] - ], - "o": [ - [-1.424, 0.805], - [0, 0], - [-0.93, -1.899], - [0.622, -2.006], - [1.844, -1.042], - [2.084, 0.474], - [0, 0], - [0.553, 0.939], - [0, 0], - [1.921, -0.899], - [2.042, 0.631], - [1.076, 1.823], - [-0.463, 2.047], - [0, 0] - ], - "v": [ - [-26.138, 30.587], - [-30.199, 29.498], - [-58.899, -19.189], - [-59.378, -25.261], - [-55.542, -30.001], - [-49.433, -30.884], - [-44.302, -27.441], - [-23.618, 7.647], - [-20.911, 8.371], - [47.571, -30.344], - [53.736, -30.76], - [58.584, -26.94], - [59.537, -20.918], - [56.086, -15.898] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [98.943, 102.024], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [-47.479, -12.722], - [12.721, -47.478], - [47.478, 12.722], - [-12.723, 47.478] - ], - "o": [ - [47.479, 12.722], - [-12.723, 47.479], - [-47.478, -12.723], - [12.721, -47.479] - ], - "v": [ - [23.036, -85.967], - [85.969, 23.035], - [-23.035, 85.967], - [-85.966, -23.035] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { - "a": 0, - "k": [0.2235294117647059, 0.7843137254901961, 0.32941176470588235, 1], - "ix": 4 - }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [98.939, 98.939], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 2", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 2, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 50, - "op": 650, - "st": 50, - "bm": 0 - } - ], - "markers": [] + "v": "5.7.1", + "fr": 30, + "ip": 0, + "op": 60, + "w": 300, + "h": 300, + "nm": "Comp 1", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "4", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [151, 151, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, 0], + [-22.5, -7.5], + [0, 0] + ], + "o": [ + [0, 0], + [22.5, 7.5], + [0, 0] + ], + "v": [ + [61, 78.5], + [78, 100.5], + [94, 115.5] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.12941176470588237, 0.19607843137254902, 0.3843137254901961, + 1 + ], + "ix": 3 + }, + "o": { "a": 0, "k": 100, "ix": 4 }, + "w": { "a": 0, "k": 5, "ix": 5 }, + "lc": 2, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "t": 28.254, + "s": [0] + }, + { "t": 51, "s": [100] } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "t": 17, + "s": [0] + }, + { "t": 46.2109375, "s": [100] } + ], + "ix": 2 + }, + "o": { "a": 0, "k": 0, "ix": 3 }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 17, + "op": 617, + "st": 17, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "3", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 203, "ix": 10 }, + "p": { "a": 0, "k": [251.408, 59.449, 0], "ix": 2 }, + "a": { "a": 0, "k": [-103.936, 89.696, 0], "ix": 1 }, + "s": { "a": 0, "k": [91.456, 89.381, 100], "ix": 6 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, 0], + [29.459, 25.084], + [5, -25.75], + [5.75, -0.75] + ], + "o": [ + [0, 0], + [-25.25, -21.5], + [-5.515, 28.405], + [-5.75, 0.75] + ], + "v": [ + [-75, 68.75], + [-107.25, 86.5], + [-94, 84], + [-128.75, 113] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [1, 0.713725490196, 0.211764720842, 1], + "ix": 3 + }, + "o": { "a": 0, "k": 100, "ix": 4 }, + "w": { "a": 0, "k": 5, "ix": 5 }, + "lc": 2, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "t": 28.254, + "s": [0] + }, + { "t": 51, "s": [100] } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "t": 17, + "s": [0] + }, + { "t": 46.2109375, "s": [100] } + ], + "ix": 2 + }, + "o": { "a": 0, "k": 0, "ix": 3 }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 17, + "op": 617, + "st": 17, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "2", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [-21, 347, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, 0], + [20.5, 4.5], + [0, 0], + [18.25, 9.25], + [3.5, -3.25] + ], + "o": [ + [0, 0], + [-20.5, -4.5], + [0, 0], + [-18.25, -9.25], + [-3.5, 3.25] + ], + "v": [ + [90.75, -122.25], + [83.5, -110.5], + [81.25, -97.25], + [69.75, -93.25], + [61, -75.75] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [0.745098039216, 0.643137254902, 1, 1], + "ix": 3 + }, + "o": { "a": 0, "k": 100, "ix": 4 }, + "w": { "a": 0, "k": 5, "ix": 5 }, + "lc": 2, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "t": 28.254, + "s": [0] + }, + { "t": 51, "s": [100] } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "t": 17, + "s": [0] + }, + { "t": 46.2109375, "s": [100] } + ], + "ix": 2 + }, + "o": { "a": 0, "k": 0, "ix": 3 }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 17, + "op": 617, + "st": 17, + "bm": 0 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "1", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [147, 146, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, + "s": { "a": 0, "k": [109.756, 101.856, 100], "ix": 6 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, 0], + [33.75, 1] + ], + "o": [ + [0, 0], + [-33.75, -1] + ], + "v": [ + [-57.75, -75.75], + [-90.75, -107.5] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [1, 0.431372578939, 0.513725490196, 1], + "ix": 3 + }, + "o": { "a": 0, "k": 100, "ix": 4 }, + "w": { "a": 0, "k": 5, "ix": 5 }, + "lc": 2, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-1.5, -0.25], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "t": 28.254, + "s": [0] + }, + { "t": 51, "s": [100] } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "t": 17, + "s": [0] + }, + { "t": 46.2109375, "s": [100] } + ], + "ix": 2 + }, + "o": { "a": 0, "k": 0, "ix": 3 }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 17, + "op": 617, + "st": 17, + "bm": 0 + }, + { + "ddd": 0, + "ind": 5, + "ty": 4, + "nm": "checkmark/success Outlines", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [150.06, 150.061, 0], "ix": 2 }, + "a": { "a": 0, "k": [99, 99, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, + "t": 0, + "s": [100, 100, 100] + }, + { + "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, + "t": 12, + "s": [27, 27, 100] + }, + { "t": 21, "s": [100, 100, 100] } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, 0], + [0.831, 1.409], + [0, 0], + [-0.622, 2.004], + [-1.844, 1.043], + [-2.083, -0.474], + [-1.215, -1.74], + [0, 0], + [-0.949, 0.537], + [0, 0], + [-2.041, -0.632], + [-1.075, -1.826], + [0.463, -2.047], + [1.756, -1.181] + ], + "o": [ + [-1.424, 0.805], + [0, 0], + [-0.93, -1.899], + [0.622, -2.006], + [1.844, -1.042], + [2.084, 0.474], + [0, 0], + [0.553, 0.939], + [0, 0], + [1.921, -0.899], + [2.042, 0.631], + [1.076, 1.823], + [-0.463, 2.047], + [0, 0] + ], + "v": [ + [-26.138, 30.587], + [-30.199, 29.498], + [-58.899, -19.189], + [-59.378, -25.261], + [-55.542, -30.001], + [-49.433, -30.884], + [-44.302, -27.441], + [-23.618, 7.647], + [-20.911, 8.371], + [47.571, -30.344], + [53.736, -30.76], + [58.584, -26.94], + [59.537, -20.918], + [56.086, -15.898] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [99.943, 103.024], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [83.631, 83.631], "ix": 3 }, + "r": { "a": 0, "k": -12.6, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [-47.479, -12.722], + [12.721, -47.478], + [47.478, 12.722], + [-12.723, 47.478] + ], + "o": [ + [47.479, 12.722], + [-12.723, 47.479], + [-47.478, -12.723], + [12.721, -47.479] + ], + "v": [ + [23.036, -85.967], + [85.969, 23.035], + [-23.035, 85.967], + [-85.966, -23.035] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "t": 0, + "s": [0.901960790157, 0.901960790157, 0.901960790157, 1] + }, + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "t": 10, + "s": [0.901960790157, 0.901960790157, 0.901960790157, 1] + }, + { + "t": 29, + "s": [0.262745112181, 0.823529422283, 0.588235318661, 1] + } + ], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [98.939, 98.939], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 2", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 2, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 600, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 6, + "ty": 4, + "nm": "large-circles/success Outlines", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "t": 42, + "s": [100] + }, + { "t": 52, "s": [2] } + ], + "ix": 11 + }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [148, 146, 0], "ix": 2 }, + "a": { "a": 0, "k": [95.5, 98, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, + "o": { "x": [0.009, 0.009, 0.333], "y": [1.621, 1.621, 0] }, + "t": 17, + "s": [62, 62, 100] + }, + { "t": 50, "s": [124, 124, 100] } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [-6.209, 1.338], + [-1.337, -6.209], + [6.209, -1.337], + [1.338, 6.209] + ], + "o": [ + [6.208, -1.337], + [1.338, 6.209], + [-6.209, 1.338], + [-1.337, -6.209] + ], + "v": [ + [-2.421, -11.243], + [11.242, -2.422], + [2.421, 11.241], + [-11.243, 2.421] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.745000023935, 0.642999985639, 1, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [173.998, 145.423], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [71, 71], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [-6.209, 1.337], + [-1.338, -6.209], + [6.209, -1.338], + [1.338, 6.209] + ], + "o": [ + [6.209, -1.338], + [1.337, 6.209], + [-6.209, 1.337], + [-1.337, -6.208] + ], + "v": [ + [-2.421, -11.242], + [11.242, -2.422], + [2.422, 11.242], + [-11.243, 2.422] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.011764705882352941, 0.45098039215686275, 0.8901960784313725, + 1 + ], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [63.997, 183.011], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [70, 70], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 2", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 2, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [-6.209, 1.337], + [-1.337, -6.208], + [6.209, -1.337], + [1.338, 6.209] + ], + "o": [ + [6.209, -1.337], + [1.338, 6.209], + [-6.209, 1.338], + [-1.337, -6.209] + ], + "v": [ + [-2.421, -11.243], + [11.242, -2.423], + [2.421, 11.242], + [-11.243, 2.421] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [1, 0.713999968884, 0.211999990426, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [103.291, 12.83], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [70, 70], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 3", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 3, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [-6.208, 1.337], + [-1.337, -6.209], + [6.209, -1.338], + [1.338, 6.209] + ], + "o": [ + [6.209, -1.337], + [1.338, 6.209], + [-6.209, 1.337], + [-1.337, -6.209] + ], + "v": [ + [-2.423, -11.242], + [11.241, -2.422], + [2.421, 11.242], + [-11.243, 2.422] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.011764705882352941, 0.45098039215686275, 0.8901960784313725, + 1 + ], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [178.004, 59.795], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [67, 67], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 4", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 4, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [-6.209, 1.337], + [-1.338, -6.208], + [6.209, -1.337], + [1.338, 6.209] + ], + "o": [ + [6.209, -1.337], + [1.337, 6.209], + [-6.209, 1.338], + [-1.337, -6.209] + ], + "v": [ + [-2.421, -11.243], + [11.243, -2.423], + [2.422, 11.242], + [-11.243, 2.421] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [1, 0.430999995213, 0.513999968884, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [12.829, 84.352], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [68, 68], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 5", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 5, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 17, + "op": 617, + "st": 17, + "bm": 0 + }, + { + "ddd": 0, + "ind": 7, + "ty": 4, + "nm": "checkmark/success Outlines", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [150.073, 150.073, 0], "ix": 2 }, + "a": { "a": 0, "k": [99, 99, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.203, 0.203, 0.667], "y": [0.507, 0.507, 1] }, + "o": { "x": [0.915, 0.915, 0.333], "y": [0.651, 0.651, 0] }, + "t": 50, + "s": [93.357, 93.357, 100] + }, + { "t": 59, "s": [121.357, 121.357, 100] } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, 0], + [0.831, 1.409], + [0, 0], + [-0.622, 2.004], + [-1.844, 1.043], + [-2.083, -0.474], + [-1.215, -1.74], + [0, 0], + [-0.949, 0.537], + [0, 0], + [-2.041, -0.632], + [-1.075, -1.826], + [0.463, -2.047], + [1.756, -1.181] + ], + "o": [ + [-1.424, 0.805], + [0, 0], + [-0.93, -1.899], + [0.622, -2.006], + [1.844, -1.042], + [2.084, 0.474], + [0, 0], + [0.553, 0.939], + [0, 0], + [1.921, -0.899], + [2.042, 0.631], + [1.076, 1.823], + [-0.463, 2.047], + [0, 0] + ], + "v": [ + [-26.138, 30.587], + [-30.199, 29.498], + [-58.899, -19.189], + [-59.378, -25.261], + [-55.542, -30.001], + [-49.433, -30.884], + [-44.302, -27.441], + [-23.618, 7.647], + [-20.911, 8.371], + [47.571, -30.344], + [53.736, -30.76], + [58.584, -26.94], + [59.537, -20.918], + [56.086, -15.898] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [98.943, 102.024], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [-47.479, -12.722], + [12.721, -47.478], + [47.478, 12.722], + [-12.723, 47.478] + ], + "o": [ + [47.479, 12.722], + [-12.723, 47.479], + [-47.478, -12.723], + [12.721, -47.479] + ], + "v": [ + [23.036, -85.967], + [85.969, 23.035], + [-23.035, 85.967], + [-85.966, -23.035] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.2235294117647059, 0.7843137254901961, 0.32941176470588235, 1 + ], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [98.939, 98.939], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 2", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 2, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 50, + "op": 650, + "st": 50, + "bm": 0 + } + ], + "markers": [] } diff --git a/static/manifest.webmanifest b/static/manifest.webmanifest index c9243a94..f1cdeec1 100644 --- a/static/manifest.webmanifest +++ b/static/manifest.webmanifest @@ -1,38 +1,38 @@ { - "theme_color": "#fff", - "background_color": "#000", - "display": "fullscreen", - "scope": "/", - "start_url": "/", - "name": "Coinos", - "description": "Bitcoin web wallet", - "short_name": "Coinos", - "icons": [ - { - "src": "/icon-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/icon-256x256.png", - "sizes": "256x256", - "type": "image/png" - }, - { - "src": "/icon-384x384.png", - "sizes": "384x384", - "type": "image/png" - }, - { - "src": "/icon-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "protocol_handlers": [ - { - "protocol": "bitcoin", - "url": "/send/%s" - } - ] + "theme_color": "#fff", + "background_color": "#000", + "display": "fullscreen", + "scope": "/", + "start_url": "/", + "name": "Coinos", + "description": "Bitcoin web wallet", + "short_name": "Coinos", + "icons": [ + { + "src": "/icon-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icon-256x256.png", + "sizes": "256x256", + "type": "image/png" + }, + { + "src": "/icon-384x384.png", + "sizes": "384x384", + "type": "image/png" + }, + { + "src": "/icon-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "protocol_handlers": [ + { + "protocol": "bitcoin", + "url": "/send/%s" + } + ] } diff --git a/static/qr.js b/static/qr.js index 92aa4ec1..e8a68e2b 100644 --- a/static/qr.js +++ b/static/qr.js @@ -19,7 +19,7 @@ /* * @Modified: Pudon * @demoURL: https://github.com/Pudon/weapp-qrcode - * @Date: 2018-09-11 14:00:05 + * @Date: 2018-09-11 14:00:05 * @Last Modified by: Pudon * @Last Modified time: 2018-09-12 16:33:19 */ @@ -30,457 +30,434 @@ * @param typeNumber 1 to 40 * @param errorCorrectLevel 'L','M','Q','H' */ -var qrcode = function(typeNumber, errorCorrectLevel) { +var qrcode = function (typeNumber, errorCorrectLevel) { + var PAD0 = 0xec; + var PAD1 = 0x11; - var PAD0 = 0xEC; - var PAD1 = 0x11; + var _typeNumber = typeNumber; + var _errorCorrectLevel = QRErrorCorrectLevel[errorCorrectLevel]; + var _modules = null; + var _moduleCount = 0; + var _dataCache = null; + var _dataList = new Array(); - var _typeNumber = typeNumber; - var _errorCorrectLevel = QRErrorCorrectLevel[errorCorrectLevel]; - var _modules = null; - var _moduleCount = 0; - var _dataCache = null; - var _dataList = new Array(); + var _this = {}; - var _this = {}; - - var makeImpl = function(test, maskPattern) { - - _moduleCount = _typeNumber * 4 + 17; - _modules = function(moduleCount) { - var modules = new Array(moduleCount); - for (var row = 0; row < moduleCount; row += 1) { - modules[row] = new Array(moduleCount); - for (var col = 0; col < moduleCount; col += 1) { - modules[row][col] = null; - } - } - return modules; - }(_moduleCount); - - setupPositionProbePattern(0, 0); - setupPositionProbePattern(_moduleCount - 7, 0); - setupPositionProbePattern(0, _moduleCount - 7); - setupPositionAdjustPattern(); - setupTimingPattern(); - setupTypeInfo(test, maskPattern); - - if (_typeNumber >= 7) { - setupTypeNumber(test); - } - - if (_dataCache == null) { - _dataCache = createData(_typeNumber, _errorCorrectLevel, _dataList); - } - - mapData(_dataCache, maskPattern); - }; - - var setupPositionProbePattern = function(row, col) { - - for (var r = -1; r <= 7; r += 1) { - - if (row + r <= -1 || _moduleCount <= row + r) continue; - - for (var c = -1; c <= 7; c += 1) { - - if (col + c <= -1 || _moduleCount <= col + c) continue; - - if ( (0 <= r && r <= 6 && (c == 0 || c == 6) ) - || (0 <= c && c <= 6 && (r == 0 || r == 6) ) - || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) { - _modules[row + r][col + c] = true; - } else { - _modules[row + r][col + c] = false; - } - } - } - }; - - var getBestMaskPattern = function() { - - var minLostPoint = 0; - var pattern = 0; - - for (var i = 0; i < 8; i += 1) { - - makeImpl(true, i); - - var lostPoint = QRUtil.getLostPoint(_this); - - if (i == 0 || minLostPoint > lostPoint) { - minLostPoint = lostPoint; - pattern = i; - } - } - - return pattern; - }; - - var setupTimingPattern = function() { - - for (var r = 8; r < _moduleCount - 8; r += 1) { - if (_modules[r][6] != null) { - continue; - } - _modules[r][6] = (r % 2 == 0); - } - - for (var c = 8; c < _moduleCount - 8; c += 1) { - if (_modules[6][c] != null) { - continue; - } - _modules[6][c] = (c % 2 == 0); - } - }; - - var setupPositionAdjustPattern = function() { - - var pos = QRUtil.getPatternPosition(_typeNumber); - - for (var i = 0; i < pos.length; i += 1) { - - for (var j = 0; j < pos.length; j += 1) { - - var row = pos[i]; - var col = pos[j]; - - if (_modules[row][col] != null) { - continue; - } - - for (var r = -2; r <= 2; r += 1) { + var makeImpl = function (test, maskPattern) { + _moduleCount = _typeNumber * 4 + 17; + _modules = (function (moduleCount) { + var modules = new Array(moduleCount); + for (var row = 0; row < moduleCount; row += 1) { + modules[row] = new Array(moduleCount); + for (var col = 0; col < moduleCount; col += 1) { + modules[row][col] = null; + } + } + return modules; + })(_moduleCount); - for (var c = -2; c <= 2; c += 1) { + setupPositionProbePattern(0, 0); + setupPositionProbePattern(_moduleCount - 7, 0); + setupPositionProbePattern(0, _moduleCount - 7); + setupPositionAdjustPattern(); + setupTimingPattern(); + setupTypeInfo(test, maskPattern); - if (r == -2 || r == 2 || c == -2 || c == 2 - || (r == 0 && c == 0) ) { - _modules[row + r][col + c] = true; - } else { - _modules[row + r][col + c] = false; - } - } - } - } - } - }; + if (_typeNumber >= 7) { + setupTypeNumber(test); + } - var setupTypeNumber = function(test) { + if (_dataCache == null) { + _dataCache = createData(_typeNumber, _errorCorrectLevel, _dataList); + } - var bits = QRUtil.getBCHTypeNumber(_typeNumber); + mapData(_dataCache, maskPattern); + }; - for (var i = 0; i < 18; i += 1) { - var mod = (!test && ( (bits >> i) & 1) == 1); - _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod; - } + var setupPositionProbePattern = function (row, col) { + for (var r = -1; r <= 7; r += 1) { + if (row + r <= -1 || _moduleCount <= row + r) continue; - for (var i = 0; i < 18; i += 1) { - var mod = (!test && ( (bits >> i) & 1) == 1); - _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod; - } - }; + for (var c = -1; c <= 7; c += 1) { + if (col + c <= -1 || _moduleCount <= col + c) continue; - var setupTypeInfo = function(test, maskPattern) { + if ( + (0 <= r && r <= 6 && (c == 0 || c == 6)) || + (0 <= c && c <= 6 && (r == 0 || r == 6)) || + (2 <= r && r <= 4 && 2 <= c && c <= 4) + ) { + _modules[row + r][col + c] = true; + } else { + _modules[row + r][col + c] = false; + } + } + } + }; - var data = (_errorCorrectLevel << 3) | maskPattern; - var bits = QRUtil.getBCHTypeInfo(data); + var getBestMaskPattern = function () { + var minLostPoint = 0; + var pattern = 0; - // vertical - for (var i = 0; i < 15; i += 1) { + for (var i = 0; i < 8; i += 1) { + makeImpl(true, i); - var mod = (!test && ( (bits >> i) & 1) == 1); + var lostPoint = QRUtil.getLostPoint(_this); - if (i < 6) { - _modules[i][8] = mod; - } else if (i < 8) { - _modules[i + 1][8] = mod; - } else { - _modules[_moduleCount - 15 + i][8] = mod; - } - } + if (i == 0 || minLostPoint > lostPoint) { + minLostPoint = lostPoint; + pattern = i; + } + } - // horizontal - for (var i = 0; i < 15; i += 1) { + return pattern; + }; - var mod = (!test && ( (bits >> i) & 1) == 1); + var setupTimingPattern = function () { + for (var r = 8; r < _moduleCount - 8; r += 1) { + if (_modules[r][6] != null) { + continue; + } + _modules[r][6] = r % 2 == 0; + } - if (i < 8) { - _modules[8][_moduleCount - i - 1] = mod; - } else if (i < 9) { - _modules[8][15 - i - 1 + 1] = mod; - } else { - _modules[8][15 - i - 1] = mod; - } - } + for (var c = 8; c < _moduleCount - 8; c += 1) { + if (_modules[6][c] != null) { + continue; + } + _modules[6][c] = c % 2 == 0; + } + }; - // fixed module - _modules[_moduleCount - 8][8] = (!test); - }; + var setupPositionAdjustPattern = function () { + var pos = QRUtil.getPatternPosition(_typeNumber); - var mapData = function(data, maskPattern) { + for (var i = 0; i < pos.length; i += 1) { + for (var j = 0; j < pos.length; j += 1) { + var row = pos[i]; + var col = pos[j]; - var inc = -1; - var row = _moduleCount - 1; - var bitIndex = 7; - var byteIndex = 0; - var maskFunc = QRUtil.getMaskFunction(maskPattern); + if (_modules[row][col] != null) { + continue; + } - for (var col = _moduleCount - 1; col > 0; col -= 2) { + for (var r = -2; r <= 2; r += 1) { + for (var c = -2; c <= 2; c += 1) { + if (r == -2 || r == 2 || c == -2 || c == 2 || (r == 0 && c == 0)) { + _modules[row + r][col + c] = true; + } else { + _modules[row + r][col + c] = false; + } + } + } + } + } + }; - if (col == 6) col -= 1; + var setupTypeNumber = function (test) { + var bits = QRUtil.getBCHTypeNumber(_typeNumber); - while (true) { + for (var i = 0; i < 18; i += 1) { + var mod = !test && ((bits >> i) & 1) == 1; + _modules[Math.floor(i / 3)][(i % 3) + _moduleCount - 8 - 3] = mod; + } - for (var c = 0; c < 2; c += 1) { + for (var i = 0; i < 18; i += 1) { + var mod = !test && ((bits >> i) & 1) == 1; + _modules[(i % 3) + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod; + } + }; - if (_modules[row][col - c] == null) { + var setupTypeInfo = function (test, maskPattern) { + var data = (_errorCorrectLevel << 3) | maskPattern; + var bits = QRUtil.getBCHTypeInfo(data); - var dark = false; + // vertical + for (var i = 0; i < 15; i += 1) { + var mod = !test && ((bits >> i) & 1) == 1; - if (byteIndex < data.length) { - dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1); - } + if (i < 6) { + _modules[i][8] = mod; + } else if (i < 8) { + _modules[i + 1][8] = mod; + } else { + _modules[_moduleCount - 15 + i][8] = mod; + } + } - var mask = maskFunc(row, col - c); + // horizontal + for (var i = 0; i < 15; i += 1) { + var mod = !test && ((bits >> i) & 1) == 1; - if (mask) { - dark = !dark; - } + if (i < 8) { + _modules[8][_moduleCount - i - 1] = mod; + } else if (i < 9) { + _modules[8][15 - i - 1 + 1] = mod; + } else { + _modules[8][15 - i - 1] = mod; + } + } - _modules[row][col - c] = dark; - bitIndex -= 1; + // fixed module + _modules[_moduleCount - 8][8] = !test; + }; - if (bitIndex == -1) { - byteIndex += 1; - bitIndex = 7; - } - } - } + var mapData = function (data, maskPattern) { + var inc = -1; + var row = _moduleCount - 1; + var bitIndex = 7; + var byteIndex = 0; + var maskFunc = QRUtil.getMaskFunction(maskPattern); - row += inc; + for (var col = _moduleCount - 1; col > 0; col -= 2) { + if (col == 6) col -= 1; - if (row < 0 || _moduleCount <= row) { - row -= inc; - inc = -inc; - break; - } - } - } - }; + while (true) { + for (var c = 0; c < 2; c += 1) { + if (_modules[row][col - c] == null) { + var dark = false; - var createBytes = function(buffer, rsBlocks) { + if (byteIndex < data.length) { + dark = ((data[byteIndex] >>> bitIndex) & 1) == 1; + } - var offset = 0; + var mask = maskFunc(row, col - c); - var maxDcCount = 0; - var maxEcCount = 0; + if (mask) { + dark = !dark; + } - var dcdata = new Array(rsBlocks.length); - var ecdata = new Array(rsBlocks.length); + _modules[row][col - c] = dark; + bitIndex -= 1; - for (var r = 0; r < rsBlocks.length; r += 1) { + if (bitIndex == -1) { + byteIndex += 1; + bitIndex = 7; + } + } + } - var dcCount = rsBlocks[r].dataCount; - var ecCount = rsBlocks[r].totalCount - dcCount; + row += inc; - maxDcCount = Math.max(maxDcCount, dcCount); - maxEcCount = Math.max(maxEcCount, ecCount); + if (row < 0 || _moduleCount <= row) { + row -= inc; + inc = -inc; + break; + } + } + } + }; - dcdata[r] = new Array(dcCount); + var createBytes = function (buffer, rsBlocks) { + var offset = 0; - for (var i = 0; i < dcdata[r].length; i += 1) { - dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset]; - } - offset += dcCount; + var maxDcCount = 0; + var maxEcCount = 0; - var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); - var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1); + var dcdata = new Array(rsBlocks.length); + var ecdata = new Array(rsBlocks.length); - var modPoly = rawPoly.mod(rsPoly); - ecdata[r] = new Array(rsPoly.getLength() - 1); - for (var i = 0; i < ecdata[r].length; i += 1) { - var modIndex = i + modPoly.getLength() - ecdata[r].length; - ecdata[r][i] = (modIndex >= 0)? modPoly.getAt(modIndex) : 0; - } - } + for (var r = 0; r < rsBlocks.length; r += 1) { + var dcCount = rsBlocks[r].dataCount; + var ecCount = rsBlocks[r].totalCount - dcCount; - var totalCodeCount = 0; - for (var i = 0; i < rsBlocks.length; i += 1) { - totalCodeCount += rsBlocks[i].totalCount; - } + maxDcCount = Math.max(maxDcCount, dcCount); + maxEcCount = Math.max(maxEcCount, ecCount); - var data = new Array(totalCodeCount); - var index = 0; + dcdata[r] = new Array(dcCount); - for (var i = 0; i < maxDcCount; i += 1) { - for (var r = 0; r < rsBlocks.length; r += 1) { - if (i < dcdata[r].length) { - data[index] = dcdata[r][i]; - index += 1; - } - } - } + for (var i = 0; i < dcdata[r].length; i += 1) { + dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset]; + } + offset += dcCount; - for (var i = 0; i < maxEcCount; i += 1) { - for (var r = 0; r < rsBlocks.length; r += 1) { - if (i < ecdata[r].length) { - data[index] = ecdata[r][i]; - index += 1; - } - } - } + var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); + var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1); - return data; - }; + var modPoly = rawPoly.mod(rsPoly); + ecdata[r] = new Array(rsPoly.getLength() - 1); + for (var i = 0; i < ecdata[r].length; i += 1) { + var modIndex = i + modPoly.getLength() - ecdata[r].length; + ecdata[r][i] = modIndex >= 0 ? modPoly.getAt(modIndex) : 0; + } + } - var createData = function(typeNumber, errorCorrectLevel, dataList) { + var totalCodeCount = 0; + for (var i = 0; i < rsBlocks.length; i += 1) { + totalCodeCount += rsBlocks[i].totalCount; + } - var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel); + var data = new Array(totalCodeCount); + var index = 0; - var buffer = qrBitBuffer(); + for (var i = 0; i < maxDcCount; i += 1) { + for (var r = 0; r < rsBlocks.length; r += 1) { + if (i < dcdata[r].length) { + data[index] = dcdata[r][i]; + index += 1; + } + } + } - for (var i = 0; i < dataList.length; i += 1) { - var data = dataList[i]; - buffer.put(data.getMode(), 4); - buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) ); - data.write(buffer); - } + for (var i = 0; i < maxEcCount; i += 1) { + for (var r = 0; r < rsBlocks.length; r += 1) { + if (i < ecdata[r].length) { + data[index] = ecdata[r][i]; + index += 1; + } + } + } - // calc num max data. - var totalDataCount = 0; - for (var i = 0; i < rsBlocks.length; i += 1) { - totalDataCount += rsBlocks[i].dataCount; - } + return data; + }; - if (buffer.getLengthInBits() > totalDataCount * 8) { - throw new Error('code length overflow. (' - + buffer.getLengthInBits() - + '>' - + totalDataCount * 8 - + ')'); - } + var createData = function (typeNumber, errorCorrectLevel, dataList) { + var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel); - // end code - if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { - buffer.put(0, 4); - } + var buffer = qrBitBuffer(); - // padding - while (buffer.getLengthInBits() % 8 != 0) { - buffer.putBit(false); - } + for (var i = 0; i < dataList.length; i += 1) { + var data = dataList[i]; + buffer.put(data.getMode(), 4); + buffer.put( + data.getLength(), + QRUtil.getLengthInBits(data.getMode(), typeNumber) + ); + data.write(buffer); + } - // padding - while (true) { + // calc num max data. + var totalDataCount = 0; + for (var i = 0; i < rsBlocks.length; i += 1) { + totalDataCount += rsBlocks[i].dataCount; + } - if (buffer.getLengthInBits() >= totalDataCount * 8) { - break; - } - buffer.put(PAD0, 8); + if (buffer.getLengthInBits() > totalDataCount * 8) { + throw new Error( + "code length overflow. (" + + buffer.getLengthInBits() + + ">" + + totalDataCount * 8 + + ")" + ); + } - if (buffer.getLengthInBits() >= totalDataCount * 8) { - break; - } - buffer.put(PAD1, 8); - } + // end code + if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { + buffer.put(0, 4); + } - return createBytes(buffer, rsBlocks); - }; + // padding + while (buffer.getLengthInBits() % 8 != 0) { + buffer.putBit(false); + } - _this.addData = function(data) { - var newData = qr8BitByte(data); - _dataList.push(newData); - _dataCache = null; - }; + // padding + while (true) { + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(PAD0, 8); - _this.isDark = function(row, col) { - if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) { - throw new Error(row + ',' + col); - } - return _modules[row][col]; - }; + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(PAD1, 8); + } - _this.getModuleCount = function() { - return _moduleCount; - }; + return createBytes(buffer, rsBlocks); + }; - _this.make = function() { - makeImpl(false, getBestMaskPattern() ); - }; + _this.addData = function (data) { + var newData = qr8BitByte(data); + _dataList.push(newData); + _dataCache = null; + }; - _this.createTableTag = function(cellSize, margin) { + _this.isDark = function (row, col) { + if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) { + throw new Error(row + "," + col); + } + return _modules[row][col]; + }; - cellSize = cellSize || 2; - margin = (typeof margin == 'undefined')? cellSize * 4 : margin; + _this.getModuleCount = function () { + return _moduleCount; + }; - var qrHtml = ''; + _this.make = function () { + makeImpl(false, getBestMaskPattern()); + }; - qrHtml += '<table style="'; - qrHtml += ' border-width: 0px; border-style: none;'; - qrHtml += ' border-collapse: collapse;'; - qrHtml += ' padding: 0px; margin: ' + margin + 'px;'; - qrHtml += '">'; - qrHtml += '<tbody>'; + _this.createTableTag = function (cellSize, margin) { + cellSize = cellSize || 2; + margin = typeof margin == "undefined" ? cellSize * 4 : margin; - for (var r = 0; r < _this.getModuleCount(); r += 1) { + var qrHtml = ""; - qrHtml += '<tr>'; + qrHtml += '<table style="'; + qrHtml += " border-width: 0px; border-style: none;"; + qrHtml += " border-collapse: collapse;"; + qrHtml += " padding: 0px; margin: " + margin + "px;"; + qrHtml += '">'; + qrHtml += "<tbody>"; - for (var c = 0; c < _this.getModuleCount(); c += 1) { - qrHtml += '<td style="'; - qrHtml += ' border-width: 0px; border-style: none;'; - qrHtml += ' border-collapse: collapse;'; - qrHtml += ' padding: 0px; margin: 0px;'; - qrHtml += ' width: ' + cellSize + 'px;'; - qrHtml += ' height: ' + cellSize + 'px;'; - qrHtml += ' background-color: '; - qrHtml += _this.isDark(r, c)? '#000000' : '#ffffff'; - qrHtml += ';'; - qrHtml += '"/>'; - } + for (var r = 0; r < _this.getModuleCount(); r += 1) { + qrHtml += "<tr>"; - qrHtml += '</tr>'; - } + for (var c = 0; c < _this.getModuleCount(); c += 1) { + qrHtml += '<td style="'; + qrHtml += " border-width: 0px; border-style: none;"; + qrHtml += " border-collapse: collapse;"; + qrHtml += " padding: 0px; margin: 0px;"; + qrHtml += " width: " + cellSize + "px;"; + qrHtml += " height: " + cellSize + "px;"; + qrHtml += " background-color: "; + qrHtml += _this.isDark(r, c) ? "#000000" : "#ffffff"; + qrHtml += ";"; + qrHtml += '"/>'; + } - qrHtml += '</tbody>'; - qrHtml += '</table>'; + qrHtml += "</tr>"; + } - return qrHtml; - }; + qrHtml += "</tbody>"; + qrHtml += "</table>"; - _this.createImgTag = function(cellSize, margin, size) { + return qrHtml; + }; - cellSize = cellSize || 2; - margin = (typeof margin == 'undefined')? cellSize * 4 : margin; + _this.createImgTag = function (cellSize, margin, size) { + cellSize = cellSize || 2; + margin = typeof margin == "undefined" ? cellSize * 4 : margin; - var min = margin; - var max = _this.getModuleCount() * cellSize + margin; + var min = margin; + var max = _this.getModuleCount() * cellSize + margin; - return createImgTag(size, size, function(x, y) { - if (min <= x && x < max && min <= y && y < max) { - var c = Math.floor( (x - min) / cellSize); - var r = Math.floor( (y - min) / cellSize); - return _this.isDark(r, c)? 0 : 1; - } else { - return 1; - } - } ); - }; + return createImgTag(size, size, function (x, y) { + if (min <= x && x < max && min <= y && y < max) { + var c = Math.floor((x - min) / cellSize); + var r = Math.floor((y - min) / cellSize); + return _this.isDark(r, c) ? 0 : 1; + } else { + return 1; + } + }); + }; - return _this; + return _this; }; //--------------------------------------------------------------------- // qrcode.stringToBytes //--------------------------------------------------------------------- -qrcode.stringToBytes = function(s) { - var bytes = new Array(); - for (var i = 0; i < s.length; i += 1) { - var c = s.charCodeAt(i); - bytes.push(c & 0xff); - } - return bytes; +qrcode.stringToBytes = function (s) { + var bytes = new Array(); + for (var i = 0; i < s.length; i += 1) { + var c = s.charCodeAt(i); + bytes.push(c & 0xff); + } + return bytes; }; //--------------------------------------------------------------------- @@ -492,65 +469,63 @@ qrcode.stringToBytes = function(s) { * [16bit Unicode],[16bit Bytes], ... * @param numChars */ -qrcode.createStringToBytes = function(unicodeData, numChars) { +qrcode.createStringToBytes = function (unicodeData, numChars) { + // create conversion map. - // create conversion map. - - var unicodeMap = function() { - - var bin = base64DecodeInputStream(unicodeData); - var read = function() { - var b = bin.read(); - if (b == -1) throw new Error(); - return b; - }; + var unicodeMap = (function () { + var bin = base64DecodeInputStream(unicodeData); + var read = function () { + var b = bin.read(); + if (b == -1) throw new Error(); + return b; + }; - var count = 0; - var unicodeMap = {}; - while (true) { - var b0 = bin.read(); - if (b0 == -1) break; - var b1 = read(); - var b2 = read(); - var b3 = read(); - var k = String.fromCharCode( (b0 << 8) | b1); - var v = (b2 << 8) | b3; - unicodeMap[k] = v; - count += 1; - } - if (count != numChars) { - throw new Error(count + ' != ' + numChars); - } + var count = 0; + var unicodeMap = {}; + while (true) { + var b0 = bin.read(); + if (b0 == -1) break; + var b1 = read(); + var b2 = read(); + var b3 = read(); + var k = String.fromCharCode((b0 << 8) | b1); + var v = (b2 << 8) | b3; + unicodeMap[k] = v; + count += 1; + } + if (count != numChars) { + throw new Error(count + " != " + numChars); + } - return unicodeMap; - }(); + return unicodeMap; + })(); - var unknownChar = '?'.charCodeAt(0); + var unknownChar = "?".charCodeAt(0); - return function(s) { - var bytes = new Array(); - for (var i = 0; i < s.length; i += 1) { - var c = s.charCodeAt(i); - if (c < 128) { - bytes.push(c); - } else { - var b = unicodeMap[s.charAt(i)]; - if (typeof b == 'number') { - if ( (b & 0xff) == b) { - // 1byte - bytes.push(b); - } else { - // 2bytes - bytes.push(b >>> 8); - bytes.push(b & 0xff); - } - } else { - bytes.push(unknownChar); - } - } - } - return bytes; - }; + return function (s) { + var bytes = new Array(); + for (var i = 0; i < s.length; i += 1) { + var c = s.charCodeAt(i); + if (c < 128) { + bytes.push(c); + } else { + var b = unicodeMap[s.charAt(i)]; + if (typeof b == "number") { + if ((b & 0xff) == b) { + // 1byte + bytes.push(b); + } else { + // 2bytes + bytes.push(b >>> 8); + bytes.push(b & 0xff); + } + } else { + bytes.push(unknownChar); + } + } + } + return bytes; + }; }; //--------------------------------------------------------------------- @@ -558,10 +533,10 @@ qrcode.createStringToBytes = function(unicodeData, numChars) { //--------------------------------------------------------------------- var QRMode = { - MODE_NUMBER : 1 << 0, - MODE_ALPHA_NUM : 1 << 1, - MODE_8BIT_BYTE : 1 << 2, - MODE_KANJI : 1 << 3 + MODE_NUMBER: 1 << 0, + MODE_ALPHA_NUM: 1 << 1, + MODE_8BIT_BYTE: 1 << 2, + MODE_KANJI: 1 << 3, }; //--------------------------------------------------------------------- @@ -569,10 +544,10 @@ var QRMode = { //--------------------------------------------------------------------- var QRErrorCorrectLevel = { - L : 1, - M : 0, - Q : 3, - H : 2 + L: 1, + M: 0, + Q: 3, + H: 2, }; //--------------------------------------------------------------------- @@ -580,581 +555,759 @@ var QRErrorCorrectLevel = { //--------------------------------------------------------------------- var QRMaskPattern = { - PATTERN000 : 0, - PATTERN001 : 1, - PATTERN010 : 2, - PATTERN011 : 3, - PATTERN100 : 4, - PATTERN101 : 5, - PATTERN110 : 6, - PATTERN111 : 7 + PATTERN000: 0, + PATTERN001: 1, + PATTERN010: 2, + PATTERN011: 3, + PATTERN100: 4, + PATTERN101: 5, + PATTERN110: 6, + PATTERN111: 7, }; //--------------------------------------------------------------------- // QRUtil //--------------------------------------------------------------------- -var QRUtil = function() { - - var PATTERN_POSITION_TABLE = [ - [], - [6, 18], - [6, 22], - [6, 26], - [6, 30], - [6, 34], - [6, 22, 38], - [6, 24, 42], - [6, 26, 46], - [6, 28, 50], - [6, 30, 54], - [6, 32, 58], - [6, 34, 62], - [6, 26, 46, 66], - [6, 26, 48, 70], - [6, 26, 50, 74], - [6, 30, 54, 78], - [6, 30, 56, 82], - [6, 30, 58, 86], - [6, 34, 62, 90], - [6, 28, 50, 72, 94], - [6, 26, 50, 74, 98], - [6, 30, 54, 78, 102], - [6, 28, 54, 80, 106], - [6, 32, 58, 84, 110], - [6, 30, 58, 86, 114], - [6, 34, 62, 90, 118], - [6, 26, 50, 74, 98, 122], - [6, 30, 54, 78, 102, 126], - [6, 26, 52, 78, 104, 130], - [6, 30, 56, 82, 108, 134], - [6, 34, 60, 86, 112, 138], - [6, 30, 58, 86, 114, 142], - [6, 34, 62, 90, 118, 146], - [6, 30, 54, 78, 102, 126, 150], - [6, 24, 50, 76, 102, 128, 154], - [6, 28, 54, 80, 106, 132, 158], - [6, 32, 58, 84, 110, 136, 162], - [6, 26, 54, 82, 110, 138, 166], - [6, 30, 58, 86, 114, 142, 170] - ]; - var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0); - var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0); - var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1); +var QRUtil = (function () { + var PATTERN_POSITION_TABLE = [ + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170], + ]; + var G15 = + (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0); + var G18 = + (1 << 12) | + (1 << 11) | + (1 << 10) | + (1 << 9) | + (1 << 8) | + (1 << 5) | + (1 << 2) | + (1 << 0); + var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1); - var _this = {}; - - var getBCHDigit = function(data) { - var digit = 0; - while (data != 0) { - digit += 1; - data >>>= 1; - } - return digit; - }; + var _this = {}; - _this.getBCHTypeInfo = function(data) { - var d = data << 10; - while (getBCHDigit(d) - getBCHDigit(G15) >= 0) { - d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) ); - } - return ( (data << 10) | d) ^ G15_MASK; - }; - - _this.getBCHTypeNumber = function(data) { - var d = data << 12; - while (getBCHDigit(d) - getBCHDigit(G18) >= 0) { - d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) ); - } - return (data << 12) | d; - }; - - _this.getPatternPosition = function(typeNumber) { - return PATTERN_POSITION_TABLE[typeNumber - 1]; - }; - - _this.getMaskFunction = function(maskPattern) { - - switch (maskPattern) { - - case QRMaskPattern.PATTERN000 : - return function(i, j) { return (i + j) % 2 == 0; }; - case QRMaskPattern.PATTERN001 : - return function(i, j) { return i % 2 == 0; }; - case QRMaskPattern.PATTERN010 : - return function(i, j) { return j % 3 == 0; }; - case QRMaskPattern.PATTERN011 : - return function(i, j) { return (i + j) % 3 == 0; }; - case QRMaskPattern.PATTERN100 : - return function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; }; - case QRMaskPattern.PATTERN101 : - return function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0; }; - case QRMaskPattern.PATTERN110 : - return function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; }; - case QRMaskPattern.PATTERN111 : - return function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; }; - - default : - throw new Error('bad maskPattern:' + maskPattern); - } - }; - - _this.getErrorCorrectPolynomial = function(errorCorrectLength) { - var a = qrPolynomial([1], 0); - for (var i = 0; i < errorCorrectLength; i += 1) { - a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0) ); - } - return a; - }; - - _this.getLengthInBits = function(mode, type) { - - if (1 <= type && type < 10) { - - // 1 - 9 - - switch(mode) { - case QRMode.MODE_NUMBER : return 10; - case QRMode.MODE_ALPHA_NUM : return 9; - case QRMode.MODE_8BIT_BYTE : return 8; - case QRMode.MODE_KANJI : return 8; - default : - throw new Error('mode:' + mode); - } - - } else if (type < 27) { + var getBCHDigit = function (data) { + var digit = 0; + while (data != 0) { + digit += 1; + data >>>= 1; + } + return digit; + }; - // 10 - 26 + _this.getBCHTypeInfo = function (data) { + var d = data << 10; + while (getBCHDigit(d) - getBCHDigit(G15) >= 0) { + d ^= G15 << (getBCHDigit(d) - getBCHDigit(G15)); + } + return ((data << 10) | d) ^ G15_MASK; + }; - switch(mode) { - case QRMode.MODE_NUMBER : return 12; - case QRMode.MODE_ALPHA_NUM : return 11; - case QRMode.MODE_8BIT_BYTE : return 16; - case QRMode.MODE_KANJI : return 10; - default : - throw new Error('mode:' + mode); - } + _this.getBCHTypeNumber = function (data) { + var d = data << 12; + while (getBCHDigit(d) - getBCHDigit(G18) >= 0) { + d ^= G18 << (getBCHDigit(d) - getBCHDigit(G18)); + } + return (data << 12) | d; + }; - } else if (type < 41) { + _this.getPatternPosition = function (typeNumber) { + return PATTERN_POSITION_TABLE[typeNumber - 1]; + }; - // 27 - 40 + _this.getMaskFunction = function (maskPattern) { + switch (maskPattern) { + case QRMaskPattern.PATTERN000: + return function (i, j) { + return (i + j) % 2 == 0; + }; + case QRMaskPattern.PATTERN001: + return function (i, j) { + return i % 2 == 0; + }; + case QRMaskPattern.PATTERN010: + return function (i, j) { + return j % 3 == 0; + }; + case QRMaskPattern.PATTERN011: + return function (i, j) { + return (i + j) % 3 == 0; + }; + case QRMaskPattern.PATTERN100: + return function (i, j) { + return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0; + }; + case QRMaskPattern.PATTERN101: + return function (i, j) { + return ((i * j) % 2) + ((i * j) % 3) == 0; + }; + case QRMaskPattern.PATTERN110: + return function (i, j) { + return (((i * j) % 2) + ((i * j) % 3)) % 2 == 0; + }; + case QRMaskPattern.PATTERN111: + return function (i, j) { + return (((i * j) % 3) + ((i + j) % 2)) % 2 == 0; + }; - switch(mode) { - case QRMode.MODE_NUMBER : return 14; - case QRMode.MODE_ALPHA_NUM : return 13; - case QRMode.MODE_8BIT_BYTE : return 16; - case QRMode.MODE_KANJI : return 12; - default : - throw new Error('mode:' + mode); - } + default: + throw new Error("bad maskPattern:" + maskPattern); + } + }; - } else { - throw new Error('type:' + type); - } - }; + _this.getErrorCorrectPolynomial = function (errorCorrectLength) { + var a = qrPolynomial([1], 0); + for (var i = 0; i < errorCorrectLength; i += 1) { + a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0)); + } + return a; + }; - _this.getLostPoint = function(qrcode) { + _this.getLengthInBits = function (mode, type) { + if (1 <= type && type < 10) { + // 1 - 9 - var moduleCount = qrcode.getModuleCount(); + switch (mode) { + case QRMode.MODE_NUMBER: + return 10; + case QRMode.MODE_ALPHA_NUM: + return 9; + case QRMode.MODE_8BIT_BYTE: + return 8; + case QRMode.MODE_KANJI: + return 8; + default: + throw new Error("mode:" + mode); + } + } else if (type < 27) { + // 10 - 26 - var lostPoint = 0; + switch (mode) { + case QRMode.MODE_NUMBER: + return 12; + case QRMode.MODE_ALPHA_NUM: + return 11; + case QRMode.MODE_8BIT_BYTE: + return 16; + case QRMode.MODE_KANJI: + return 10; + default: + throw new Error("mode:" + mode); + } + } else if (type < 41) { + // 27 - 40 - // LEVEL1 + switch (mode) { + case QRMode.MODE_NUMBER: + return 14; + case QRMode.MODE_ALPHA_NUM: + return 13; + case QRMode.MODE_8BIT_BYTE: + return 16; + case QRMode.MODE_KANJI: + return 12; + default: + throw new Error("mode:" + mode); + } + } else { + throw new Error("type:" + type); + } + }; - for (var row = 0; row < moduleCount; row += 1) { - for (var col = 0; col < moduleCount; col += 1) { + _this.getLostPoint = function (qrcode) { + var moduleCount = qrcode.getModuleCount(); - var sameCount = 0; - var dark = qrcode.isDark(row, col); + var lostPoint = 0; - for (var r = -1; r <= 1; r += 1) { + // LEVEL1 - if (row + r < 0 || moduleCount <= row + r) { - continue; - } + for (var row = 0; row < moduleCount; row += 1) { + for (var col = 0; col < moduleCount; col += 1) { + var sameCount = 0; + var dark = qrcode.isDark(row, col); - for (var c = -1; c <= 1; c += 1) { + for (var r = -1; r <= 1; r += 1) { + if (row + r < 0 || moduleCount <= row + r) { + continue; + } - if (col + c < 0 || moduleCount <= col + c) { - continue; - } + for (var c = -1; c <= 1; c += 1) { + if (col + c < 0 || moduleCount <= col + c) { + continue; + } - if (r == 0 && c == 0) { - continue; - } + if (r == 0 && c == 0) { + continue; + } - if (dark == qrcode.isDark(row + r, col + c) ) { - sameCount += 1; - } - } - } + if (dark == qrcode.isDark(row + r, col + c)) { + sameCount += 1; + } + } + } - if (sameCount > 5) { - lostPoint += (3 + sameCount - 5); - } - } - }; + if (sameCount > 5) { + lostPoint += 3 + sameCount - 5; + } + } + } - // LEVEL2 + // LEVEL2 - for (var row = 0; row < moduleCount - 1; row += 1) { - for (var col = 0; col < moduleCount - 1; col += 1) { - var count = 0; - if (qrcode.isDark(row, col) ) count += 1; - if (qrcode.isDark(row + 1, col) ) count += 1; - if (qrcode.isDark(row, col + 1) ) count += 1; - if (qrcode.isDark(row + 1, col + 1) ) count += 1; - if (count == 0 || count == 4) { - lostPoint += 3; - } - } - } + for (var row = 0; row < moduleCount - 1; row += 1) { + for (var col = 0; col < moduleCount - 1; col += 1) { + var count = 0; + if (qrcode.isDark(row, col)) count += 1; + if (qrcode.isDark(row + 1, col)) count += 1; + if (qrcode.isDark(row, col + 1)) count += 1; + if (qrcode.isDark(row + 1, col + 1)) count += 1; + if (count == 0 || count == 4) { + lostPoint += 3; + } + } + } - // LEVEL3 + // LEVEL3 - for (var row = 0; row < moduleCount; row += 1) { - for (var col = 0; col < moduleCount - 6; col += 1) { - if (qrcode.isDark(row, col) - && !qrcode.isDark(row, col + 1) - && qrcode.isDark(row, col + 2) - && qrcode.isDark(row, col + 3) - && qrcode.isDark(row, col + 4) - && !qrcode.isDark(row, col + 5) - && qrcode.isDark(row, col + 6) ) { - lostPoint += 40; - } - } - } + for (var row = 0; row < moduleCount; row += 1) { + for (var col = 0; col < moduleCount - 6; col += 1) { + if ( + qrcode.isDark(row, col) && + !qrcode.isDark(row, col + 1) && + qrcode.isDark(row, col + 2) && + qrcode.isDark(row, col + 3) && + qrcode.isDark(row, col + 4) && + !qrcode.isDark(row, col + 5) && + qrcode.isDark(row, col + 6) + ) { + lostPoint += 40; + } + } + } - for (var col = 0; col < moduleCount; col += 1) { - for (var row = 0; row < moduleCount - 6; row += 1) { - if (qrcode.isDark(row, col) - && !qrcode.isDark(row + 1, col) - && qrcode.isDark(row + 2, col) - && qrcode.isDark(row + 3, col) - && qrcode.isDark(row + 4, col) - && !qrcode.isDark(row + 5, col) - && qrcode.isDark(row + 6, col) ) { - lostPoint += 40; - } - } - } + for (var col = 0; col < moduleCount; col += 1) { + for (var row = 0; row < moduleCount - 6; row += 1) { + if ( + qrcode.isDark(row, col) && + !qrcode.isDark(row + 1, col) && + qrcode.isDark(row + 2, col) && + qrcode.isDark(row + 3, col) && + qrcode.isDark(row + 4, col) && + !qrcode.isDark(row + 5, col) && + qrcode.isDark(row + 6, col) + ) { + lostPoint += 40; + } + } + } - // LEVEL4 + // LEVEL4 - var darkCount = 0; + var darkCount = 0; - for (var col = 0; col < moduleCount; col += 1) { - for (var row = 0; row < moduleCount; row += 1) { - if (qrcode.isDark(row, col) ) { - darkCount += 1; - } - } - } + for (var col = 0; col < moduleCount; col += 1) { + for (var row = 0; row < moduleCount; row += 1) { + if (qrcode.isDark(row, col)) { + darkCount += 1; + } + } + } - var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5; - lostPoint += ratio * 10; + var ratio = + Math.abs((100 * darkCount) / moduleCount / moduleCount - 50) / 5; + lostPoint += ratio * 10; - return lostPoint; - }; + return lostPoint; + }; - return _this; -}(); + return _this; +})(); //--------------------------------------------------------------------- // QRMath //--------------------------------------------------------------------- -var QRMath = function() { +var QRMath = (function () { + var EXP_TABLE = new Array(256); + var LOG_TABLE = new Array(256); - var EXP_TABLE = new Array(256); - var LOG_TABLE = new Array(256); + // initialize tables + for (var i = 0; i < 8; i += 1) { + EXP_TABLE[i] = 1 << i; + } + for (var i = 8; i < 256; i += 1) { + EXP_TABLE[i] = + EXP_TABLE[i - 4] ^ EXP_TABLE[i - 5] ^ EXP_TABLE[i - 6] ^ EXP_TABLE[i - 8]; + } + for (var i = 0; i < 255; i += 1) { + LOG_TABLE[EXP_TABLE[i]] = i; + } - // initialize tables - for (var i = 0; i < 8; i += 1) { - EXP_TABLE[i] = 1 << i; - } - for (var i = 8; i < 256; i += 1) { - EXP_TABLE[i] = EXP_TABLE[i - 4] - ^ EXP_TABLE[i - 5] - ^ EXP_TABLE[i - 6] - ^ EXP_TABLE[i - 8]; - } - for (var i = 0; i < 255; i += 1) { - LOG_TABLE[EXP_TABLE[i] ] = i; - } - - var _this = {}; + var _this = {}; - _this.glog = function(n) { - - if (n < 1) { - throw new Error('glog(' + n + ')'); - } - - return LOG_TABLE[n]; - }; + _this.glog = function (n) { + if (n < 1) { + throw new Error("glog(" + n + ")"); + } - _this.gexp = function(n) { + return LOG_TABLE[n]; + }; - while (n < 0) { - n += 255; - } + _this.gexp = function (n) { + while (n < 0) { + n += 255; + } - while (n >= 256) { - n -= 255; - } + while (n >= 256) { + n -= 255; + } - return EXP_TABLE[n]; - }; + return EXP_TABLE[n]; + }; - return _this; -}(); + return _this; +})(); //--------------------------------------------------------------------- // qrPolynomial //--------------------------------------------------------------------- function qrPolynomial(num, shift) { + if (typeof num.length == "undefined") { + throw new Error(num.length + "/" + shift); + } - if (typeof num.length == 'undefined') { - throw new Error(num.length + '/' + shift); + var _num = (function () { + var offset = 0; + while (offset < num.length && num[offset] == 0) { + offset += 1; } + var _num = new Array(num.length - offset + shift); + for (var i = 0; i < num.length - offset; i += 1) { + _num[i] = num[i + offset]; + } + return _num; + })(); - var _num = function() { - var offset = 0; - while (offset < num.length && num[offset] == 0) { - offset += 1; - } - var _num = new Array(num.length - offset + shift); - for (var i = 0; i < num.length - offset; i += 1) { - _num[i] = num[i + offset]; - } - return _num; - }(); - - var _this = {}; - - _this.getAt = function(index) { - return _num[index]; - }; - - _this.getLength = function() { - return _num.length; - }; + var _this = {}; - _this.multiply = function(e) { + _this.getAt = function (index) { + return _num[index]; + }; - var num = new Array(_this.getLength() + e.getLength() - 1); + _this.getLength = function () { + return _num.length; + }; - for (var i = 0; i < _this.getLength(); i += 1) { - for (var j = 0; j < e.getLength(); j += 1) { - num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i) ) + QRMath.glog(e.getAt(j) ) ); - } - } + _this.multiply = function (e) { + var num = new Array(_this.getLength() + e.getLength() - 1); - return qrPolynomial(num, 0); - }; + for (var i = 0; i < _this.getLength(); i += 1) { + for (var j = 0; j < e.getLength(); j += 1) { + num[i + j] ^= QRMath.gexp( + QRMath.glog(_this.getAt(i)) + QRMath.glog(e.getAt(j)) + ); + } + } - _this.mod = function(e) { + return qrPolynomial(num, 0); + }; - if (_this.getLength() - e.getLength() < 0) { - return _this; - } + _this.mod = function (e) { + if (_this.getLength() - e.getLength() < 0) { + return _this; + } - var ratio = QRMath.glog(_this.getAt(0) ) - QRMath.glog(e.getAt(0) ); + var ratio = QRMath.glog(_this.getAt(0)) - QRMath.glog(e.getAt(0)); - var num = new Array(_this.getLength() ); - for (var i = 0; i < _this.getLength(); i += 1) { - num[i] = _this.getAt(i); - } + var num = new Array(_this.getLength()); + for (var i = 0; i < _this.getLength(); i += 1) { + num[i] = _this.getAt(i); + } - for (var i = 0; i < e.getLength(); i += 1) { - num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i) ) + ratio); - } + for (var i = 0; i < e.getLength(); i += 1) { + num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i)) + ratio); + } - // recursive call - return qrPolynomial(num, 0).mod(e); - }; + // recursive call + return qrPolynomial(num, 0).mod(e); + }; - return _this; + return _this; } //--------------------------------------------------------------------- // QRRSBlock //--------------------------------------------------------------------- -var QRRSBlock = function() { - - - // [1: [L, M, Q, H], ..] - var RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]]; - - var qrRSBlock = function(totalCount, dataCount) { - var _this = {}; - _this.totalCount = totalCount; - _this.dataCount = dataCount; - return _this; - }; +var QRRSBlock = (function () { + // [1: [L, M, Q, H], ..] + var RS_BLOCK_TABLE = [ + [1, 26, 19], + [1, 26, 16], + [1, 26, 13], + [1, 26, 9], + [1, 44, 34], + [1, 44, 28], + [1, 44, 22], + [1, 44, 16], + [1, 70, 55], + [1, 70, 44], + [2, 35, 17], + [2, 35, 13], + [1, 100, 80], + [2, 50, 32], + [2, 50, 24], + [4, 25, 9], + [1, 134, 108], + [2, 67, 43], + [2, 33, 15, 2, 34, 16], + [2, 33, 11, 2, 34, 12], + [2, 86, 68], + [4, 43, 27], + [4, 43, 19], + [4, 43, 15], + [2, 98, 78], + [4, 49, 31], + [2, 32, 14, 4, 33, 15], + [4, 39, 13, 1, 40, 14], + [2, 121, 97], + [2, 60, 38, 2, 61, 39], + [4, 40, 18, 2, 41, 19], + [4, 40, 14, 2, 41, 15], + [2, 146, 116], + [3, 58, 36, 2, 59, 37], + [4, 36, 16, 4, 37, 17], + [4, 36, 12, 4, 37, 13], + [2, 86, 68, 2, 87, 69], + [4, 69, 43, 1, 70, 44], + [6, 43, 19, 2, 44, 20], + [6, 43, 15, 2, 44, 16], + [4, 101, 81], + [1, 80, 50, 4, 81, 51], + [4, 50, 22, 4, 51, 23], + [3, 36, 12, 8, 37, 13], + [2, 116, 92, 2, 117, 93], + [6, 58, 36, 2, 59, 37], + [4, 46, 20, 6, 47, 21], + [7, 42, 14, 4, 43, 15], + [4, 133, 107], + [8, 59, 37, 1, 60, 38], + [8, 44, 20, 4, 45, 21], + [12, 33, 11, 4, 34, 12], + [3, 145, 115, 1, 146, 116], + [4, 64, 40, 5, 65, 41], + [11, 36, 16, 5, 37, 17], + [11, 36, 12, 5, 37, 13], + [5, 109, 87, 1, 110, 88], + [5, 65, 41, 5, 66, 42], + [5, 54, 24, 7, 55, 25], + [11, 36, 12], + [5, 122, 98, 1, 123, 99], + [7, 73, 45, 3, 74, 46], + [15, 43, 19, 2, 44, 20], + [3, 45, 15, 13, 46, 16], + [1, 135, 107, 5, 136, 108], + [10, 74, 46, 1, 75, 47], + [1, 50, 22, 15, 51, 23], + [2, 42, 14, 17, 43, 15], + [5, 150, 120, 1, 151, 121], + [9, 69, 43, 4, 70, 44], + [17, 50, 22, 1, 51, 23], + [2, 42, 14, 19, 43, 15], + [3, 141, 113, 4, 142, 114], + [3, 70, 44, 11, 71, 45], + [17, 47, 21, 4, 48, 22], + [9, 39, 13, 16, 40, 14], + [3, 135, 107, 5, 136, 108], + [3, 67, 41, 13, 68, 42], + [15, 54, 24, 5, 55, 25], + [15, 43, 15, 10, 44, 16], + [4, 144, 116, 4, 145, 117], + [17, 68, 42], + [17, 50, 22, 6, 51, 23], + [19, 46, 16, 6, 47, 17], + [2, 139, 111, 7, 140, 112], + [17, 74, 46], + [7, 54, 24, 16, 55, 25], + [34, 37, 13], + [4, 151, 121, 5, 152, 122], + [4, 75, 47, 14, 76, 48], + [11, 54, 24, 14, 55, 25], + [16, 45, 15, 14, 46, 16], + [6, 147, 117, 4, 148, 118], + [6, 73, 45, 14, 74, 46], + [11, 54, 24, 16, 55, 25], + [30, 46, 16, 2, 47, 17], + [8, 132, 106, 4, 133, 107], + [8, 75, 47, 13, 76, 48], + [7, 54, 24, 22, 55, 25], + [22, 45, 15, 13, 46, 16], + [10, 142, 114, 2, 143, 115], + [19, 74, 46, 4, 75, 47], + [28, 50, 22, 6, 51, 23], + [33, 46, 16, 4, 47, 17], + [8, 152, 122, 4, 153, 123], + [22, 73, 45, 3, 74, 46], + [8, 53, 23, 26, 54, 24], + [12, 45, 15, 28, 46, 16], + [3, 147, 117, 10, 148, 118], + [3, 73, 45, 23, 74, 46], + [4, 54, 24, 31, 55, 25], + [11, 45, 15, 31, 46, 16], + [7, 146, 116, 7, 147, 117], + [21, 73, 45, 7, 74, 46], + [1, 53, 23, 37, 54, 24], + [19, 45, 15, 26, 46, 16], + [5, 145, 115, 10, 146, 116], + [19, 75, 47, 10, 76, 48], + [15, 54, 24, 25, 55, 25], + [23, 45, 15, 25, 46, 16], + [13, 145, 115, 3, 146, 116], + [2, 74, 46, 29, 75, 47], + [42, 54, 24, 1, 55, 25], + [23, 45, 15, 28, 46, 16], + [17, 145, 115], + [10, 74, 46, 23, 75, 47], + [10, 54, 24, 35, 55, 25], + [19, 45, 15, 35, 46, 16], + [17, 145, 115, 1, 146, 116], + [14, 74, 46, 21, 75, 47], + [29, 54, 24, 19, 55, 25], + [11, 45, 15, 46, 46, 16], + [13, 145, 115, 6, 146, 116], + [14, 74, 46, 23, 75, 47], + [44, 54, 24, 7, 55, 25], + [59, 46, 16, 1, 47, 17], + [12, 151, 121, 7, 152, 122], + [12, 75, 47, 26, 76, 48], + [39, 54, 24, 14, 55, 25], + [22, 45, 15, 41, 46, 16], + [6, 151, 121, 14, 152, 122], + [6, 75, 47, 34, 76, 48], + [46, 54, 24, 10, 55, 25], + [2, 45, 15, 64, 46, 16], + [17, 152, 122, 4, 153, 123], + [29, 74, 46, 14, 75, 47], + [49, 54, 24, 10, 55, 25], + [24, 45, 15, 46, 46, 16], + [4, 152, 122, 18, 153, 123], + [13, 74, 46, 32, 75, 47], + [48, 54, 24, 14, 55, 25], + [42, 45, 15, 32, 46, 16], + [20, 147, 117, 4, 148, 118], + [40, 75, 47, 7, 76, 48], + [43, 54, 24, 22, 55, 25], + [10, 45, 15, 67, 46, 16], + [19, 148, 118, 6, 149, 119], + [18, 75, 47, 31, 76, 48], + [34, 54, 24, 34, 55, 25], + [20, 45, 15, 61, 46, 16], + ]; + var qrRSBlock = function (totalCount, dataCount) { var _this = {}; + _this.totalCount = totalCount; + _this.dataCount = dataCount; + return _this; + }; - var getRsBlockTable = function(typeNumber, errorCorrectLevel) { + var _this = {}; - switch(errorCorrectLevel) { - case QRErrorCorrectLevel.L : - return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; - case QRErrorCorrectLevel.M : - return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; - case QRErrorCorrectLevel.Q : - return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; - case QRErrorCorrectLevel.H : - return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; - default : - return undefined; - } - }; - - _this.getRSBlocks = function(typeNumber, errorCorrectLevel) { + var getRsBlockTable = function (typeNumber, errorCorrectLevel) { + switch (errorCorrectLevel) { + case QRErrorCorrectLevel.L: + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; + case QRErrorCorrectLevel.M: + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; + case QRErrorCorrectLevel.Q: + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; + case QRErrorCorrectLevel.H: + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; + default: + return undefined; + } + }; - var rsBlock = getRsBlockTable(typeNumber, errorCorrectLevel); + _this.getRSBlocks = function (typeNumber, errorCorrectLevel) { + var rsBlock = getRsBlockTable(typeNumber, errorCorrectLevel); - if (typeof rsBlock == 'undefined') { - throw new Error('bad rs block @ typeNumber:' + typeNumber + - '/errorCorrectLevel:' + errorCorrectLevel); - } + if (typeof rsBlock == "undefined") { + throw new Error( + "bad rs block @ typeNumber:" + + typeNumber + + "/errorCorrectLevel:" + + errorCorrectLevel + ); + } - var length = rsBlock.length / 3; + var length = rsBlock.length / 3; - var list = new Array(); + var list = new Array(); - for (var i = 0; i < length; i += 1) { + for (var i = 0; i < length; i += 1) { + var count = rsBlock[i * 3 + 0]; + var totalCount = rsBlock[i * 3 + 1]; + var dataCount = rsBlock[i * 3 + 2]; - var count = rsBlock[i * 3 + 0]; - var totalCount = rsBlock[i * 3 + 1]; - var dataCount = rsBlock[i * 3 + 2]; + for (var j = 0; j < count; j += 1) { + list.push(qrRSBlock(totalCount, dataCount)); + } + } - for (var j = 0; j < count; j += 1) { - list.push(qrRSBlock(totalCount, dataCount) ); - } - } + return list; + }; - return list; - }; - - return _this; -}(); + return _this; +})(); //--------------------------------------------------------------------- // qrBitBuffer //--------------------------------------------------------------------- -var qrBitBuffer = function() { - - var _buffer = new Array(); - var _length = 0; - - var _this = {}; +var qrBitBuffer = function () { + var _buffer = new Array(); + var _length = 0; - _this.getBuffer = function() { - return _buffer; - }; + var _this = {}; - _this.getAt = function(index) { - var bufIndex = Math.floor(index / 8); - return ( (_buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1; - }; + _this.getBuffer = function () { + return _buffer; + }; - _this.put = function(num, length) { - for (var i = 0; i < length; i += 1) { - _this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1); - } - }; + _this.getAt = function (index) { + var bufIndex = Math.floor(index / 8); + return ((_buffer[bufIndex] >>> (7 - (index % 8))) & 1) == 1; + }; - _this.getLengthInBits = function() { - return _length; - }; + _this.put = function (num, length) { + for (var i = 0; i < length; i += 1) { + _this.putBit(((num >>> (length - i - 1)) & 1) == 1); + } + }; - _this.putBit = function(bit) { + _this.getLengthInBits = function () { + return _length; + }; - var bufIndex = Math.floor(_length / 8); - if (_buffer.length <= bufIndex) { - _buffer.push(0); - } + _this.putBit = function (bit) { + var bufIndex = Math.floor(_length / 8); + if (_buffer.length <= bufIndex) { + _buffer.push(0); + } - if (bit) { - _buffer[bufIndex] |= (0x80 >>> (_length % 8) ); - } + if (bit) { + _buffer[bufIndex] |= 0x80 >>> _length % 8; + } - _length += 1; - }; + _length += 1; + }; - return _this; + return _this; }; //--------------------------------------------------------------------- // qr8BitByte //--------------------------------------------------------------------- -var qr8BitByte = function(data) { - - var _mode = QRMode.MODE_8BIT_BYTE; - var _data = data; - var _parsedData = []; - - var _this = {}; - +var qr8BitByte = function (data) { + var _mode = QRMode.MODE_8BIT_BYTE; + var _data = data; + var _parsedData = []; - // Added to support UTF-8 Characters - for (var i = 0, l = _data.length; i < l; i++) { - var byteArray = []; - var code = _data.charCodeAt(i); + var _this = {}; - if (code > 0x10000) { - byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18); - byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12); - byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6); - byteArray[3] = 0x80 | (code & 0x3F); - } else if (code > 0x800) { - byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12); - byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6); - byteArray[2] = 0x80 | (code & 0x3F); - } else if (code > 0x80) { - byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6); - byteArray[1] = 0x80 | (code & 0x3F); - } else { - byteArray[0] = code; - } + // Added to support UTF-8 Characters + for (var i = 0, l = _data.length; i < l; i++) { + var byteArray = []; + var code = _data.charCodeAt(i); - // Fix Unicode corruption bug - _parsedData.push(byteArray); + if (code > 0x10000) { + byteArray[0] = 0xf0 | ((code & 0x1c0000) >>> 18); + byteArray[1] = 0x80 | ((code & 0x3f000) >>> 12); + byteArray[2] = 0x80 | ((code & 0xfc0) >>> 6); + byteArray[3] = 0x80 | (code & 0x3f); + } else if (code > 0x800) { + byteArray[0] = 0xe0 | ((code & 0xf000) >>> 12); + byteArray[1] = 0x80 | ((code & 0xfc0) >>> 6); + byteArray[2] = 0x80 | (code & 0x3f); + } else if (code > 0x80) { + byteArray[0] = 0xc0 | ((code & 0x7c0) >>> 6); + byteArray[1] = 0x80 | (code & 0x3f); + } else { + byteArray[0] = code; } - _parsedData = Array.prototype.concat.apply([], _parsedData); + // Fix Unicode corruption bug + _parsedData.push(byteArray); + } - if (_parsedData.length != _data.length) { - _parsedData.unshift(191); - _parsedData.unshift(187); - _parsedData.unshift(239); - } + _parsedData = Array.prototype.concat.apply([], _parsedData); - var _bytes = _parsedData; + if (_parsedData.length != _data.length) { + _parsedData.unshift(191); + _parsedData.unshift(187); + _parsedData.unshift(239); + } - _this.getMode = function() { - return _mode; - }; + var _bytes = _parsedData; - _this.getLength = function(buffer) { - return _bytes.length; - }; + _this.getMode = function () { + return _mode; + }; - _this.write = function(buffer) { - for (var i = 0; i < _bytes.length; i += 1) { - buffer.put(_bytes[i], 8); - } - }; + _this.getLength = function (buffer) { + return _bytes.length; + }; - return _this; + _this.write = function (buffer) { + for (var i = 0; i < _bytes.length; i += 1) { + buffer.put(_bytes[i], 8); + } + }; + + return _this; }; //===================================================================== @@ -1165,462 +1318,443 @@ var qr8BitByte = function(data) { // byteArrayOutputStream //--------------------------------------------------------------------- -var byteArrayOutputStream = function() { +var byteArrayOutputStream = function () { + var _bytes = new Array(); - var _bytes = new Array(); + var _this = {}; - var _this = {}; + _this.writeByte = function (b) { + _bytes.push(b & 0xff); + }; - _this.writeByte = function(b) { - _bytes.push(b & 0xff); - }; - - _this.writeShort = function(i) { - _this.writeByte(i); - _this.writeByte(i >>> 8); - }; + _this.writeShort = function (i) { + _this.writeByte(i); + _this.writeByte(i >>> 8); + }; - _this.writeBytes = function(b, off, len) { - off = off || 0; - len = len || b.length; - for (var i = 0; i < len; i += 1) { - _this.writeByte(b[i + off]); - } - }; + _this.writeBytes = function (b, off, len) { + off = off || 0; + len = len || b.length; + for (var i = 0; i < len; i += 1) { + _this.writeByte(b[i + off]); + } + }; - _this.writeString = function(s) { - for (var i = 0; i < s.length; i += 1) { - _this.writeByte(s.charCodeAt(i) ); - } - }; + _this.writeString = function (s) { + for (var i = 0; i < s.length; i += 1) { + _this.writeByte(s.charCodeAt(i)); + } + }; - _this.toByteArray = function() { - return _bytes; - }; + _this.toByteArray = function () { + return _bytes; + }; - _this.toString = function() { - var s = ''; - s += '['; - for (var i = 0; i < _bytes.length; i += 1) { - if (i > 0) { - s += ','; - } - s += _bytes[i]; - } - s += ']'; - return s; - }; + _this.toString = function () { + var s = ""; + s += "["; + for (var i = 0; i < _bytes.length; i += 1) { + if (i > 0) { + s += ","; + } + s += _bytes[i]; + } + s += "]"; + return s; + }; - return _this; + return _this; }; //--------------------------------------------------------------------- // base64EncodeOutputStream //--------------------------------------------------------------------- -var base64EncodeOutputStream = function() { - - var _buffer = 0; - var _buflen = 0; - var _length = 0; - var _base64 = ''; - - var _this = {}; - - var writeEncoded = function(b) { - _base64 += String.fromCharCode(encode(b & 0x3f) ); - }; +var base64EncodeOutputStream = function () { + var _buffer = 0; + var _buflen = 0; + var _length = 0; + var _base64 = ""; - var encode = function(n) { - if (n < 0) { - // error. - } else if (n < 26) { - return 0x41 + n; - } else if (n < 52) { - return 0x61 + (n - 26); - } else if (n < 62) { - return 0x30 + (n - 52); - } else if (n == 62) { - return 0x2b; - } else if (n == 63) { - return 0x2f; - } - throw new Error('n:' + n); - }; + var _this = {}; - _this.writeByte = function(n) { + var writeEncoded = function (b) { + _base64 += String.fromCharCode(encode(b & 0x3f)); + }; - _buffer = (_buffer << 8) | (n & 0xff); - _buflen += 8; - _length += 1; + var encode = function (n) { + if (n < 0) { + // error. + } else if (n < 26) { + return 0x41 + n; + } else if (n < 52) { + return 0x61 + (n - 26); + } else if (n < 62) { + return 0x30 + (n - 52); + } else if (n == 62) { + return 0x2b; + } else if (n == 63) { + return 0x2f; + } + throw new Error("n:" + n); + }; - while (_buflen >= 6) { - writeEncoded(_buffer >>> (_buflen - 6) ); - _buflen -= 6; - } - }; + _this.writeByte = function (n) { + _buffer = (_buffer << 8) | (n & 0xff); + _buflen += 8; + _length += 1; - _this.flush = function() { + while (_buflen >= 6) { + writeEncoded(_buffer >>> (_buflen - 6)); + _buflen -= 6; + } + }; - if (_buflen > 0) { - writeEncoded(_buffer << (6 - _buflen) ); - _buffer = 0; - _buflen = 0; - } + _this.flush = function () { + if (_buflen > 0) { + writeEncoded(_buffer << (6 - _buflen)); + _buffer = 0; + _buflen = 0; + } - if (_length % 3 != 0) { - // padding - var padlen = 3 - _length % 3; - for (var i = 0; i < padlen; i += 1) { - _base64 += '='; - } - } - }; + if (_length % 3 != 0) { + // padding + var padlen = 3 - (_length % 3); + for (var i = 0; i < padlen; i += 1) { + _base64 += "="; + } + } + }; - _this.toString = function() { - return _base64; - }; + _this.toString = function () { + return _base64; + }; - return _this; + return _this; }; //--------------------------------------------------------------------- // base64DecodeInputStream //--------------------------------------------------------------------- -var base64DecodeInputStream = function(str) { +var base64DecodeInputStream = function (str) { + var _str = str; + var _pos = 0; + var _buffer = 0; + var _buflen = 0; - var _str = str; - var _pos = 0; - var _buffer = 0; - var _buflen = 0; + var _this = {}; - var _this = {}; - - _this.read = function() { - - while (_buflen < 8) { - - if (_pos >= _str.length) { - if (_buflen == 0) { - return -1; - } - throw new Error('unexpected end of file./' + _buflen); - } + _this.read = function () { + while (_buflen < 8) { + if (_pos >= _str.length) { + if (_buflen == 0) { + return -1; + } + throw new Error("unexpected end of file./" + _buflen); + } - var c = _str.charAt(_pos); - _pos += 1; + var c = _str.charAt(_pos); + _pos += 1; - if (c == '=') { - _buflen = 0; - return -1; - } else if (c.match(/^\s$/) ) { - // ignore if whitespace. - continue; - } + if (c == "=") { + _buflen = 0; + return -1; + } else if (c.match(/^\s$/)) { + // ignore if whitespace. + continue; + } - _buffer = (_buffer << 6) | decode(c.charCodeAt(0) ); - _buflen += 6; - } + _buffer = (_buffer << 6) | decode(c.charCodeAt(0)); + _buflen += 6; + } - var n = (_buffer >>> (_buflen - 8) ) & 0xff; - _buflen -= 8; - return n; - }; + var n = (_buffer >>> (_buflen - 8)) & 0xff; + _buflen -= 8; + return n; + }; - var decode = function(c) { - if (0x41 <= c && c <= 0x5a) { - return c - 0x41; - } else if (0x61 <= c && c <= 0x7a) { - return c - 0x61 + 26; - } else if (0x30 <= c && c <= 0x39) { - return c - 0x30 + 52; - } else if (c == 0x2b) { - return 62; - } else if (c == 0x2f) { - return 63; - } else { - throw new Error('c:' + c); - } - }; + var decode = function (c) { + if (0x41 <= c && c <= 0x5a) { + return c - 0x41; + } else if (0x61 <= c && c <= 0x7a) { + return c - 0x61 + 26; + } else if (0x30 <= c && c <= 0x39) { + return c - 0x30 + 52; + } else if (c == 0x2b) { + return 62; + } else if (c == 0x2f) { + return 63; + } else { + throw new Error("c:" + c); + } + }; - return _this; + return _this; }; //--------------------------------------------------------------------- // gifImage (B/W) //--------------------------------------------------------------------- -var gifImage = function(width, height) { +var gifImage = function (width, height) { + var _width = width; + var _height = height; + var _data = new Array(width * height); - var _width = width; - var _height = height; - var _data = new Array(width * height); + var _this = {}; - var _this = {}; - - _this.setPixel = function(x, y, pixel) { - _data[y * _width + x] = pixel; - }; + _this.setPixel = function (x, y, pixel) { + _data[y * _width + x] = pixel; + }; - _this.write = function(out) { + _this.write = function (out) { + //--------------------------------- + // GIF Signature - //--------------------------------- - // GIF Signature + out.writeString("GIF87a"); - out.writeString('GIF87a'); + //--------------------------------- + // Screen Descriptor - //--------------------------------- - // Screen Descriptor + out.writeShort(_width); + out.writeShort(_height); - out.writeShort(_width); - out.writeShort(_height); + out.writeByte(0x80); // 2bit + out.writeByte(0); + out.writeByte(0); - out.writeByte(0x80); // 2bit - out.writeByte(0); - out.writeByte(0); + //--------------------------------- + // Global Color Map - //--------------------------------- - // Global Color Map + // black + out.writeByte(0x00); + out.writeByte(0x00); + out.writeByte(0x00); - // black - out.writeByte(0x00); - out.writeByte(0x00); - out.writeByte(0x00); + // white + out.writeByte(0xff); + out.writeByte(0xff); + out.writeByte(0xff); - // white - out.writeByte(0xff); - out.writeByte(0xff); - out.writeByte(0xff); + //--------------------------------- + // Image Descriptor - //--------------------------------- - // Image Descriptor + out.writeString(","); + out.writeShort(0); + out.writeShort(0); + out.writeShort(_width); + out.writeShort(_height); + out.writeByte(0); - out.writeString(','); - out.writeShort(0); - out.writeShort(0); - out.writeShort(_width); - out.writeShort(_height); - out.writeByte(0); + //--------------------------------- + // Local Color Map - //--------------------------------- - // Local Color Map + //--------------------------------- + // Raster Data - //--------------------------------- - // Raster Data + var lzwMinCodeSize = 2; + var raster = getLZWRaster(lzwMinCodeSize); - var lzwMinCodeSize = 2; - var raster = getLZWRaster(lzwMinCodeSize); + out.writeByte(lzwMinCodeSize); - out.writeByte(lzwMinCodeSize); + var offset = 0; - var offset = 0; - - while (raster.length - offset > 255) { - out.writeByte(255); - out.writeBytes(raster, offset, 255); - offset += 255; - } - - out.writeByte(raster.length - offset); - out.writeBytes(raster, offset, raster.length - offset); - out.writeByte(0x00); - - //--------------------------------- - // GIF Terminator - out.writeString(';'); - }; - - var bitOutputStream = function(out) { - - var _out = out; - var _bitLength = 0; - var _bitBuffer = 0; + while (raster.length - offset > 255) { + out.writeByte(255); + out.writeBytes(raster, offset, 255); + offset += 255; + } - var _this = {}; + out.writeByte(raster.length - offset); + out.writeBytes(raster, offset, raster.length - offset); + out.writeByte(0x00); - _this.write = function(data, length) { + //--------------------------------- + // GIF Terminator + out.writeString(";"); + }; - if ( (data >>> length) != 0) { - throw new Error('length over'); - } + var bitOutputStream = function (out) { + var _out = out; + var _bitLength = 0; + var _bitBuffer = 0; - while (_bitLength + length >= 8) { - _out.writeByte(0xff & ( (data << _bitLength) | _bitBuffer) ); - length -= (8 - _bitLength); - data >>>= (8 - _bitLength); - _bitBuffer = 0; - _bitLength = 0; - } + var _this = {}; - _bitBuffer = (data << _bitLength) | _bitBuffer; - _bitLength = _bitLength + length; - }; + _this.write = function (data, length) { + if (data >>> length != 0) { + throw new Error("length over"); + } - _this.flush = function() { - if (_bitLength > 0) { - _out.writeByte(_bitBuffer); - } - }; + while (_bitLength + length >= 8) { + _out.writeByte(0xff & ((data << _bitLength) | _bitBuffer)); + length -= 8 - _bitLength; + data >>>= 8 - _bitLength; + _bitBuffer = 0; + _bitLength = 0; + } - return _this; + _bitBuffer = (data << _bitLength) | _bitBuffer; + _bitLength = _bitLength + length; }; - var getLZWRaster = function(lzwMinCodeSize) { - - var clearCode = 1 << lzwMinCodeSize; - var endCode = (1 << lzwMinCodeSize) + 1; - var bitLength = lzwMinCodeSize + 1; + _this.flush = function () { + if (_bitLength > 0) { + _out.writeByte(_bitBuffer); + } + }; - // Setup LZWTable - var table = lzwTable(); + return _this; + }; - for (var i = 0; i < clearCode; i += 1) { - table.add(String.fromCharCode(i) ); - } - table.add(String.fromCharCode(clearCode) ); - table.add(String.fromCharCode(endCode) ); + var getLZWRaster = function (lzwMinCodeSize) { + var clearCode = 1 << lzwMinCodeSize; + var endCode = (1 << lzwMinCodeSize) + 1; + var bitLength = lzwMinCodeSize + 1; - var byteOut = byteArrayOutputStream(); - var bitOut = bitOutputStream(byteOut); + // Setup LZWTable + var table = lzwTable(); - // clear code - bitOut.write(clearCode, bitLength); + for (var i = 0; i < clearCode; i += 1) { + table.add(String.fromCharCode(i)); + } + table.add(String.fromCharCode(clearCode)); + table.add(String.fromCharCode(endCode)); - var dataIndex = 0; + var byteOut = byteArrayOutputStream(); + var bitOut = bitOutputStream(byteOut); - var s = String.fromCharCode(_data[dataIndex]); - dataIndex += 1; + // clear code + bitOut.write(clearCode, bitLength); - while (dataIndex < _data.length) { + var dataIndex = 0; - var c = String.fromCharCode(_data[dataIndex]); - dataIndex += 1; + var s = String.fromCharCode(_data[dataIndex]); + dataIndex += 1; - if (table.contains(s + c) ) { + while (dataIndex < _data.length) { + var c = String.fromCharCode(_data[dataIndex]); + dataIndex += 1; - s = s + c; + if (table.contains(s + c)) { + s = s + c; + } else { + bitOut.write(table.indexOf(s), bitLength); - } else { + if (table.size() < 0xfff) { + if (table.size() == 1 << bitLength) { + bitLength += 1; + } - bitOut.write(table.indexOf(s), bitLength); + table.add(s + c); + } - if (table.size() < 0xfff) { + s = c; + } + } - if (table.size() == (1 << bitLength) ) { - bitLength += 1; - } + bitOut.write(table.indexOf(s), bitLength); - table.add(s + c); - } + // end code + bitOut.write(endCode, bitLength); - s = c; - } - } + bitOut.flush(); - bitOut.write(table.indexOf(s), bitLength); + return byteOut.toByteArray(); + }; - // end code - bitOut.write(endCode, bitLength); + var lzwTable = function () { + var _map = {}; + var _size = 0; - bitOut.flush(); + var _this = {}; - return byteOut.toByteArray(); + _this.add = function (key) { + if (_this.contains(key)) { + throw new Error("dup key:" + key); + } + _map[key] = _size; + _size += 1; }; - var lzwTable = function() { - - var _map = {}; - var _size = 0; - - var _this = {}; - - _this.add = function(key) { - if (_this.contains(key) ) { - throw new Error('dup key:' + key); - } - _map[key] = _size; - _size += 1; - }; - - _this.size = function() { - return _size; - }; - - _this.indexOf = function(key) { - return _map[key]; - }; + _this.size = function () { + return _size; + }; - _this.contains = function(key) { - return typeof _map[key] != 'undefined'; - }; + _this.indexOf = function (key) { + return _map[key]; + }; - return _this; + _this.contains = function (key) { + return typeof _map[key] != "undefined"; }; return _this; -}; + }; -var createImgTag = function(width, height, getPixel, alt) { + return _this; +}; - var gif = gifImage(width, height); - for (var y = 0; y < height; y += 1) { - for (var x = 0; x < width; x += 1) { - gif.setPixel(x, y, getPixel(x, y) ); - } +var createImgTag = function (width, height, getPixel, alt) { + var gif = gifImage(width, height); + for (var y = 0; y < height; y += 1) { + for (var x = 0; x < width; x += 1) { + gif.setPixel(x, y, getPixel(x, y)); } + } - var b = byteArrayOutputStream(); - gif.write(b); + var b = byteArrayOutputStream(); + gif.write(b); - var base64 = base64EncodeOutputStream(); - var bytes = b.toByteArray(); - for (var i = 0; i < bytes.length; i += 1) { - base64.writeByte(bytes[i]); - } - base64.flush(); + var base64 = base64EncodeOutputStream(); + var bytes = b.toByteArray(); + for (var i = 0; i < bytes.length; i += 1) { + base64.writeByte(bytes[i]); + } + base64.flush(); - var img = ''; - img += 'data:image/gif;base64,'; - img += base64; + var img = ""; + img += "data:image/gif;base64,"; + img += base64; - return img; + return img; }; //--------------------------------------------------------------------- // returns qrcode function. -var drawImg = function(text, options) { - options = options || {}; - var typeNumber = options.typeNumber || 4; - var errorCorrectLevel = options.errorCorrectLevel || 'M'; - var size = options.size || 500; +var drawImg = function (text, options) { + options = options || {}; + var typeNumber = options.typeNumber || 4; + var errorCorrectLevel = options.errorCorrectLevel || "M"; + var size = options.size || 500; - var qr; + var qr; - try { - qr = qrcode(typeNumber, errorCorrectLevel || 'M'); - qr.addData(text); - qr.make(); - } catch (e) { - if(typeNumber >= 40) { - throw new Error('Text too long to encode'); - } else { - return drawImg(text, { - size: size, - errorCorrectLevel: errorCorrectLevel, - typeNumber: typeNumber + 1 - }); - } + try { + qr = qrcode(typeNumber, errorCorrectLevel || "M"); + qr.addData(text); + qr.make(); + } catch (e) { + if (typeNumber >= 40) { + throw new Error("Text too long to encode"); + } else { + return drawImg(text, { + size: size, + errorCorrectLevel: errorCorrectLevel, + typeNumber: typeNumber + 1, + }); } + } - // calc cellsize and margin - var cellsize = parseInt(size / qr.getModuleCount()); - var margin = parseInt((size - qr.getModuleCount() * cellsize) / 2); + // calc cellsize and margin + var cellsize = parseInt(size / qr.getModuleCount()); + var margin = parseInt((size - qr.getModuleCount() * cellsize) / 2); - return qr.createImgTag(cellsize, margin, size); + return qr.createImgTag(cellsize, margin, size); }; export default { - drawImg: drawImg -};
\ No newline at end of file + drawImg: drawImg, +}; diff --git a/static/shopify.html b/static/shopify.html index 886c3d34..ef338d35 100644 --- a/static/shopify.html +++ b/static/shopify.html @@ -1,87 +1,88 @@ <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script> <script> - window.addEventListener('load', async () => { - let sat = (s) => { - s = Math.abs(s); - let p = Math.floor(Math.log(s) / Math.LN10 + 0.000000001); - let d = Math.floor((p + 1) / 3); + window.addEventListener("load", async () => { + let sat = (s) => { + s = Math.abs(s); + let p = Math.floor(Math.log(s) / Math.LN10 + 0.000000001); + let d = Math.floor((p + 1) / 3); - return ( - '⚡️' + - (parseInt(s) > 0 - ? new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 }).format( - s / Math.pow(1000, d) - ) + ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z'][d] - : 0 - ).toString() - ); - }; + return ( + "⚡️" + + (parseInt(s) > 0 + ? new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }).format( + s / Math.pow(1000, d) + ) + ["", "K", "M", "G", "T", "P", "E", "Z"][d] + : 0 + ).toString() + ); + }; - let copy = (text) => { - navigator.clipboard.writeText(text); - }; + let copy = (text) => { + navigator.clipboard.writeText(text); + }; - let f = (s, currency) => - new Intl.NumberFormat('en-US', { - style: 'currency', - currency - }) - .format(s) - .replace('CA', ''); + let f = (s, currency) => + new Intl.NumberFormat("en-US", { + style: "currency", + currency, + }) + .format(s) + .replace("CA", ""); - let s = (s) => new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(s); + let s = (s) => + new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(s); - let { - default: { drawImg } - } = await import('https://coinos.io/qr.js'); - let base64 = drawImg('test', { size: 300 }); + let { + default: { drawImg }, + } = await import("https://coinos.io/qr.js"); + let base64 = drawImg("test", { size: 300 }); - let link = document.createElement('link'); - link.setAttribute('rel', 'stylesheet'); - link.setAttribute('type', 'text/css'); - link.setAttribute( - 'href', - 'https://fonts.googleapis.com/css?family=Public+Sans:400italic,400,300,700' - ); + let link = document.createElement("link"); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("type", "text/css"); + link.setAttribute( + "href", + "https://fonts.googleapis.com/css?family=Public+Sans:400italic,400,300,700" + ); - var style = document.createElement('style'); - style.innerHTML = `#check { + var style = document.createElement("style"); + style.innerHTML = `#check { transform: scale(0.5); transition: transform 1s; } `; - document.head.appendChild(style); + document.head.appendChild(style); - document.head.appendChild(link); - document.title = 'Pay With Bitcoin'; + document.head.appendChild(link); + document.title = "Pay With Bitcoin"; - let fiat = Shopify.checkout.payment_due; - let main = document.querySelector('main'); - main.style.fontFamily = 'Public Sans, sans-serif'; - main.innerHTML = 'Please Pay'; + let fiat = Shopify.checkout.payment_due; + let main = document.querySelector("main"); + main.style.fontFamily = "Public Sans, sans-serif"; + main.innerHTML = "Please Pay"; - let data = await fetch('https://dev.coinos.io/api/invoice', { - method: 'POST', - mode: 'cors', - headers: { - 'Content-Type': 'application/json', - accept: 'application/json' - }, - body: JSON.stringify({ - invoice: { - fiat, - type: 'lightning', - webhook: `https://dev.coinos.io/api/shopify/${Shopify.checkout.order_id}` - }, - user: { username: 'horseradish' } - }) - }) - .then(function (response) { - return response.json(); - }) - .catch(console.log); + let data = await fetch("https://dev.coinos.io/api/invoice", { + method: "POST", + mode: "cors", + headers: { + "Content-Type": "application/json", + accept: "application/json", + }, + body: JSON.stringify({ + invoice: { + fiat, + type: "lightning", + webhook: `https://dev.coinos.io/api/shopify/${Shopify.checkout.order_id}`, + }, + user: { username: "horseradish" }, + }), + }) + .then(function (response) { + return response.json(); + }) + .catch(console.log); - main.innerHTML = `<a href="lightning:${data.hash}"> + main.innerHTML = `<a href="lightning:${data.hash}"> <div style="display: flex; margin-top: 50px; margin-bottom: 80px; position: relative;"> <img id="qrcode" src="${base64}" /> <div id="bg"></div> @@ -89,14 +90,14 @@ </a> <div style="width: 100%; text-align: center; "> <div style="font-size: 24px; font-weight: bold; margin-bottom: 12px;">${f( - (data.amount * data.rate) / 100000000, - data.currency - )}</div> + (data.amount * data.rate) / 100000000, + data.currency + )}</div> <div style="color: #666; font-size: 18px;">${sat(data.amount)}</div> </div> <div style="text-align: center; margin: 40px auto; max-width: 460px; word-break: break-all; line-height: 1.5rem">${ - data.hash - }</div> + data.hash + }</div> <div style="margin: 20px 0; display: flex; gap: 1rem; justify-content: center; align-items: center;"> <a href="lightning:${data.hash}" style="text-decoration: none"> <div style="color: #999; font-size: 18px; border-radius: 9999px; padding: 0.5rem 1rem; border: 1px solid #ccc; background: white;">Open</div> @@ -107,43 +108,43 @@ </div> `; - let copyButton = document.getElementById('copy'); - copyButton.addEventListener('click', (e) => { - e.preventDefault(); - copy(data.hash); - let copied = document.createElement('div'); - copied.textContent = 'Copied!'; - main.appendChild(copied); - setTimeout(() => copied.remove(), 3000); - }); + let copyButton = document.getElementById("copy"); + copyButton.addEventListener("click", (e) => { + e.preventDefault(); + copy(data.hash); + let copied = document.createElement("div"); + copied.textContent = "Copied!"; + main.appendChild(copied); + setTimeout(() => copied.remove(), 3000); + }); - let qr = document.getElementById('qrcode'); - qr.style.margin = 'auto'; - qr.style.marginTop = '40px'; - qr.style.border = '12px white solid'; - qr.style.zIndex = 10; + let qr = document.getElementById("qrcode"); + qr.style.margin = "auto"; + qr.style.marginTop = "40px"; + qr.style.border = "12px white solid"; + qr.style.zIndex = 10; - new QRCode(qr, data.hash); + new QRCode(qr, data.hash); - let bg = document.getElementById('bg'); - bg.style.position = 'absolute'; - bg.style.margin = 'auto'; - bg.style.left = '0'; - bg.style.right = '0'; - bg.style.width = '400px'; - bg.style.height = '400px'; - bg.style.borderRadius = '50%'; - bg.style.background = 'linear-gradient(to right, #F2F6FC, #E1E3FF)'; - bg.style.zIndex = '0'; + let bg = document.getElementById("bg"); + bg.style.position = "absolute"; + bg.style.margin = "auto"; + bg.style.left = "0"; + bg.style.right = "0"; + bg.style.width = "400px"; + bg.style.height = "400px"; + bg.style.borderRadius = "50%"; + bg.style.background = "linear-gradient(to right, #F2F6FC, #E1E3FF)"; + bg.style.zIndex = "0"; - let ws = new WebSocket('wss://dev.coinos.io/ws'); - ws.onmessage = ({ data }) => { - try { - let { type, data: d } = JSON.parse(data); - if (type === 'payment') { - main.innerHTML = - '<img id="check" src="https://dev.coinos.io/icons/check.svg" alt="Check" />' + - `<h1>Payment received!</h1> + let ws = new WebSocket("wss://dev.coinos.io/ws"); + ws.onmessage = ({ data }) => { + try { + let { type, data: d } = JSON.parse(data); + if (type === "payment") { + main.innerHTML = + '<img id="check" src="https://dev.coinos.io/icons/check.svg" alt="Check" />' + + `<h1>Payment received!</h1> <h2> ${((d.amount * d.rate) / 100000000).toFixed(2)} CAD </h2> @@ -151,17 +152,17 @@ ⚡️${d.amount} </h3>`; - var image = document.getElementById('check'); - image.addEventListener('load', function () { - image.style.transform = 'scale(1)'; - }); - } - } catch (e) { - console.log(e); - } - }; - ws.onopen = () => { - ws.send(JSON.stringify({ type: 'subscribe', data })); - }; - }); + var image = document.getElementById("check"); + image.addEventListener("load", function () { + image.style.transform = "scale(1)"; + }); + } + } catch (e) { + console.log(e); + } + }; + ws.onopen = () => { + ws.send(JSON.stringify({ type: "subscribe", data })); + }; + }); </script> diff --git a/svelte.config.js b/svelte.config.js index 70484390..a0f46356 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,23 +1,24 @@ // import adapter from '@sveltejs/adapter-node'; -import adapter from 'svelte-adapter-bun' -import preprocess from 'svelte-preprocess'; +import adapter from "svelte-adapter-bun"; +import preprocess from "svelte-preprocess"; const config = { - preprocess: [ - preprocess({ - postcss: true - }) - ], - kit: { - adapter: adapter(), - csrf: { - checkOrigin: false - } - }, - onwarn: (warning, handler) => { - if (warning.code.includes('caption') || warning.filename.includes('Toast')) return; - handler(warning); - } + preprocess: [ + preprocess({ + postcss: true, + }), + ], + kit: { + adapter: adapter(), + csrf: { + checkOrigin: false, + }, + }, + onwarn: (warning, handler) => { + if (warning.code.includes("caption") || warning.filename.includes("Toast")) + return; + handler(warning); + }, }; export default config; diff --git a/tailwind.config.cjs b/tailwind.config.cjs index c57db020..746f4ca6 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -1,17 +1,17 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ['./src/**/*.{html,js,svelte}'], - theme: { - extend: { - colors: { - primary: '#F5F7FA', - secondary: '#70757E', - gradient: '#C3CFE2', - lightgrey: '#EAECEF', - grey: '#70757E' - } - } - }, - plugins: [require('tailwind-scrollbar')], - safelist: ['w-16', 'h-16', 'w-20', 'h-20', 'w-24', 'h-24'] + content: ["./src/**/*.{html,js,svelte}"], + theme: { + extend: { + colors: { + primary: "#F5F7FA", + secondary: "#70757E", + gradient: "#C3CFE2", + lightgrey: "#EAECEF", + grey: "#70757E", + }, + }, + }, + plugins: [require("tailwind-scrollbar")], + safelist: ["w-16", "h-16", "w-20", "h-20", "w-24", "h-24"], }; diff --git a/vite.config.js b/vite.config.js index 9cda1e3f..15974503 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,23 +1,23 @@ -import { sveltekit } from '@sveltejs/kit/vite'; +import { sveltekit } from "@sveltejs/kit/vite"; -import path from 'path'; +import path from "path"; /** @type {import('vite').UserConfig} */ const config = { - plugins: [sveltekit()], - resolve: { - alias: { - $comp: path.resolve('src/components/index.js'), - } - }, - server: { - proxy: { - '/api': { - target: 'http://localhost:3119', - rewrite: (path) => path.replace(/^\/api/, '') - } - } - } + plugins: [sveltekit()], + resolve: { + alias: { + $comp: path.resolve("src/components/index.js"), + }, + }, + server: { + proxy: { + "/api": { + target: "http://localhost:3119", + rewrite: (path) => path.replace(/^\/api/, ""), + }, + }, + }, }; export default config; |
