aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2023-11-15 23:02:29 -0800
committerAdam Soltys <[email protected]>2023-11-15 23:02:29 -0800
commitb466b3d39c3b9ec792ef924c7f4750fb70b6a016 (patch)
tree9f0071056f7f53129e1eb81a1bca268300d41a05
parent6d8eb8a609ebb6c1781c463a837bf9e19a6d3d4b (diff)
downloadcoinos-ui-b466b3d39c3b9ec792ef924c7f4750fb70b6a016.tar.gz
coinos-ui-b466b3d39c3b9ec792ef924c7f4750fb70b6a016.zip
prefill register page
-rw-r--r--package.json4
-rw-r--r--pnpm-lock.yaml16
-rw-r--r--src/components/AppHeader.svelte193
-rw-r--r--src/components/Avatar.svelte2
-rw-r--r--src/lib/punks.js66
-rw-r--r--src/lib/upload.js3
-rw-r--r--src/locales/en.json2
-rw-r--r--src/routes/(app)/+layout.svelte2
-rw-r--r--src/routes/(app)/[username]/(profile)/+layout.svelte36
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte8
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/+page.svelte28
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_account.svelte6
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_pos.svelte4
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_security.svelte2
-rw-r--r--src/routes/(app)/register/+page.server.js29
-rw-r--r--src/routes/(app)/register/+page.svelte245
-rw-r--r--src/routes/+layout.svelte3
-rw-r--r--src/routes/+page.server.js1
-rw-r--r--src/routes/+page.svelte103
-rw-r--r--static/icons/upload.svg4
20 files changed, 573 insertions, 184 deletions
diff --git a/package.json b/package.json
index 2063fb63..911e287f 100644
--- a/package.json
+++ b/package.json
@@ -52,10 +52,12 @@
},
"type": "module",
"dependencies": {
+ "@faker-js/faker": "^8.2.0",
"@scure/bip39": "^1.2.1",
"html2canvas": "^1.4.1",
"leaflet-tooltip-layout": "^0.1.2",
"maplibre-gl": "^3.3.1",
- "uint8array-extras": "^0.5.0"
+ "uint8array-extras": "^0.5.0",
+ "unique-names-generator": "^4.7.1"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c6490728..1463027a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
dependencies:
+ '@faker-js/faker':
+ specifier: ^8.2.0
+ version: 8.2.0
'@scure/bip39':
specifier: ^1.2.1
version: 1.2.1
@@ -20,6 +23,9 @@ dependencies:
uint8array-extras:
specifier: ^0.5.0
version: 0.5.0
+ unique-names-generator:
+ specifier: ^4.7.1
+ version: 4.7.1
devDependencies:
'@scure/base':
@@ -319,6 +325,11 @@ packages:
dev: true
optional: true
+ /@faker-js/[email protected]:
+ resolution: {integrity: sha512-VacmzZqVxdWdf9y64lDOMZNDMM/FQdtM9IsaOPKOm2suYwEatb8VkdHqOzXcDnZbk7YDE2BmsJmy/2Hmkn563g==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'}
+ dev: false
+
/@jridgewell/[email protected]:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
dev: true
@@ -2011,6 +2022,11 @@ packages:
set-value: 2.0.1
dev: false
+ resolution: {integrity: sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==}
+ engines: {node: '>=8'}
+ dev: false
+
resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
hasBin: true
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}
diff --git a/static/icons/upload.svg b/static/icons/upload.svg
new file mode 100644
index 00000000..b11b621a
--- /dev/null
+++ b/static/icons/upload.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M12 10V16M12 10L10 12M12 10L14 12M12.0627 6.06274L11.9373 5.93726C11.5914 5.59135 11.4184 5.4184 11.2166 5.29472C11.0376 5.18506 10.8425 5.10425 10.6385 5.05526C10.4083 5 10.1637 5 9.67452 5H6.2C5.0799 5 4.51984 5 4.09202 5.21799C3.71569 5.40973 3.40973 5.71569 3.21799 6.09202C3 6.51984 3 7.07989 3 8.2V15.8C3 16.9201 3 17.4802 3.21799 17.908C3.40973 18.2843 3.71569 18.5903 4.09202 18.782C4.51984 19 5.07989 19 6.2 19H17.8C18.9201 19 19.4802 19 19.908 18.782C20.2843 18.5903 20.5903 18.2843 20.782 17.908C21 17.4802 21 16.9201 21 15.8V10.2C21 9.0799 21 8.51984 20.782 8.09202C20.5903 7.71569 20.2843 7.40973 19.908 7.21799C19.4802 7 18.9201 7 17.8 7H14.3255C13.8363 7 13.5917 7 13.3615 6.94474C13.1575 6.89575 12.9624 6.81494 12.7834 6.70528C12.5816 6.5816 12.4086 6.40865 12.0627 6.06274Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg> \ No newline at end of file