aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2023-02-06 16:40:14 -0800
committerAdam Soltys <[email protected]>2023-02-06 16:40:19 -0800
commit66740dea41f2772d46e1b13b91ef0e7b1a68bfff (patch)
tree2ff8926078222020c05252e64da6ae5e9c9cbbef
parent916f331e0e9a051830ce988639bb2149788e8228 (diff)
downloadcoinos-ui-66740dea41f2772d46e1b13b91ef0e7b1a68bfff.tar.gz
coinos-ui-66740dea41f2772d46e1b13b91ef0e7b1a68bfff.zip
new pinpad component
-rw-r--r--package.json1
-rw-r--r--pnpm-lock.yaml6
-rw-r--r--src/components/Feed.svelte4
-rw-r--r--src/components/Password.svelte6
-rw-r--r--src/components/Pin.svelte26
-rw-r--r--src/components/Pinpad.svelte81
-rw-r--r--src/components/RegisterOrLogin.svelte203
-rw-r--r--src/components/index.js1
-rw-r--r--src/lib/nostr.js2
-rw-r--r--src/lib/utils.js2
-rw-r--r--src/locales/en.json2
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte5
-rw-r--r--src/routes/(app)/[username]/(profile)/messages/+page.svelte4
-rw-r--r--src/routes/(app)/[username]/dashboard/+page.svelte2
-rw-r--r--src/routes/(app)/[username]/generate/+page.svelte6
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/+page.svelte8
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_pos.svelte2
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_security.svelte161
-rw-r--r--src/routes/(app)/disable2fa/+server.js4
-rw-r--r--src/routes/(app)/otpsecret/+server.js2
-rw-r--r--src/routes/(app)/send/[...text]/+page.svelte4
-rw-r--r--static/icons/eye-off.svg73
-rw-r--r--static/icons/eye.svg52
23 files changed, 395 insertions, 262 deletions
diff --git a/package.json b/package.json
index b6b32ee6..908ec0bf 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,6 @@
"screenfull": "^6.0.2",
"svelte": "^3.55.0",
"svelte-outclick": "^3.3.3",
- "svelte-pincode": "^2.2.0",
"svelte-preprocess": "^4.10.7",
"svelte-virtual-scroll-list": "^1.1.0",
"sveltekit-i18n": "^2.2.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d072ee80..4f0d2ef2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,7 +26,6 @@ specifiers:
screenfull: ^6.0.2
svelte: ^3.55.0
svelte-outclick: ^3.3.3
- svelte-pincode: ^2.2.0
svelte-preprocess: ^4.10.7
svelte-virtual-scroll-list: ^1.1.0
sveltekit-i18n: ^2.2.2
@@ -66,7 +65,6 @@ devDependencies:
screenfull: 6.0.2
svelte: 3.55.0
svelte-outclick: [email protected]
- svelte-pincode: 2.2.0
svelte-preprocess: 4.10.7_qs7rgzvahok4cbkjoy5bgypde4
svelte-virtual-scroll-list: [email protected]
sveltekit-i18n: [email protected]
@@ -2406,10 +2404,6 @@ packages:
svelte: 3.55.0
dev: true
- /svelte-pincode/2.2.0:
- resolution: {integrity: sha512-zT2jOHffZqQ1dLmFAojfkdssKr8HGq1uSZQK1TCwD1/Mjrw2EGuKppQOh0q2pobm8AAGhvk7LKHnFtRUKsz80A==}
- dev: true
-
/svelte-preprocess/4.10.7_qs7rgzvahok4cbkjoy5bgypde4:
resolution: {integrity: sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw==}
engines: {node: '>= 9.11.2'}
diff --git a/src/components/Feed.svelte b/src/components/Feed.svelte
index ab757fa1..7de087a7 100644
--- a/src/components/Feed.svelte
+++ b/src/components/Feed.svelte
@@ -4,7 +4,7 @@
import { Event } from '$comp';
import { browser } from '$app/environment';
import VirtualScroll from 'svelte-virtual-scroll-list';
- import { failure } from '$lib/utils';
+ import { fail } from '$lib/utils';
import { sign, send } from '$lib/nostr';
export let events;
@@ -34,7 +34,7 @@
events = events;
} catch (e) {
console.log(e, e.stack);
- failure('Problem posting event');
+ fail('Problem posting event');
}
};
diff --git a/src/components/Password.svelte b/src/components/Password.svelte
index fd39441c..a84c0b16 100644
--- a/src/components/Password.svelte
+++ b/src/components/Password.svelte
@@ -1,6 +1,6 @@
<script>
import { Icon } from '$comp';
- import { focus, failure, post } from '$lib/utils';
+ import { focus, fail, post } from '$lib/utils';
import { password as pw, passwordPrompt } from '$lib/store';
export let user;
@@ -17,7 +17,7 @@
$passwordPrompt = false;
$pw = password;
} catch (e) {
- failure('Invalid password, try again');
+ fail('Invalid password, try again');
}
};
</script>
@@ -51,7 +51,7 @@
<button
type="button"
on:click={() => (revealPassword = !revealPassword)}
- class="absolute right-5 top-5"
+ class="absolute right-5 top-6"
>
<Icon icon={revealPassword ? 'eye' : 'eye-off'} />
</button>
diff --git a/src/components/Pin.svelte b/src/components/Pin.svelte
index 7225ac37..0db831ff 100644
--- a/src/components/Pin.svelte
+++ b/src/components/Pin.svelte
@@ -1,24 +1,28 @@
<script>
+ import { t } from '$lib/translations';
import { focus } from '$lib/utils';
- import { pin } from '$lib/store';
+ import { Pinpad } from '$comp';
- let pinCode = '';
- let pinInput;
+ export let value = '';
+ export let title = $t('user.settings.verifyPIN');
+ export let cancel = () => (pin = '');
- let cancel = () => ($pin = '000000');
+ let pin = '';
- $: pinCode.length > 5 && ($pin = pinCode);
+ $: update(value);
+ let update = (v) => (pin = v) || console.log('updated', v, pin);
+
+ $: pin.toString().length > 5 && (value = pin.toString());
</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 PIN</h1>
- <input bind:value={pinCode} use:focus />
+<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={pin} {cancel} />
<div class="w-full flex">
<button
- type="submit"
class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto"
- on:click={cancel}
+ on:click|preventDefault={cancel}
>
<div class="my-auto">Cancel</div>
</button>
diff --git a/src/components/Pinpad.svelte b/src/components/Pinpad.svelte
new file mode 100644
index 00000000..bf3778f7
--- /dev/null
+++ b/src/components/Pinpad.svelte
@@ -0,0 +1,81 @@
+<script>
+ import { Left, Icon } from '$comp';
+ import { pin, selectedRate } from '$lib/store';
+ import { f, s, focus, post, warning, sat, sats } from '$lib/utils';
+ import { t } from '$lib/translations';
+
+ export let v;
+ export let cancel;
+
+ let arrow = '<';
+ let loading = false;
+
+ 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;
+ };
+
+ 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;
+</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="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/RegisterOrLogin.svelte b/src/components/RegisterOrLogin.svelte
index 7ac2fbb3..6c099178 100644
--- a/src/components/RegisterOrLogin.svelte
+++ b/src/components/RegisterOrLogin.svelte
@@ -1,28 +1,32 @@
<script>
import { applyAction, deserialize } from '$app/forms';
import { tick } from 'svelte';
- import { Pincode, PincodeInput } from 'svelte-pincode';
import { fly } from 'svelte/transition';
import { enhance } from '$app/forms';
- import { Icon, Spinner } from '$comp';
- import { focus } from '$lib/utils';
+ 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';
- let token, tokenInput, formElement;
+ export let form;
+ export let pageID;
+
+ let token, formElement;
let code = [];
let redirect;
$password = undefined;
$: need2fa = form?.message === '2fa';
- $: if (need2fa) tokenInput && tick().then(tokenInput.focusFirstInput);
-
- export let form;
- export let pageID;
+ let cancel = () => (need2fa = false);
+ $: if (form?.message === '2fa' && form.token === token) {
+ token = '';
+ console.log('TOKEN', token);
+ fail('Invalid token, try again');
+ }
let username, email, btn;
@@ -66,6 +70,10 @@
}
</script>
+{#if need2fa}
+ <Pin bind:value={token} title="Enter 2FA Code" {cancel} />
+{/if}
+
<div class="pt-10">
<div class="w-[243px] mx-auto mb-10">
<a href="/">
@@ -75,126 +83,97 @@
<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]">
- {#if need2fa}
- <h1 class="text-2xl font-bold text-center">Enter 2FA Code</h1>
+ <h1 class="text-2xl font-bold text-center">{$t('login.' + pageID)}</h1>
- {#if form?.token}
- <div class="text-red-600 text-center" in:fly>Invalid token, try again</div>
- {/if}
-
- <form class="space-y-5" {action} method="POST" use:enhance>
- <input type="hidden" name="username" value={username} />
- <input type="hidden" name="password" value={$password} />
- <input type="hidden" name="token" value={token} />
-
- <div>
- <Pincode bind:code bind:value={token} bind:this={tokenInput}>
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- </Pincode>
- </div>
- <button
- bind:this={btn}
- type="submit"
- class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80"
- >{$t('login.' + pageID)}
- </button>
- </form>
- {:else}
- <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} />
+ <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} />
+ <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>
+ <div class="relative">
+ <label for="password" class="block font-semibold">{$t('login.password')}</label>
+ {#if revealPassword}
<input
- name="username"
+ name="password"
type="text"
required
class="bg-primary"
- bind:value={username}
- use:focus
+ bind:value={$password}
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>
-
- {#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> -->
+ <input
+ name="password"
+ type="password"
+ required
+ class="bg-primary"
+ bind:value={$password}
+ autocapitalize="none"
+ />
{/if}
-
<button
- type="submit"
- class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80"
- disabled={loading}
+ type="button"
+ on:click={() => (revealPassword = !revealPassword)}
+ class="absolute right-5 top-10"
>
- {#if loading}
- <Spinner />
- {:else}
- {$t('login.' + pageID)}
- {/if}
+ <Icon icon={revealPassword ? 'eye' : 'eye-off'} />
</button>
- </form>
+ </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>
- {/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>
+
+ <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/index.js b/src/components/index.js
index 18a821ff..ab54e09f 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -1,3 +1,4 @@
+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';
diff --git a/src/lib/nostr.js b/src/lib/nostr.js
index 35d843b2..de19b477 100644
--- a/src/lib/nostr.js
+++ b/src/lib/nostr.js
@@ -3,7 +3,7 @@ import * as ecc from 'tiny-secp256k1';
import { generateMnemonic, entropyToMnemonic, mnemonicToEntropy, mnemonicToSeedSync } from 'bip39';
import { Buffer } from 'buffer';
import { bech32m } from 'bech32';
-import { bip32, failure, wait, post, stretch } from '$lib/utils';
+import { bip32, fail, wait, post, stretch } from '$lib/utils';
import { calculateId, signId } from 'nostr';
import { password as pw, passwordPrompt, pin, loginRedirect } from '$lib/store';
import { goto, invalidate } from '$app/navigation';
diff --git a/src/lib/utils.js b/src/lib/utils.js
index d0b0b1c2..3a644e1f 100644
--- a/src/lib/utils.js
+++ b/src/lib/utils.js
@@ -101,7 +101,7 @@ export const warning = (m) => {
}
});
};
-export const failure = (m) => {
+export const fail = (m) => {
toast.pop();
toast.push(m, {
theme: {
diff --git a/src/locales/en.json b/src/locales/en.json
index eb0039ac..b39571f6 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -241,7 +241,7 @@
"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 enableing 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",
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte
index 40c73ea4..1cf525a0 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -4,8 +4,11 @@
export let data;
let { events, subject, user } = data;
+ let v;
</script>
<div class="flex justify-center lg:justify-start">
- <Balance {user} />
+ {#if user?.id === subject.id}
+ <Balance {user} />
+ {/if}
</div>
diff --git a/src/routes/(app)/[username]/(profile)/messages/+page.svelte b/src/routes/(app)/[username]/(profile)/messages/+page.svelte
index 00960534..db7b1ced 100644
--- a/src/routes/(app)/[username]/(profile)/messages/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/messages/+page.svelte
@@ -5,7 +5,7 @@
import { scale } from 'svelte/transition';
import { enhance } from '$app/forms';
import { Icon } from '$comp';
- import { back, failure, focus } from '$lib/utils';
+ import { back, fail, focus } from '$lib/utils';
import { sign, send, encrypt, decrypt } from '$lib/nostr';
import { messages, password } from '$lib/store';
import { tick, onMount } from 'svelte';
@@ -64,7 +64,7 @@
sent = true;
} catch (e) {
- failure('Failed to send message');
+ fail('Failed to send message');
delete $messages[user.pubkey][subject.pubkey][event.id];
$messages = $messages;
}
diff --git a/src/routes/(app)/[username]/dashboard/+page.svelte b/src/routes/(app)/[username]/dashboard/+page.svelte
index d136943e..499034e6 100644
--- a/src/routes/(app)/[username]/dashboard/+page.svelte
+++ b/src/routes/(app)/[username]/dashboard/+page.svelte
@@ -1,6 +1,6 @@
<script>
import { enhance } from '$app/forms';
- import { f, s, post, failure, sat, sats } from '$lib/utils';
+ import { f, s, post, fail, sat, sats } from '$lib/utils';
import { tick } from 'svelte';
import { Avatar, Icon } from '$comp';
import { rate, password, selectedRate } from '$lib/store';
diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/[username]/generate/+page.svelte
index 6336062a..bd72592f 100644
--- a/src/routes/(app)/[username]/generate/+page.svelte
+++ b/src/routes/(app)/[username]/generate/+page.svelte
@@ -1,5 +1,5 @@
<script>
- import { failure, post, wait } from '$lib/utils';
+ import { fail, post, wait } from '$lib/utils';
import { Buffer } from 'buffer';
import { onMount } from 'svelte';
import { bech32m } from 'bech32';
@@ -41,9 +41,9 @@
} catch (e) {
$pin = '';
if (e.message?.startsWith('Pin')) {
- failure('Wrong pin, try again');
+ fail('Wrong pin, try again');
} else {
- failure('Failed to generate keys');
+ fail('Failed to generate keys');
goto('/');
}
}
diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte
index 1e8b31ce..5638a22b 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte
+++ b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte
@@ -5,7 +5,7 @@
import { Icon, Spinner, Pin } from '$comp';
import { t } from '$lib/translations';
- import { failure, post, success } from '$lib/utils';
+ import { fail, post, success } from '$lib/utils';
import { avatar, banner, password, pin } from '$lib/store';
import { upload } from '$lib/upload';
import { page } from '$app/stores';
@@ -34,7 +34,7 @@
$: form?.success && success('Settings saved!');
$: if (form?.message?.startsWith('Pin')) {
- failure('Wrong pin, try again');
+ fail('Wrong pin, try again');
$pin = '';
}
@@ -98,7 +98,7 @@
applyAction(result);
} catch (e) {
console.log(e);
- failure('Something went wrong');
+ fail('Something went wrong');
}
loading = false;
@@ -109,7 +109,7 @@
</script>
{#if loaded && user.haspin && $pin?.length !== 6}
- <Pin />
+ <Pin bind:value={$pin} />
{/if}
<form
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte
index 94dc8c88..472f4d07 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte
+++ b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte
@@ -1,7 +1,7 @@
<script>
import { Icon, LocaleSelector, Toggle } from '$comp';
import { t } from '$lib/translations';
- import { success, failure } from '$lib/utils';
+ import { success, fail } from '$lib/utils';
import { page } from '$app/stores';
export let user, rates;
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte
index cf969f1c..eb3b405e 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte
+++ b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte
@@ -1,80 +1,52 @@
<script>
import { tick } from 'svelte';
import { t } from '$lib/translations';
- import { Icon, Qr } from '$comp';
- import { Pincode, PincodeInput } from 'svelte-pincode';
- import { post, success, failure } from '$lib/utils';
+ import { Pin, Icon, Qr } from '$comp';
+ import { 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, submit;
- let settingPin, setting2fa, disabling2fa;
+ let pin, verify;
+ let settingPin, setting2fa, disabling2fa, confirming2fa;
let password, revealPassword;
let token = '';
- let pinCode = [];
- let verifyCode = [];
- let tokenInput, pinInput, verifyInput;
- let verifying = false;
let old;
let togglePassword = () => (revealPassword = !revealPassword);
- let startVerifying = () => {
- if (pinCode.join('').length < 6) return;
- verifying = true;
- };
-
- $: pin = pinCode.join('');
-
let otp;
- $: pinChange($current);
- let pinChange = async (pin) => {
- try {
- otp = await post('/otpsecret', { pin });
- } catch (e) {
- console.log(e);
- }
- };
let locked;
- $: verifyCode.join('').length > 5 && checkPin();
+ $: verifying = pin?.length > 5;
+ $: verify && checkPin(pin);
let checkPin = async () => {
old = $current;
- if (locked) return;
- locked = true;
-
- let verify = verifyCode.join('');
-
- pinCode = ['', '', '', '', '', ''];
- verifyCode = ['', '', '', '', '', ''];
- settingPin = false;
try {
if (pin.length > 5 && pin === verify) {
user.haspin = true;
$current = pin;
+ pin = '';
+ verify = '';
submit.click();
+ settingPin = false;
+ verifying = false;
} else {
- failure('Pin mismatch, try again');
+ fail('Pin mismatch, try again');
+ pin = '';
+ verify = '';
+ verifying = false;
settingPin = true;
- pinInput.focusFirstInput();
}
} catch (e) {
- failure('Problem setting PIN');
+ console.log(e);
+ fail('Problem setting PIN');
}
-
- verifying = false;
-
- await new Promise((r) => setTimeout(r, 500));
- locked = false;
};
- $: if (verifying) verifyInput && tick().then(verifyInput.focusFirstInput);
- $: if (settingPin) pinInput && tick().then(pinInput.focusFirstInput);
- $: if (setting2fa || disabling2fa) tokenInput && tick().then(tokenInput?.focusFirstInput);
-
let reset = () => {
token = '';
return true;
@@ -88,15 +60,35 @@
submit.click();
} catch (e) {
console.log(e);
- failure('Failed to disable pin');
+ fail('Failed to disable pin');
}
} else {
settingPin = true;
}
};
- let toggleEnabling = () => reset() && (setting2fa = !setting2fa);
- let toggleDisabling = () => reset() && (disabling2fa = !disabling2fa);
+ let startEnabling2fa = async () => {
+ reset();
+ try {
+ if (!otp) otp = await post('/otpsecret', { pin: $current });
+ } catch (e) {
+ console.log(e);
+ }
+
+ 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;
+ };
$: enable2fa(token);
let enable2fa = async (twoFa) => {
@@ -105,10 +97,10 @@
await post('/enable2fa', { token });
success('2FA enabled');
user.twofa = 1;
- toggleEnabling();
+ cancel();
}
} catch (e) {
- failure('Failed to enable 2FA, try again');
+ fail('Failed to enable 2FA, try again');
}
};
@@ -118,12 +110,12 @@
if (disabling2fa && token.length === 6) {
await post('/disable2fa', { token });
success('2FA disabled');
- user.twofa = 0;
- toggleDisabling();
+ delete user.twofa;
+ disabling2fa = false;
}
} catch (e) {
console.log(e);
- failure('Failed to disable 2FA, try again');
+ fail('Failed to disable 2FA, try again');
}
};
</script>
@@ -155,31 +147,10 @@
<p class="text-secondary mb-1">
{$t('user.settings.securityPINDescription')}
</p>
- {#if settingPin}
- <div class="flex my-4">
- <div class="mx-auto">
- <div class:hidden={verifying}>
- <Pincode on:complete={startVerifying} bind:code={pinCode} bind:this={pinInput}>
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- </Pincode>
- </div>
- <div class:hidden={!verifying}>
- <Pincode bind:code={verifyCode} bind:this={verifyInput}>
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- </Pincode>
- </div>
- </div>
- </div>
+ {#if verifying}
+ <Pin bind:value={verify} {cancel} />
+ {:else if settingPin}
+ <Pin bind:value={pin} title={$t('user.settings.setPIN')} {cancel} />
{:else}
<button type="button" class="primary" on:click={togglePin}
><Icon icon="lock" style="mr-1" />
@@ -193,6 +164,7 @@
<p class="text-secondary mb-4">
{$t('user.settings.twofaDescription')}
</p>
+
{#if setting2fa}
<a href={otp.uri}>
<Qr src={otp.qr} />
@@ -203,40 +175,25 @@
<b>{otp.secret}</b>
</div>
- <div class="text-center my-4">
- {$t('user.settings.oneTimeCode')}<br />
- <Pincode bind:value={token} bind:this={tokenInput}>
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- </Pincode>
- </div>
- {:else if disabling2fa}
- <div class="text-center my-4">
- {$t('user.settings.oneTimeCode')}<br />
- <Pincode bind:value={token} bind:this={tokenInput}>
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- </Pincode>
- </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={toggleDisabling}>
+ <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={toggleEnabling}>
+ <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} />
+ {/if}
</div>
<div>
diff --git a/src/routes/(app)/disable2fa/+server.js b/src/routes/(app)/disable2fa/+server.js
index 13fcb2b9..b2bb07ec 100644
--- a/src/routes/(app)/disable2fa/+server.js
+++ b/src/routes/(app)/disable2fa/+server.js
@@ -1,4 +1,4 @@
-import { error } from '@sveltejs/kit';
+import { error, json } from '@sveltejs/kit';
import { auth, post } from '$lib/utils';
export async function POST({ cookies, request }) {
@@ -6,7 +6,7 @@ export async function POST({ cookies, request }) {
let body = await request.json();
let res = await post('/disable2fa', body, auth(cookies));
- return new Response(JSON.stringify(res));
+ return json(res);
} catch (e) {
throw error(500, 'Problem disabling 2fa');
}
diff --git a/src/routes/(app)/otpsecret/+server.js b/src/routes/(app)/otpsecret/+server.js
index 31dbcec1..7d5c1ad9 100644
--- a/src/routes/(app)/otpsecret/+server.js
+++ b/src/routes/(app)/otpsecret/+server.js
@@ -9,7 +9,7 @@ export async function POST({ cookies, request }) {
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) {
- console.log(e);
+ if (e.message.includes('pin')) throw e;
throw new error(500, 'Problem saving secret');
}
}
diff --git a/src/routes/(app)/send/[...text]/+page.svelte b/src/routes/(app)/send/[...text]/+page.svelte
index e3d8091f..46f5e56a 100644
--- a/src/routes/(app)/send/[...text]/+page.svelte
+++ b/src/routes/(app)/send/[...text]/+page.svelte
@@ -6,7 +6,7 @@
import { browser } from '$app/environment';
import { t } from '$lib/translations';
import { AppHeader, Avatar, Icon, Spinner } from '$comp';
- import { back, failure } from '$lib/utils';
+ import { back, fail } from '$lib/utils';
import { format, parseISO } from 'date-fns';
export let data;
@@ -90,7 +90,7 @@
<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={`/${c.username}`}>
+ <a href={`/${c.username}/receive`}>
<div class="border-b p-2 last:border-b-0 hover:bg-gray-100">
<div class="flex">
<div>
diff --git a/static/icons/eye-off.svg b/static/icons/eye-off.svg
index 88504dd3..0e52158a 100644
--- a/static/icons/eye-off.svg
+++ b/static/icons/eye-off.svg
@@ -1,3 +1,72 @@
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3 3L6.58916 6.58916M21 21L17.4112 17.4112M13.8749 18.8246C13.2677 18.9398 12.6411 19 12.0005 19C7.52281 19 3.73251 16.0571 2.45825 12C2.80515 10.8955 3.33851 9.87361 4.02143 8.97118M9.87868 9.87868C10.4216 9.33579 11.1716 9 12 9C13.6569 9 15 10.3431 15 12C15 12.8284 14.6642 13.5784 14.1213 14.1213M9.87868 9.87868L14.1213 14.1213M9.87868 9.87868L6.58916 6.58916M14.1213 14.1213L6.58916 6.58916M14.1213 14.1213L17.4112 17.4112M6.58916 6.58916C8.14898 5.58354 10.0066 5 12.0004 5C16.4781 5 20.2684 7.94291 21.5426 12C20.8357 14.2507 19.3545 16.1585 17.4112 17.4112" stroke="#858A92" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+
+<svg
+ fill="#000000"
+ width="32"
+ height="25.580051"
+ viewBox="0 0 20.480001 16.371233"
+ version="1.1"
+ id="svg679"
+ sodipodi:docname="eye-off.svg"
+ inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs
+ id="defs683" />
+ <sodipodi:namedview
+ id="namedview681"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ showgrid="false"
+ inkscape:zoom="5.9750523"
+ inkscape:cx="-49.20459"
+ inkscape:cy="-11.966422"
+ inkscape:window-width="1920"
+ inkscape:window-height="1048"
+ inkscape:window-x="0"
+ inkscape:window-y="32"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg679" />
+ <title
+ id="title667">ionicons-v5-i</title>
+ <path
+ d="M 17.749699,16.371232 A 0.67923596,0.67923596 0 0 1 17.267158,16.17113 L 2.2488725,1.1528484 A 0.6826492,0.6826492 0 0 1 3.2139683,0.18775264 L 18.232249,15.206035 a 0.6826492,0.6826492 0 0 1 -0.48255,1.165197 z"
+ id="path669"
+ style="stroke-width:0.0426655" />
+ <path
+ d="m 10.226055,13.640635 c -1.7701939,0 -3.4772444,-0.523933 -5.0737896,-1.557294 C 3.698649,11.144699 2.3900958,9.8003072 1.3678283,8.2007742 v -0.0034 C 2.2185799,6.9783796 3.1503966,5.9475793 4.1513306,5.1164533 a 0.08533115,0.08533115 0 0 0 0.006,-0.1254362 L 3.3074318,4.1423984 A 0.08533115,0.08533115 0 0 0 3.1918084,4.1372784 C 2.128582,5.0332561 1.1417271,6.1323208 0.24447016,7.4191149 a 1.3618851,1.3618851 0 0 0 -0.027304,1.5163343 C 1.3439644,10.698817 2.7941672,12.184006 4.4103395,13.229739 c 1.8196865,1.17885 3.7780366,1.776168 5.8157435,1.776168 a 10.202619,10.202619 0 0 0 3.234055,-0.536733 0.08533115,0.08533115 0 0 0 0.03288,-0.141223 l -0.920729,-0.920723 a 0.1706623,0.1706623 0 0 0 -0.163409,-0.04266 8.7379096,8.7379096 0 0 1 -2.182772,0.276046 z"
+ id="path671"
+ style="stroke-width:0.0426655" />
+ <path
+ d="M 20.260142,7.4370604 C 19.131212,5.6911855 17.666508,4.20813 16.024732,3.1478908 14.208462,1.9737337 12.203178,1.3529497 10.226055,1.3529497 a 9.6995917,9.6995917 0 0 0 -3.1952251,0.5473996 0.08533115,0.08533115 0 0 0 -0.032,0.1412224 l 0.9194432,0.9194432 a 0.1706623,0.1706623 0 0 0 0.165542,0.042664 8.2267762,8.2267762 0 0 1 2.1422389,-0.2854329 c 1.736063,0 3.437995,0.5303332 5.058002,1.5786262 1.480928,0.9557086 2.804841,2.2988208 3.829668,3.882567 a 0.00554652,0.00554652 0 0 1 0,0.0068 13.257048,13.257048 0 0 1 -2.735722,3.1030668 0.08533115,0.08533115 0 0 0 -0.0064,0.125864 l 0.849044,0.848619 a 0.08533115,0.08533115 0 0 0 0.115199,0.0055 14.655199,14.655199 0 0 0 2.928569,-3.3483948 1.3738315,1.3738315 0 0 0 -0.004,-1.4839086 z"
+ id="path673"
+ style="stroke-width:0.0426655" />
+ <path
+ d="m 10.240561,4.0835467 a 4.0907753,4.0907753 0 0 0 -0.9117629,0.102397 0.08533115,0.08533115 0 0 0 -0.04266,0.1442097 L 14.08985,9.1325902 a 0.08533115,0.08533115 0 0 0 0.144216,-0.04266 4.0958952,4.0958952 0 0 0 -3.9935,-5.0063789 z"
+ id="path675"
+ style="stroke-width:0.0426655" />
+ <path
+ d="m 6.3912733,7.2262927 a 0.08533115,0.08533115 0 0 0 -0.1442097,0.042664 4.0958952,4.0958952 0 0 0 4.9065414,4.9065413 0.08533115,0.08533115 0 0 0 0.04266,-0.14421 z"
+ id="path677"
+ style="stroke-width:0.0426655" />
+ <metadata
+ id="metadata796">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:title>ionicons-v5-i</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
</svg>
diff --git a/static/icons/eye.svg b/static/icons/eye.svg
index 9cf04eb9..3869fee6 100644
--- a/static/icons/eye.svg
+++ b/static/icons/eye.svg
@@ -1,4 +1,50 @@
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z" stroke="#858A92" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M2.45825 12C3.73253 7.94288 7.52281 5 12.0004 5C16.4781 5 20.2684 7.94291 21.5426 12C20.2684 16.0571 16.4781 19 12.0005 19C7.52281 19 3.73251 16.0571 2.45825 12Z" stroke="#858A92" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+
+<svg
+ fill="#000000"
+ height="21.76594"
+ width="32"
+ version="1.1"
+ id="Capa_1"
+ viewBox="0 0 8.98196 6.1094"
+ xml:space="preserve"
+ sodipodi:docname="eye.svg"
+ inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs339" /><sodipodi:namedview
+ id="namedview337"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ showgrid="false"
+ inkscape:zoom="1.05625"
+ inkscape:cx="-300.11834"
+ inkscape:cy="1.4201183"
+ inkscape:window-width="1920"
+ inkscape:window-height="1048"
+ inkscape:window-x="0"
+ inkscape:window-y="32"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="Capa_1" />
+<g
+ id="g334"
+ transform="matrix(0.04,0,0,0.04,2e-5,-1.43628)">
+ <path
+ d="m 223.476,108.41 c -1.779,-2.96 -44.35,-72.503 -111.202,-72.503 -66.852,0 -109.423,69.543 -111.202,72.503 -1.43,2.378 -1.43,5.351 0,7.729 1.779,2.96 44.35,72.503 111.202,72.503 66.852,0 109.423,-69.543 111.202,-72.503 1.43,-2.378 1.43,-5.351 0,-7.729 z m -111.202,65.232 c -49.925,0 -86.176,-47.359 -95.808,-61.374 9.614,-14.032 45.761,-61.36 95.808,-61.36 49.925,0 86.176,47.359 95.808,61.374 -9.614,14.031 -45.761,61.36 -95.808,61.36 z"
+ id="path328" />
+ <path
+ d="m 112.274,61.731 c -27.869,0 -50.542,22.674 -50.542,50.543 0,27.868 22.673,50.54 50.542,50.54 27.868,0 50.541,-22.672 50.541,-50.54 0,-27.869 -22.672,-50.543 -50.541,-50.543 z m 0,86.083 c -19.598,0 -35.542,-15.943 -35.542,-35.54 0,-19.599 15.944,-35.543 35.542,-35.543 19.598,0 35.541,15.944 35.541,35.543 0,19.597 -15.943,35.54 -35.541,35.54 z"
+ id="path330" />
+ <path
+ d="m 112.274,92.91 c -10.702,0 -19.372,8.669 -19.372,19.364 0,10.694 8.67,19.363 19.372,19.363 10.703,0 19.373,-8.669 19.373,-19.363 0,-10.695 -8.67,-19.364 -19.373,-19.364 z"
+ id="path332" />
+</g>
</svg>