aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2022-10-21 21:18:10 -0700
committerAdam Soltys <[email protected]>2022-10-21 21:18:10 -0700
commita67f9cbc9f609aa56e445fe8f422ba3be14e9345 (patch)
treef584c51fadf79169278e51ba9a888b980ac4ac29
parenta005ef51ee46618a1875dee4559ccddfec41dfcd (diff)
downloadcoinos-ui-a67f9cbc9f609aa56e445fe8f422ba3be14e9345.tar.gz
coinos-ui-a67f9cbc9f609aa56e445fe8f422ba3be14e9345.zip
pin code input
-rw-r--r--src/components/Qr.svelte4
-rw-r--r--src/lib/upload.js1
-rw-r--r--src/lib/utils.js1
-rw-r--r--src/routes/[username]/settings/+page.server.js4
-rw-r--r--src/routes/[username]/settings/+page.svelte28
-rw-r--r--src/routes/[username]/settings/_account.svelte18
-rw-r--r--src/routes/[username]/settings/_security.svelte104
7 files changed, 125 insertions, 35 deletions
diff --git a/src/components/Qr.svelte b/src/components/Qr.svelte
index 0ae028b9..3870abe0 100644
--- a/src/components/Qr.svelte
+++ b/src/components/Qr.svelte
@@ -57,8 +57,8 @@
{#if mask}
<div class="absolute bg-white w-[300px] h-[300px] t-0 l-0 z-10" out:fade />
{/if}
-<div bind:this={qr} on:click={toggle} class:hidden={full} class="relative overflow-visible" />
-<div bind:this={fullQr} on:click={toggle} class:hidden={!full} />
+<div bind:this={qr} on:click={toggle} on:keydown={toggle} class:hidden={full} class="relative overflow-visible" />
+<div bind:this={fullQr} on:click={toggle} on:keydown={toggle} class:hidden={!full} />
<style>
div {
diff --git a/src/lib/upload.js b/src/lib/upload.js
index 0e8a2908..099e2f30 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('FILE', file);
let url = `/api/upload/${type}`;
let formData = new FormData();
formData.append('file', file);
diff --git a/src/lib/utils.js b/src/lib/utils.js
index 7cb09c61..d8a7b60c 100644
--- a/src/lib/utils.js
+++ b/src/lib/utils.js
@@ -36,7 +36,6 @@ export const post = (url, body, headers) => {
return fetch(base + url, { method: 'POST', body: JSON.stringify(body), headers })
.then((r) => r.text())
.then((body) => {
- console.log('BODY', body);
try {
body = JSON.parse(body);
} catch (e) {
diff --git a/src/routes/[username]/settings/+page.server.js b/src/routes/[username]/settings/+page.server.js
index 7053a939..deb4356e 100644
--- a/src/routes/[username]/settings/+page.server.js
+++ b/src/routes/[username]/settings/+page.server.js
@@ -3,9 +3,11 @@ import { auth, get, post } from '$lib/utils';
export const actions = {
default: async ({ cookies, request }) => {
+ let form = Object.fromEntries(await request.formData())
+
let user = {
...(await get('/me', auth(cookies))),
- ...Object.fromEntries(await request.formData())
+ ...form
};
try {
diff --git a/src/routes/[username]/settings/+page.svelte b/src/routes/[username]/settings/+page.svelte
index c1ac20d0..a584fc56 100644
--- a/src/routes/[username]/settings/+page.svelte
+++ b/src/routes/[username]/settings/+page.svelte
@@ -4,7 +4,7 @@
import Pos from './_pos.svelte';
import Security from './_security.svelte';
- import { Icon } from '$comp';
+ import { Icon, Spinner } from '$comp';
import { t } from '$lib/translations';
import { failure, success } from '$lib/utils';
import { avatarUpload, bannerUpload } from '$lib/store';
@@ -26,19 +26,29 @@
$: ({ comp } = tabs.find((t) => t.name === tab));
- let save = async () => {
+ let loading, submit;
+ let save = async (msg) => {
+ loading = true;
+ if (typeof msg !== 'string') msg = 'Settings saved';
+
+ console.log("SAVING ONCE")
+
try {
if ($avatarUpload) {
await upload($avatarUpload.file, $avatarUpload.type, $avatarUpload.progress, token);
}
+
if ($bannerUpload) {
await upload($bannerUpload.file, $bannerUpload.type, $bannerUpload.progress, token);
}
- success('Settings saved');
+
+ success(msg);
} catch (e) {
console.log(e);
failure('Something went wrong');
}
+
+ loading = false;
};
</script>
@@ -58,16 +68,22 @@
{/each}
</div>
- <svelte:component this={comp} {user} {rates} />
+ <svelte:component this={comp} {user} {rates} {submit} />
</div>
<div
class="z-10 fixed bottom-0 bg-white shadow border-t w-full flex justify-center items-center py-3"
>
<button
+ bind:this={submit}
type="submit"
- class="bg-black text-white rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80"
+ class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80"
+ class:bg-black={loading}
>
- <div class="my-auto">Save Settings</div>
+ {#if loading}
+ <Spinner />
+ {:else}
+ <div class="my-auto">Save Settings</div>
+ {/if}
</button>
</div>
</form>
diff --git a/src/routes/[username]/settings/_account.svelte b/src/routes/[username]/settings/_account.svelte
index 10756c7a..d7ab9f5e 100644
--- a/src/routes/[username]/settings/_account.svelte
+++ b/src/routes/[username]/settings/_account.svelte
@@ -17,6 +17,7 @@
$: banner = files.banner.src;
$: $tempProfileFiles = { profile: files.profile.src, banner: files.banner.src };
+ let togglePassword = () => (revealPassword = !revealPassword);
let colorThemes = [
{ theme: 1, color1: 'from-[#F5F7FA]', color2: 'to-[#C3CFE2]' },
{ theme: 2, color1: 'from-[#FDFCFB]', color2: 'to-[#E2D1C3]' },
@@ -88,7 +89,8 @@
{/if}
<button
type="button"
- on:click={() => (revealPassword = !revealPassword)}
+ on:click={togglePassword}
+ on:keydown={togglePassword}
class="absolute right-5 top-11"
>
<Icon icon={revealPassword ? 'eye' : 'eye-off'} />
@@ -129,6 +131,7 @@
<div
class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer"
on:click={() => select('profile')}
+ on:keydown={() => select('profile')}
>
<img
src={profile}
@@ -140,6 +143,7 @@
<div
class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer"
on:click={() => select('profile')}
+ on:keydown={() => select('profile')}
>
<img
src={`/api/public/${user.username}-profile.webp`}
@@ -151,6 +155,7 @@
<div
class="rounded-full border-4 border-white p-4 bg-gradient-to-r {$colorTheme} w-24 my-auto hover:opacity-80 cursor-pointer"
on:click={() => select('profile')}
+ on:keydown={() => select('profile')}
>
<Icon icon="logo-symbol-white" style="mx-auto" />
</div>
@@ -160,7 +165,9 @@
<button
type="button"
class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80"
- on:click={() => select('profile')}>Select</button
+ on:click={() => select('profile')}
+ on:keydown={() => select('profile')}
+ >Select</button
>
<input
type="file"
@@ -190,6 +197,7 @@
src={banner}
class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80"
on:click={() => select('banner')}
+ on:keydown={() => select('banner')}
alt="Banner"
/>
{:else if user.banner}
@@ -197,12 +205,14 @@
src={`/api/public/${user.username}-banner.webp`}
class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80"
on:click={() => select('banner')}
+ on:keydown={() => select('banner')}
alt="Banner"
/>
{:else}
<div
class="bg-gradient-to-r {$colorTheme} w-full h-48 mb-4 cursor-pointer hover:opacity-80"
on:click={() => select('banner')}
+ on:keydown={() => select('banner')}
alt="Banner"
/>
{/if}
@@ -211,7 +221,8 @@
<button
type="button"
class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80"
- on:click={() => select('banner')}>Select</button
+ on:click={() => select('banner')}
+ on:keydown={() => select('banner')}>Select</button
>
<input
type="file"
@@ -240,6 +251,7 @@
? 'border-black'
: ''}"
on:click={() => handleThemeClick(colors)}
+ on:keydown={() => handleThemeClick(colors)}
/>
{/each}
</div> -->
diff --git a/src/routes/[username]/settings/_security.svelte b/src/routes/[username]/settings/_security.svelte
index 2a490aa4..a74517aa 100644
--- a/src/routes/[username]/settings/_security.svelte
+++ b/src/routes/[username]/settings/_security.svelte
@@ -5,25 +5,75 @@
import { Pincode, PincodeInput } from 'svelte-pincode';
import { post, success, failure } from '$lib/utils';
- export let user;
+ export let user, submit;
let settingPin, setting2fa, disabling2fa;
- let code = [];
- let pin = '';
let token = '';
- let tokenInput, pinInput;
+ let pinCode = [];
+ let verifyCode = [];
+ let tokenInput, pinInput, verifyInput;
+ let verifying = false;
+ let startVerifying = () => {
+ verifying = true;
+ };
+
+ $: pin = pinCode.join('');
+
+ let locked;
+ $: verifyCode.join('').length > 5 && checkPin();
+ let checkPin = async () => {
+ if (locked) return;
+ locked = true;
+
+ let verify = verifyCode.join('');
+
+ pinCode = ['', '', '', '', '', ''];
+ verifyCode = ['', '', '', '', '', ''];
+ settingPin = false;
+
+ try {
+ if (pin.length > 5 && pin === verify) {
+ user.pin = pin;
+ submit.click();
+ } else {
+ failure('Pin mismatch, try again');
+ settingPin = true;
+ pinInput.focusFirstInput();
+ }
+ } catch (e) {
+ failure('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);
+ $: if (setting2fa || disabling2fa) tokenInput && tick().then(tokenInput?.focusFirstInput);
let reset = () => {
token = '';
- code = [];
return true;
};
- let togglePin = () => (settingPin = !settingPin);
+ let togglePin = async () => {
+ if (user.pin) {
+ try {
+ user.pin = null;
+ submit.click();
+ } catch (e) {
+ failure('Failed to disable pin');
+ }
+ } else {
+ settingPin = true;
+ }
+ };
+
let toggleEnabling = () => reset() && (setting2fa = !setting2fa);
let toggleDisabling = () => reset() && (disabling2fa = !disabling2fa);
@@ -58,31 +108,43 @@
$: otpUri = `otpauth://totp/coinos:${user.username}?secret=${user.otpsecret}&period=30&digits=6&algorithm=SHA1&issuer=coinos`;
</script>
-<!-- TODO
<div>
- <span class="font-bold mb-1">{$t('user.settings.securityPIN')}</span>
+ <input type="hidden" name="pin" value={pin} />
+ <span class="font-bold mb-1">{verifying ? 'Verify' : 'Security'} PIN</span>
<p class="text-secondary mb-1">
{$t('user.settings.securityPINDescription')}
</p>
{#if settingPin}
<div class="flex my-4">
<div class="mx-auto">
- <Pincode bind:code bind:value={user.pin} bind:this={pinInput}>
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- <PincodeInput />
- </Pincode>
+ <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>
{:else}
<button type="button" class="primary" on:click={togglePin}
- ><Icon icon="lock" style="mr-1" /> {$t('user.settings.setPIN')}</button
+ ><Icon icon="lock" style="mr-1" /> {user.pin ? 'Disable Pin' : 'Enable Pin'}</button
>
{/if}
-</div> -->
+</div>
<!-- TODO
<div>
@@ -116,7 +178,7 @@
<div class="text-center my-4">
{$t('user.settings.oneTimeCode')}<br />
- <Pincode bind:code bind:value={token} bind:this={tokenInput}>
+ <Pincode bind:value={token} bind:this={tokenInput}>
<PincodeInput />
<PincodeInput />
<PincodeInput />
@@ -128,7 +190,7 @@
{:else if disabling2fa}
<div class="text-center my-4">
{$t('user.settings.oneTimeCode')}<br />
- <Pincode bind:code bind:value={token} bind:this={tokenInput}>
+ <Pincode bind:value={token} bind:this={tokenInput}>
<PincodeInput />
<PincodeInput />
<PincodeInput />