diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/AppHeader.svelte | 193 | ||||
| -rw-r--r-- | src/components/Avatar.svelte | 2 | ||||
| -rw-r--r-- | src/lib/punks.js | 66 | ||||
| -rw-r--r-- | src/lib/upload.js | 3 | ||||
| -rw-r--r-- | src/locales/en.json | 2 | ||||
| -rw-r--r-- | src/routes/(app)/+layout.svelte | 2 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+layout.svelte | 36 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+page.svelte | 8 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/settings/[...tab]/+page.svelte | 28 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/settings/[...tab]/_account.svelte | 6 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/settings/[...tab]/_pos.svelte | 4 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/settings/[...tab]/_security.svelte | 2 | ||||
| -rw-r--r-- | src/routes/(app)/register/+page.server.js | 29 | ||||
| -rw-r--r-- | src/routes/(app)/register/+page.svelte | 245 | ||||
| -rw-r--r-- | src/routes/+layout.svelte | 3 | ||||
| -rw-r--r-- | src/routes/+page.server.js | 1 | ||||
| -rw-r--r-- | src/routes/+page.svelte | 103 |
17 files changed, 550 insertions, 183 deletions
diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte index a37a834e..123606e1 100644 --- a/src/components/AppHeader.svelte +++ b/src/components/AppHeader.svelte @@ -3,7 +3,7 @@ import { banner, newPayment, colorTheme } from '$lib/store'; import { goto } from '$app/navigation'; import { Avatar, Icon } from '$comp'; - import { t } from '$lib/translations'; + import { loading, t } from '$lib/translations'; import { page } from '$app/stores'; export let data; @@ -28,112 +28,115 @@ $banner?.id && $banner.id === subject.id ? `url(${$banner.src})` : subject?.banner - ? `url(/api/public/${subject.id}-banner.webp)` + ? `url(/api/public/${subject.banner}.webp)` : undefined; $: $page && (showMenu = false); </script> -<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]" /> +{#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> - </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)}> + {/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 === `/${user.username}` || - $page.url.pathname === `/${user.username}/settings` || - $page.url.pathname === `/support` + .url.pathname === `/send` ? '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" /> + : '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" + <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 > - <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-[138px] left-[calc(50vw-64px)] -bottom-[64px] z-30" - > - <Avatar user={subject} /> - </div> -</header> + </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 { diff --git a/src/components/Avatar.svelte b/src/components/Avatar.svelte index ac2dfdda..d39dd8ba 100644 --- a/src/components/Avatar.svelte +++ b/src/components/Avatar.svelte @@ -13,7 +13,7 @@ $: src = ($avatar?.id && $avatar.id === user.id && $avatar.src) || '/api/public/' + - (user.profile ? user.id + '-profile.webp' : 'punks/' + punk(user.pubkey || user.id || 'aa')); + (user.profile + '.webp' || 'punks/' + punk(user.pubkey || user.id || 'aa')); </script> <a href={link} class:pointer-events-none={disabled}> diff --git a/src/lib/punks.js b/src/lib/punks.js new file mode 100644 index 00000000..4d9f2e60 --- /dev/null +++ b/src/lib/punks.js @@ -0,0 +1,66 @@ +export default [ + '5211890e4128d8d730802cc75ee105867e8878581d5e72ffc98a0d60027a5398', + 'd1ca262272613d7386fecce7f1ad268188940b59ea83fdd1f8d7ab85771e3f43', + '62a55d4d6e4b6f992a1c1840f041585c957affcdbf875202cf9e1db9def05aeb', + 'd3018e45c66dd485697f738ad191af98267e6255d562496dd06b1a662e0c7a88', + '7354f3829dd05c838aa16d66b00316f7994797927a4d3b594c8af04a2b4ae86a', + '9018be885177c675baf9b36978428d841ca30a9185e11de1a31238a08e3ec1af', + '11701e76233395ebdba8d1d50334d1a0cc3a158f3452260c9a7b9a067b78d753', + '0ec2e8d07e053d686140857493ece653cde52764a60aa1d25651d81811d44a3d', + 'c1dececcbd7b623bfaa726392512e6ea110db93a0649dd01bf9e559318514f7c', + '01aa9348244c4b65de3df758d49c98076e17a109aef04bc211abc77cf42dc51d', + 'a80951fc593035abb2dda045a2076d29a30f5cc470e0a20db39c3ec588ed61d8', + 'ba8dd5a7070aee7102d4f8bc83ac497091e2e473a0f448a1c513569167dd0791', + '3f8f861e663accee51e68b987705ce647a7446b7f118abc94c632e158acbb44f', + '777c5715de7423597fea6c03c496ea46414f0508cd96e119261f002965beb805', + 'e19eb1eeabdeffa04ccd792eb09776690cf2121887b26a7c5d20d66e3e8fd0fb', + 'db5fe28b8ddb9ae5e4f5e0e757ee60bc938bffbdfdd38de807939e212d3c5066', + '914813583b901044970b9c7d6dcdfde4344339325f5988d0dd681d377f1243fb', + '8c18c036b257d7b9c5a970a786efa04c320f3cf5afa73fa310a6603744d1ee3a', + '2aaba839e8546a8cb6d04a81ff5be83ac4e700ca4491607a16ebd60dc4995750', + 'b742b7509a97c2b346ea762f0510e8475b30dc988417f0f146ae00a800c6dd56', + '836d72150b6f3033b6220d15ac9d49457d81041a700d12898bd3b8e737b4d265', + '8ba4736edc68da8e5b21c23af4de1f9131f47778c2a5b2a970a799223a9380b3', + '4f07e1da3685a858970a9f39300564971e97257f5d37b55a6d9882014aecc421', + '698a2ca7a09e88fa6594801da037f4dbce6ae841d1d33736aebcf8f6fe104e01', + 'd5b5b2167602f7277cdac76cfda8bdad0befcb8ff12df495dfabf40c91d1120c', + '50c7c2e8e8c7f4aabffb195187a1ac1105f5712859f02d491d794ba2d8498e08', + '6e1f1626a9308abd0b7732fecbc7d49366ced46dead63ef3603fb0f4ec7aa53d', + '85cb7cf0245613d08b3027f879f58a6db645bf0db107242beb856b340ee49ee8', + 'd6dbb18d5df5e92c8c74ee59aa86d19dff25a5d0f9d030e84285a686f86804d7', + 'eccc90741370ba4acc70bc65f4ccc38fddb1327d977c366156018974bc52fe4a', + '78ae0118856a1f17c13f292628fcf3dccc150c7278578caaceb9f35ca8bc9f55', + '43ce93c9835e983403a01329918d184832c5516e1b0ea4e0878b4c4def042777', + '5f88f7f579ce50d7bcf60d6e9556f77acf361ba54caf3eb61dc4f2ca9c94c6a2', + '43bab95a047112dca6705506f916a97252363a20da4a4c0a5ab7b676e11083fc', + '08ce4840184f63a779bcd1f91bbcb56a2e8cba75c3ba2dbd290a559a57e4238e', + '4595bb092682af7177799464364c047fbfa7ede767336734949bce5d085fce15', + 'c84724a398bda8d20436fb0663d059c196628fa71192bb720fa5adf6137bed9b', + '65309f82cd9f9926a578d09e1ad13909f68a06d02b853760731a4225f7aecef4', + '433519d13f2b64fe82a90cd054797d4779507fea93c1054bd51c35ba846a0f7f', + '4d8ad7a761d068b7ef9b058fb4ba924b276e7a45a3ad47a79b767e08e821e038', + '2cc94821f56d21918ba4f2770d97f280e06fd99f4199167e2f4ddc780f404c8a', + '7849ac0f31056265efb0110038f86e61effe17b7db836377d88433b73312e65a', + '2155224ef35cfb4bbdb06d982b42bb9b93d97b2bc691c22c804e1759a0a04882', + '9d9a9a40c490f05d7dbafde8bf60d68aab7dcb79a7976eef62d1448a4549816b', + '5f6549ad9dd15dd57c204b1a9b60c68c376e77d7d114cfa13219e6a1a71f901f', + 'f46310b2e60727af071d09e759814f52f933688a0ff0995c7da373b381e70005', + 'f307620a90a36045fa550316d61be4755c1f6c7f3fc81c95fc4d590ac568b2c4', + '6f2618272bb0164247e9b0f40b19f020c1c9253510dc658a3cc078d079ef01bf', + '6dbec748ea87e959390b0df429c005fccffe9e672864f555c14c7a407b940870', + 'a7a6187e54e83acdb893d456c8b4ad6877e4738ea9e0d47c9b2070bf2c98a35f', + '3759946d9e875ac2fc5c51ed850686193bb1e0716219e42efd52f3965993b275', + 'f6017bec50563dbbb24b979214ae225f96b01dea5b090b174038ff2842a1c6b1', + 'fd502d5007bfc62a91eb7a34c1ae23b9de4a20cf5e1bbd86dadc421d7083099c', + 'e3d1c40538a8aed282a430647e3ff16ff94b64b8ed0a7cf427e8b0e2780a9b2a', + '27caa38c14a85ad64b4c4469a34e61a3965a1cbc4cad9a85707da374301c830d', + '080574eb9e0613926e0e5d9b667c132e12965a1b1d47218a0b290a2d55c55d2f', + '14d254333c6240fbdacbbbe261f7ae15be2e83bffcf084f4f59f459bae0a4571', + '82390020b2bca461204cc49b20ee6fec1b93dad476726f377785d04040a4d6c3', + '7af3029a826e6dfba23ad502901a3632ef4ee047bd45d618b1f106717b0af7ea', + '6bc2990699be7f610c9f6a49bc7a19b034040727bc3de63fee89a1573fe00673', + '96ea95735b61c48c6f465db8ca0246a62539f34c3a977be6338770d92461da89', + '4f1b39906714266a7a1936bfa8e08c5d891285dd2b188b4bcd4afb0107671004', + '6524936709e0719f9e28bf1caac91f86f323d1a79f10ab65a03fa9ea2e6b3270', + '5d1d3769cd141603ce907df5dca159e389ca4d75338eddbe1b7db1c0766cbfd6' +]; diff --git a/src/lib/upload.js b/src/lib/upload.js index d8b27061..92f5290f 100644 --- a/src/lib/upload.js +++ b/src/lib/upload.js @@ -1,5 +1,4 @@ export const upload = async (file, type, progress, token) => { - console.log('TYPE', type); let url = `/api/upload/${type}`; let formData = new FormData(); formData.append('file', file); @@ -14,7 +13,7 @@ export const upload = async (file, type, progress, token) => { }); ajax.upload.addEventListener('progress', progress, false); ajax.open('POST', url); - ajax.setRequestHeader('Authorization', `Bearer ${token}`); + // ajax.setRequestHeader('Authorization', `Bearer ${token}`); ajax.send(formData); }); }; diff --git a/src/locales/en.json b/src/locales/en.json index 2f629675..36cb3518 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -121,7 +121,7 @@ "username": "Username", "password": "Password", "email": "Email", - "passwordRecommendation": "We recommend a long, random password generated with a password manager.", + "passwordRecommendation": "Change to a longer random password if you intend to keep significant funds in the account.", "forgotUserOrPassword": "Forgot Username or Password?", "haveAccount": "Already have an account?", "noAccount": "Don't have an account?", diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index bcff6ef6..012dd957 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -98,7 +98,7 @@ {/if} {#if subject?.profile} - <meta name="og:image" content={`/api/public/${subject.uuid}-profile.webp`} /> + <meta name="og:image" content={`/api/public/${subject.profile}.webp`} /> {:else} <meta property="og:image" content="/icons/logo.svg" /> {/if} diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte index 1b99c4b4..bd2bf0d3 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.svelte +++ b/src/routes/(app)/[username]/(profile)/+layout.svelte @@ -61,58 +61,66 @@ <div class="container mx-auto w-full px-4 mb-4 flex flex-wrap lg:flex-nowrap space-y-5"> <div class="hidden lg:block lg:w-[300px]" /> <div class="lg:w-[300px] lg:absolute space-y-5 left-20 mx-auto max-w-md"> - <div - class="flex text-3xl font-bold text-center mx-auto justify-center gap-2" - on:click={toggleDetails} - > + <div class="flex text-3xl font-bold text-center mx-auto justify-center gap-2"> <div class="my-auto">{display || username}</div> - <Icon icon="stats" style="w-12 rounded cursor-pointer" /> + <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="font-bold">{$t('user.profilePage')}</div> <div class="flex gap-2"> <div class="break-all grow text-secondary text-lg"> {profile} </div> <div class="flex mb-auto gap-1"> - <button class="my-auto opacity-60 hover:opacity-100" on:click={() => copy(profile)} + <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 opacity-60 hover:opacity-100"> + <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="font-bold">{$t('user.lightningAddress')}</div> <div class="flex gap-2"> <div class="break-all grow text-secondary text-lg"> {lnaddr} </div> <div class="flex mb-auto gap-1"> - <button class="my-auto opacity-60 hover:opacity-100" on:click={() => copy(lnaddr)} + <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 opacity-60 hover:opacity-100"> + <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="font-bold">{$t('user.nostrPubkey')}</div> <div class="flex gap-2"> <div class="break-all grow text-secondary text-lg"> {npub} </div> <div class="flex my-auto gap-1"> - <button class="my-auto opacity-60 hover:opacity-100" on:click={() => copy(npub)} + <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 opacity-60 hover:opacity-100"> + <a + href={`/qr/${encodeURIComponent(npub)}`} + class="my-auto" + > <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> </a> </div> diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index e3150ff2..42cf9527 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -96,20 +96,20 @@ <div class="flex flex-wrap gap-4 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" + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full bg-black text-white" > <div class="mx-auto flex"> - <Icon icon="numpad" style="my-auto h-6 mr-2" /> + <Icon icon="numpad" style="my-auto h-6 mr-2 invert" /> <div class="my-auto mt-1">{$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" + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full bg-black text-white" > <div class="mx-auto flex"> - <Icon icon="send" style="my-auto h-6 mr-2" /> + <Icon icon="send" style="my-auto h-6 mr-2 invert" /> <div class="my-auto mt-1">{$t('user.dashboard.send')}</div> </div> </button> diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte index 762e836e..087c9450 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte @@ -68,13 +68,31 @@ } if ($avatar) { - await upload($avatar.file, $avatar.type, $avatar.progress, token); - await fetch(`/api/public/${id}-profile.webp`, { cache: 'reload', mode: 'no-cors' }); + 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); + } } if ($banner) { - await upload($banner.file, $banner.type, $banner.progress, token); - await fetch(`/api/public/${id}-banner.webp`, { cache: 'reload', mode: 'no-cors' }); + try { + let { hash } = JSON.parse( + await upload($banner.file, $banner.type, $banner.progress, token) + ); + + data.set('banner', hash); + + await fetch(`/api/public/${hash}.webp`, { cache: 'reload', mode: 'no-cors' }); + } catch (e) { + console.log('problem uploading banner', e); + } } let event = { @@ -84,7 +102,7 @@ content: JSON.stringify({ name: user.username, about: user.address, - picture: `${PUBLIC_COINOS_URL}/public/${user.id}-profile.webp` + picture: `${PUBLIC_COINOS_URL}/public/${user.profile}.webp` }), tags: [] }; diff --git a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte index 26c327c0..15d286c8 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte @@ -5,7 +5,7 @@ import { t } from '$lib/translations'; import { page } from '$app/stores'; - export let user; + export let user, rates, submit; let { id } = user; let selectedTheme = 1; @@ -95,7 +95,7 @@ on:keydown={selectAvatar} > <img - src={$avatar?.src || `/api/public/${user.id}-profile.webp`} + src={$avatar?.src || `/api/public/${user.profile}.webp`} class="absolute w-full h-full object-cover object-center visible overflow-hidden" alt={user.username} /> @@ -138,7 +138,7 @@ {#if $banner || user.banner} <img - src={$banner ? $banner.src : `/api/public/${user.id}-banner.webp`} + 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} diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte index 057d8c20..a96528a0 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte @@ -4,7 +4,7 @@ import { success, fail } from '$lib/utils'; import { page } from '$app/stores'; - export let user, rates; + export let user, rates, submit; let fiats = Object.keys(rates); </script> @@ -25,7 +25,7 @@ <div> <div class="flex justify-between items-center"> <span class="font-bold">{$t('user.settings.tipPrompt')}</span> - <Toggle id="prompt" checked={user.prompt} bind:value={user.prompt} /> + <Toggle id="prompt" bind:value={user.prompt} /> </div> <p class="text-secondary mt-1 w-9/12"> {$t('user.settings.tipPromptDescription')} diff --git a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte index 85f461ad..170df744 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte @@ -8,7 +8,7 @@ import { invalidate } from '$app/navigation'; import { page } from '$app/stores'; - export let user, submit; + export let user, rates, submit; let confirming2fa, disabling2fa, diff --git a/src/routes/(app)/register/+page.server.js b/src/routes/(app)/register/+page.server.js index c90d4ee3..f2b49e3f 100644 --- a/src/routes/(app)/register/+page.server.js +++ b/src/routes/(app)/register/+page.server.js @@ -1,9 +1,32 @@ +import { + NumberDictionary, + uniqueNamesGenerator, + colors, + adjectives, + animals +} from 'unique-names-generator'; 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 username = uniqueNamesGenerator({ + dictionaries: [animals, NumberDictionary.generate({ min: 10, max: 99 })], + length: 2, + separator: '' + }); + + let password = uniqueNamesGenerator({ + dictionaries: [colors, NumberDictionary.generate({ min: 100, max: 999 })], + length: 2, + separator: '' + }); + + let index = Math.floor(Math.random() * 64) + 1; + + return { index, username, password }; }; export const actions = { @@ -11,8 +34,8 @@ export const actions = { let ip = request.headers.get('cf-connecting-ip'); let form = await fd(request); - let { username, password, cipher, salt, pubkey, loginRedirect } = form; - let user = { username, password, cipher, salt, pubkey }; + let { profile, username, password, cipher, salt, pubkey, loginRedirect } = form; + let user = { profile, username, password, cipher, salt, pubkey }; let error; if (loginRedirect === 'undefined') loginRedirect = undefined; @@ -20,7 +43,7 @@ export const actions = { try { await post('/register', { user }, { 'cf-connecting-ip': ip }); } catch (e) { - console.log(e) + console.log(e); if (e.message.includes('taken')) error = e.message; } diff --git a/src/routes/(app)/register/+page.svelte b/src/routes/(app)/register/+page.svelte index fd9e4417..37ff67c8 100644 --- a/src/routes/(app)/register/+page.svelte +++ b/src/routes/(app)/register/+page.svelte @@ -1,7 +1,33 @@ <script> + import punks from '$lib/punks'; + import { upload } from '$lib/upload'; import { afterNavigate, invalidateAll } from '$app/navigation'; - import { RegisterOrLogin } from '$comp'; + 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'; + export let form; + export let data; + + let cleared; + let clear = () => { + if (!cleared) { + cleared = true; + username = ''; + $password = ''; + revealPassword = false; + } + }; + + let { index, username } = data; + $password = data.password; afterNavigate(async () => { try { @@ -10,6 +36,221 @@ console.log(e); } }); + + let token, formElement; + let code = []; + let redirect; + + $: need2fa = form?.message === '2fa'; + let cancel = () => (need2fa = false); + $: if (form?.message === '2fa' && form.token === token) token = ''; + + let email, btn; + + $: update(form); + let update = (form) => form && ({ username, password: $password } = form); + + $: token && token?.length === 6 && tick().then(() => btn.click()); + + let revealPassword = true; + + let loading; + async function handleSubmit(e) { + loading = true; + + let data = new FormData(this); + let user = Object.fromEntries(data); + await generate(user); + + 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', 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 result = deserialize(await response.text()); + + if (result.type === 'success') { + await invalidateAll(); + } + + applyAction(result); + loading = false; + } + + let avatarInput; + $: src = `/api/public/${punks[index]}.webp`; + let decr = () => (index = index <= 1 ? 64 : index - 1); + let incr = () => (index = index >= 64 ? 1 : index + 1); + let selectAvatar = () => avatarInput.click(); + + 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 }; + + var reader = new FileReader(); + reader.onload = async (e) => { + src = e.target.result; + }; + + reader.readAsDataURL(file); + }; </script> -<RegisterOrLogin pageID="register" bind:form /> +{#if need2fa} + <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="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.register')}</h1> + + <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-8 bg-white rounded top-12" on:click={decr}> + <Icon icon="chevron-left" style="w-8 " /> + </button> + <div class="relative w-36 mx-auto" on:click={selectAvatar}> + <button + class="w-36 h-36 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-8 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} + + <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} + on:focus={clear} + 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" + on:focus={clear} + /> + {/if} + <button + type="button" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-5 top-10" + > + <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> + </button> + </div> + + <p class="text-secondary text-sm"> + {$t('login.passwordRecommendation')} + </p> + + <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.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> +</div> diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 7e9cb755..2cd0c473 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,5 +1,6 @@ <script> import '../app.css'; + import { loading, t } from '$lib/translations'; export let data; let { host, pathname } = data; @@ -26,7 +27,9 @@ <meta name="twitter:creator" content="@coinoswallet" /> </svelte:head> +{#if !$loading} <slot /> +{/if} <style global> :root { diff --git a/src/routes/+page.server.js b/src/routes/+page.server.js index 7936e88b..9e4b62e2 100644 --- a/src/routes/+page.server.js +++ b/src/routes/+page.server.js @@ -8,5 +8,6 @@ export let load = async () => { data = await get('/locations'); } catch (e) {} + data.faqs = ['cost', 'compatibility', 'safety', 'pos', 'bitcoin']; return data; }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 0aaf887d..9d2ba213 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -12,11 +12,11 @@ import { t } from '$lib/translations'; import { close } from '$lib/socket'; - import { onMount } from 'svelte'; + import { onDestroy, onMount } from 'svelte'; export let data; - let { locations, user } = data; - let update = (data) => ({ locations, user } = data); + let { faqs, locations, user } = data; + let update = (data) => ({ faqs, locations, user } = data); $: update(data); let howItWorks; @@ -24,7 +24,6 @@ let about; let log = console.log; - let faqs = {}; const howItWorksSteps = [ { image: 'hand', stepID: 'step1' }, @@ -32,9 +31,9 @@ { image: 'smile', stepID: 'step3' } ]; - onMount(async () => { - faqs = (await import('../locales/en.json')).default.faq; + let loaded; + onMount(async () => { close(); try { @@ -42,62 +41,68 @@ } catch (e) { console.log(e); } + + loaded = true; }); + + onDestroy(() => (loaded = false)); </script> <svelte:head> <script src="https://stamen-maps.a.ssl.fastly.net/js/tile.stamen.js"></script> </svelte:head> -<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 {user} /> - <LandingInfoCard - image="lightning-qr" - title={$t('landing.info1.title')} - description={$t('landing.info1.description')} - /> +{#if loaded} + <LandingHeader {howItWorks} {faq} {about} {user} /> - <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> + <main class="space-y-40 py-20 md:py-32 lg:py-36 xl:py-40 px-5 md:px-0"> + <LandingHero {user} /> <LandingInfoCard - image="customize" - title={$t('landing.info3.title')} - description={$t('landing.info3.description')} + image="lightning-qr" + title={$t('landing.info1.title')} + description={$t('landing.info1.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} + <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> + <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 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 Object.keys(faqs).filter((f) => f !== 'header') as faqID} - <FaqCard questionID={faqID} /> - {/each} + <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 bind:this={about} /> - </div> - <About {locations} /> -</main> + <About {locations} /> + </main> -<Footer /> + <Footer /> +{/if} |
