Add remaining auth flow details (#43)

* Copy edits

* Support for info and warning notifications

* Account linking success toasts

* Cancel account linking

* Hide skip for now when backup exists


Co-authored-by: Jess Martin <jessmartin@gmail.com>
This commit is contained in:
Brian Ginsburg 2022-08-30 15:24:55 -07:00 committed by GitHub
parent 4482604a36
commit 9a1532715a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 43 deletions

View File

@ -24,8 +24,8 @@
</p> </p>
<p class="mt-8 mb-4"> <p class="mt-8 mb-4">
We highly recommend backing up your account on at least one additional We highly recommend connecting your account on at least one more device,
device. so that you have a backup.
</p> </p>
<button class="btn btn-primary" on:click={() => goto('delegate-account')}> <button class="btn btn-primary" on:click={() => goto('delegate-account')}>

View File

@ -2,11 +2,12 @@
import clipboardCopy from 'clipboard-copy' import clipboardCopy from 'clipboard-copy'
import QRCode from 'qrcode-svg' import QRCode from 'qrcode-svg'
import { goto } from '$app/navigation' import { goto } from '$app/navigation'
import { onMount } from 'svelte' import { onDestroy, onMount } from 'svelte'
import { addNotification } from '$lib/notifications'
import { createAccountLinkingProducer } from '$lib/auth/linking' import { createAccountLinkingProducer } from '$lib/auth/linking'
import { filesystemStore, sessionStore, theme } from '../../../stores' import { filesystemStore, sessionStore, theme } from '../../../stores'
import { setBackupStatus } from '$lib/auth/backup' import { getBackupStatus, setBackupStatus } from '$lib/auth/backup'
import ClipboardIcon from '$components/icons/ClipboardIcon.svelte' import ClipboardIcon from '$components/icons/ClipboardIcon.svelte'
let view: 'backup-device' | 'delegate-account' = 'backup-device' let view: 'backup-device' | 'delegate-account' = 'backup-device'
@ -14,14 +15,29 @@
let connectionLink = null let connectionLink = null
let qrcode = null let qrcode = null
let fs = null
let backupCreated = true
let pin: number[] let pin: number[]
let pinInput = '' let pinInput = ''
let pinError = false let pinError = false
let confirmPin = () => {} let confirmPin = () => {}
let rejectPin = () => {} let rejectPin = () => {}
let unsubscribeFilesystemStore = () => {}
let unsubscribeSessionStore = () => {}
onMount(() => { onMount(() => {
sessionStore.subscribe(val => { unsubscribeFilesystemStore = filesystemStore.subscribe(async val => {
fs = val
if (fs) {
const backupStatus = await getBackupStatus(fs)
backupCreated = backupStatus.created
}
})
unsubscribeSessionStore = sessionStore.subscribe(async val => {
const username = val.username const username = val.username
if (username) { if (username) {
@ -57,15 +73,28 @@
backupCreated: true backupCreated: true
})) }))
const fs = $filesystemStore if (fs) {
await setBackupStatus(fs, { created: true }) await setBackupStatus(fs, { created: true })
goto('/') addNotification("You've connected a backup device!", 'success')
// Send up a toast on '/' goto('/')
} else {
addNotification(
'Missing filesystem. Unable to create a backup device.',
'error'
)
}
} }
}) })
} }
const cancelConnection = () => {
rejectPin()
addNotification('The connection attempt was cancelled', 'info')
goto('/')
}
const copyLink = async () => { const copyLink = async () => {
await clipboardCopy(connectionLink) await clipboardCopy(connectionLink)
} }
@ -78,11 +107,10 @@
} }
} }
const refuseConnection = () => { onDestroy(() => {
rejectPin() unsubscribeFilesystemStore()
unsubscribeSessionStore()
view = 'backup-device' })
}
</script> </script>
{#if view === 'backup-device'} {#if view === 'backup-device'}
@ -106,12 +134,14 @@
<ClipboardIcon /> <ClipboardIcon />
<span class="ml-2">Copy connection link</span> <span class="ml-2">Copy connection link</span>
</button> </button>
<button {#if !backupCreated}
class="btn btn-xs btn-link text-base text-error font-normal underline mt-4" <button
on:click={() => goto('/backup?view=are-you-sure')} class="btn btn-xs btn-link text-base text-error font-normal underline mt-4"
> on:click={() => goto('/backup?view=are-you-sure')}
Skip for now >
</button> Skip for now
</button>
{/if}
</div> </div>
</div> </div>
</div> </div>
@ -134,17 +164,17 @@
<input <input
id="pin" id="pin"
type="text" type="text"
class="input input-bordered w-full max-w-xs mb-2 font-mono text-3xl text-center tracking-[0.18em] font-light" class="input input-bordered w-full max-w-xs mb-2 rounded-full h-16 font-mono text-3xl text-center tracking-[0.18em] font-light dark:border-slate-300"
bind:value={pinInput} bind:value={pinInput}
/> />
<label for="pin" class="label"> <label for="pin" class="label">
{#if !pinError} {#if !pinError}
<span class="label-text-alt text-slate-500"> <span class="label-text-alt text-slate-500">
Enter the connection code to approve the connection Enter the connection code to approve the connection.
</span> </span>
{:else} {:else}
<span class="label-text-alt text-error"> <span class="label-text-alt text-error">
Entered pin does not match a pin from a known device Entered pin does not match a pin from a known device.
</span> </span>
{/if} {/if}
</label> </label>
@ -154,10 +184,10 @@
Approve the connection Approve the connection
</button> </button>
<button <button
class="btn btn-error btn-outline w-full" class="btn btn-primary btn-outline w-full"
on:click={refuseConnection} on:click={cancelConnection}
> >
Refuse the connection Cancel Request
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,10 +1,14 @@
<script lang="ts"> <script lang="ts">
import type { account } from 'webnative'
import { goto } from '$app/navigation' import { goto } from '$app/navigation'
import { page } from '$app/stores' import { page } from '$app/stores'
import { addNotification } from '$lib/notifications'
import { createAccountLinkingConsumer } from '$lib/auth/linking' import { createAccountLinkingConsumer } from '$lib/auth/linking'
import { loadAccount } from '$lib/common/webnative' import { loadAccount } from '$lib/common/webnative'
let accountLinkingConsumer: account.AccountLinkingConsumer
let loadingFilesystem = false let loadingFilesystem = false
let displayPin: string = '' let displayPin: string = ''
@ -16,7 +20,7 @@
history.replaceState(null, document.title, url.toString()) history.replaceState(null, document.title, url.toString())
const initAccountLinkingConsumer = async () => { const initAccountLinkingConsumer = async () => {
const accountLinkingConsumer = await createAccountLinkingConsumer(username) accountLinkingConsumer = await createAccountLinkingConsumer(username)
accountLinkingConsumer.on('challenge', ({ pin }) => { accountLinkingConsumer.on('challenge', ({ pin }) => {
displayPin = pin.join('') displayPin = pin.join('')
@ -27,12 +31,23 @@
loadingFilesystem = true loadingFilesystem = true
await loadAccount(username) await loadAccount(username)
addNotification("You're now connected!", 'success')
goto('/')
} else {
addNotification('The connection attempt was cancelled', 'info')
goto('/') goto('/')
// Send up a toast on '/'
} }
}) })
} }
const cancelConnection = async () => {
addNotification('The connection attempt was cancelled', 'info')
await accountLinkingConsumer?.cancel()
goto('/')
}
initAccountLinkingConsumer() initAccountLinkingConsumer()
</script> </script>
@ -62,7 +77,7 @@
> >
{#if displayPin} {#if displayPin}
<span <span
class="btn bg-blue-900 btn-lg rounded-full text-3xl tracking-[.18em] font-normal w-3/4 cursor-default font-mono font-light" class="btn bg-blue-100 dark:bg-blue-900 hover:bg-blue-100 dark:hover:bg-blue-900 border-0 btn-lg rounded-full text-3xl tracking-[.18em] w-3/4 cursor-default font-mono font-light"
> >
{displayPin} {displayPin}
</span> </span>
@ -78,7 +93,10 @@
</div> </div>
</div> </div>
<div> <div>
<button class="btn btn-primary btn-outline text-base font-normal"> <button
class="btn btn-primary btn-outline text-base font-normal mt-4"
on:click={cancelConnection}
>
Cancel Request Cancel Request
</button> </button>
</div> </div>

View File

@ -1,13 +1,10 @@
<script lang="ts"> <script lang="ts">
import { fade, fly } from 'svelte/transition' import { fade, fly } from 'svelte/transition'
import CheckThinIcon from '$components/icons/CheckThinIcon.svelte' import CheckThinIcon from '$components/icons/CheckThinIcon.svelte'
import XThinIcon from '$components/icons/XThinIcon.svelte' import XThinIcon from '$components/icons/XThinIcon.svelte'
import { theme as themeStore } from '../../stores' import { theme as themeStore } from '../../stores'
import type { Notification } from '$lib/notifications'
interface Notification {
msg?: string
type?: string
}
export let notification: Notification export let notification: Notification
</script> </script>
@ -19,11 +16,7 @@
aria-live="assertive" aria-live="assertive"
aria-atomic="true" aria-atomic="true"
> >
<div <div class="alert alert-{notification.type} text-sm mb-3 peer-last:mb-0">
class="alert {notification.type === 'success'
? 'alert-success'
: 'alert-error'} text-sm mb-3"
>
<div> <div>
{#if notification.type === 'success'} {#if notification.type === 'success'}
<CheckThinIcon <CheckThinIcon

View File

@ -5,7 +5,7 @@
</script> </script>
{#if $notificationStore.length} {#if $notificationStore.length}
<div class="fixed z-50 right-6 bottom-8 flex flex-col justify-center"> <div class="fixed z-50 right-6 bottom-6 flex flex-col justify-center">
{#each $notificationStore as notification (notification.id)} {#each $notificationStore as notification (notification.id)}
<div animate:flip> <div animate:flip>
<Notification {notification} /> <Notification {notification} />

View File

@ -4,10 +4,12 @@ import { uuid } from '$lib/common/utils'
export type Notification = { export type Notification = {
id?: string id?: string
msg?: string msg?: string
type?: string type?: NotificationType
timeout?: number timeout?: number
} }
type NotificationType = 'success' | 'error' | 'info' | 'warning'
export const removeNotification: (id: string) => void = id => { export const removeNotification: (id: string) => void = id => {
notificationStore.update(all => notificationStore.update(all =>
all.filter(notification => notification.id !== id) all.filter(notification => notification.id !== id)
@ -16,7 +18,7 @@ export const removeNotification: (id: string) => void = id => {
export const addNotification: ( export const addNotification: (
msg: string, msg: string,
type?: string, type?: NotificationType,
timeout?: number timeout?: number
) => void = (msg, type = 'info', timeout = 5000) => { ) => void = (msg, type = 'info', timeout = 5000) => {
// uuid for each notification // uuid for each notification