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>
|
||||||
|
|
||||||
<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')}>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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} />
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue