diff options
| author | Adam Soltys <[email protected]> | 2022-10-21 21:18:10 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2022-10-21 21:18:10 -0700 |
| commit | a67f9cbc9f609aa56e445fe8f422ba3be14e9345 (patch) | |
| tree | f584c51fadf79169278e51ba9a888b980ac4ac29 | |
| parent | a005ef51ee46618a1875dee4559ccddfec41dfcd (diff) | |
| download | coinos-ui-a67f9cbc9f609aa56e445fe8f422ba3be14e9345.tar.gz coinos-ui-a67f9cbc9f609aa56e445fe8f422ba3be14e9345.zip | |
pin code input
| -rw-r--r-- | src/components/Qr.svelte | 4 | ||||
| -rw-r--r-- | src/lib/upload.js | 1 | ||||
| -rw-r--r-- | src/lib/utils.js | 1 | ||||
| -rw-r--r-- | src/routes/[username]/settings/+page.server.js | 4 | ||||
| -rw-r--r-- | src/routes/[username]/settings/+page.svelte | 28 | ||||
| -rw-r--r-- | src/routes/[username]/settings/_account.svelte | 18 | ||||
| -rw-r--r-- | src/routes/[username]/settings/_security.svelte | 104 |
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 /> |
