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:
parent
4482604a36
commit
9a1532715a
|
|
@ -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')}>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue