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

View File

@ -2,11 +2,12 @@
import clipboardCopy from 'clipboard-copy'
import QRCode from 'qrcode-svg'
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 { 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'
let view: 'backup-device' | 'delegate-account' = 'backup-device'
@ -14,14 +15,29 @@
let connectionLink = null
let qrcode = null
let fs = null
let backupCreated = true
let pin: number[]
let pinInput = ''
let pinError = false
let confirmPin = () => {}
let rejectPin = () => {}
let unsubscribeFilesystemStore = () => {}
let unsubscribeSessionStore = () => {}
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
if (username) {
@ -57,15 +73,28 @@
backupCreated: true
}))
const fs = $filesystemStore
await setBackupStatus(fs, { created: true })
if (fs) {
await setBackupStatus(fs, { created: true })
goto('/')
// Send up a toast on '/'
addNotification("You've connected a backup device!", 'success')
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 () => {
await clipboardCopy(connectionLink)
}
@ -78,11 +107,10 @@
}
}
const refuseConnection = () => {
rejectPin()
view = 'backup-device'
}
onDestroy(() => {
unsubscribeFilesystemStore()
unsubscribeSessionStore()
})
</script>
{#if view === 'backup-device'}
@ -106,12 +134,14 @@
<ClipboardIcon />
<span class="ml-2">Copy connection link</span>
</button>
<button
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>
{#if !backupCreated}
<button
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>
{/if}
</div>
</div>
</div>
@ -134,17 +164,17 @@
<input
id="pin"
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}
/>
<label for="pin" class="label">
{#if !pinError}
<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>
{:else}
<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>
{/if}
</label>
@ -154,10 +184,10 @@
Approve the connection
</button>
<button
class="btn btn-error btn-outline w-full"
on:click={refuseConnection}
class="btn btn-primary btn-outline w-full"
on:click={cancelConnection}
>
Refuse the connection
Cancel Request
</button>
</div>
</div>

View File

@ -1,10 +1,14 @@
<script lang="ts">
import type { account } from 'webnative'
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { addNotification } from '$lib/notifications'
import { createAccountLinkingConsumer } from '$lib/auth/linking'
import { loadAccount } from '$lib/common/webnative'
let accountLinkingConsumer: account.AccountLinkingConsumer
let loadingFilesystem = false
let displayPin: string = ''
@ -16,7 +20,7 @@
history.replaceState(null, document.title, url.toString())
const initAccountLinkingConsumer = async () => {
const accountLinkingConsumer = await createAccountLinkingConsumer(username)
accountLinkingConsumer = await createAccountLinkingConsumer(username)
accountLinkingConsumer.on('challenge', ({ pin }) => {
displayPin = pin.join('')
@ -27,12 +31,23 @@
loadingFilesystem = true
await loadAccount(username)
addNotification("You're now connected!", 'success')
goto('/')
} else {
addNotification('The connection attempt was cancelled', 'info')
goto('/')
// Send up a toast on '/'
}
})
}
const cancelConnection = async () => {
addNotification('The connection attempt was cancelled', 'info')
await accountLinkingConsumer?.cancel()
goto('/')
}
initAccountLinkingConsumer()
</script>
@ -62,7 +77,7 @@
>
{#if displayPin}
<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}
</span>
@ -78,7 +93,10 @@
</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
</button>
</div>

View File

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

View File

@ -5,7 +5,7 @@
</script>
{#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)}
<div animate:flip>
<Notification {notification} />

View File

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