Welcome and backup device flow (#26)

* Comment out redirect to prevent re-register

* Welcome screen completed

* Rename LinkDevice to Welcome; Add Backup route

* Add a new page for Backup Device

* Move bootstrapFilesystem to webnative implementation

Co-authored-by: Jess Martin <jessmartin@gmail.com>

* Rename appName.ts to app-name.ts

Co-authored-by: Jess Martin <jessmartin@gmail.com>

* Refactor register and backup routes and components

Co-authored-by: Jess Martin <jessmartin@gmail.com>

Co-authored-by: Brian Ginsburg <gins@brianginsburg.com>
This commit is contained in:
Jess Martin 2022-08-04 10:59:50 -04:00 committed by GitHub
parent 1aa4463812
commit 143ec66d1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 233 additions and 66 deletions

View File

@ -56,5 +56,5 @@ See the [Fission Guide](https://guide.fission.codes/developers/installation) and
## Customize
- `app.html` - the SEO meta tags will need to be changed.
- `lib/appName.ts` - choose a better application name
- `lib/app-name.ts` - choose a better application name
- To customize the application's Tailwind theme, change `tailwind.config.ts` - link to DaisyUI customization page.

View File

@ -1 +0,0 @@
<h1>Link Device</h1>

View File

@ -0,0 +1,34 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import type { BackupView } from '$lib/views'
const dispatch = createEventDispatcher()
const navigate = (view: BackupView) => {
dispatch('navigate', { view })
}
</script>
<input type="checkbox" id="are-you-sure-modal" checked class="modal-toggle" />
<div class="modal">
<div class="modal-box w-80 relative text-center">
<div>
<h3 class="mb-7 text-xl font-serif">Are you sure?</h3>
<p class="mt-8 mb-6">
Without a backup device, if you lose this device or reset your browser,
you will not be able to recover your account data.
</p>
<button
class="btn btn-primary"
on:click={() => navigate('backup-device')}
>
Connect a backup device
</button>
<a class="text-error underline block mt-4" href="/">
YOLO&mdash;I'll risk just one device for now
</a>
</div>
</div>
</div>

View File

@ -0,0 +1,42 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { appName } from '$lib/app-name'
import type { BackupView } from '$lib/views'
const dispatch = createEventDispatcher()
const navigate = (view: BackupView) => {
dispatch('navigate', { view })
}
</script>
<input type="checkbox" id="backup-modal" checked class="modal-toggle" />
<div class="modal">
<div class="modal-box w-80 relative text-center">
<div id="backup-message" class="peer-checked:hidden">
<h3 class="mb-7 text-xl font-serif">Backup your account</h3>
<p class="mt-8 mb-4">
Your {appName} account & its data live only on your devices.
</p>
<p class="mt-8 mb-4">
We highly recommend backing up your account on at least one additional
device.
</p>
<button
class="btn btn-primary"
on:click={() => navigate('backup-device')}
>
Connect a backup device
</button>
<button
class="btn btn-xs btn-link text-base font-normal underline mt-4"
on:click={() => navigate('are-you-sure')}
>
Skip for now
</button>
</div>
</div>
</div>

View File

@ -0,0 +1,39 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import type { BackupView } from '$lib/views'
import ClipboardIcon from '$components/icons/ClipboardIcon.svelte'
const dispatch = createEventDispatcher()
const navigate = (view: BackupView) => {
dispatch('navigate', { view })
}
</script>
<input type="checkbox" id="backup-device-modal" checked class="modal-toggle" />
<div class="modal">
<div class="modal-box w-80 relative text-center">
<div>
<h3 class="mb-7 text-xl font-serif">Connect a backup device</h3>
<!-- GIANT QR CODE GOES HERE -->
QR Codes
<p class="mt-8 mb-4">
Scan this code on the new device, or share the connection link.
</p>
<button class="btn btn-primary btn-outline" href="/backup">
<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={() => navigate('are-you-sure')}
>
Skip for now
</button>
</div>
</div>
</div>

View File

@ -1,22 +1,5 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { onDestroy, onMount } from 'svelte'
import { appName } from '$lib/appName'
import { sessionStore } from '../../stores'
import type { Session } from '$lib/session'
let unsubscribeSessionStore: () => void = () => {}
onMount(() => {
unsubscribeSessionStore = sessionStore.subscribe((session: Session) => {
if (session.authed) {
goto('/')
}
})
})
onDestroy(unsubscribeSessionStore)
import { appName } from '$lib/app-name'
</script>
<input type="checkbox" id="my-modal-5" checked class="modal-toggle" />

View File

View File

@ -1,16 +1,10 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { onDestroy, onMount } from 'svelte'
import { appName } from '$lib/appName'
import { appName } from '$lib/app-name'
import {
bootstrapFilesystem,
isUsernameValid,
isUsernameAvailable,
register
} from '$lib/common/webnative'
import { filesystemStore, sessionStore } from '../../stores'
import type { Session } from '$lib/session'
import CheckIcon from '$components/icons/CheckIcon.svelte'
import XIcon from '$components/icons/XIcon.svelte'
@ -20,20 +14,11 @@
let registrationSuccess = true
let checkingUsername = false
let unsubscribeSessionStore: () => void = () => {}
onMount(() => {
unsubscribeSessionStore = sessionStore.subscribe((session: Session) => {
if (session.authed) {
goto('/')
}
})
})
const checkUsername = async (event: Event) => {
checkingUsername = true
const { value } = event.target as HTMLInputElement
username = value
checkingUsername = true
usernameValid = await isUsernameValid(username)
@ -46,27 +31,10 @@
const registerUser = async () => {
registrationSuccess = await register(username)
if (registrationSuccess) {
sessionStore.update(session => ({
...session,
username,
authed: true
}))
console.log('session after registration', $sessionStore)
const fs = await bootstrapFilesystem()
filesystemStore.set(fs)
goto('/linkDevice')
}
}
onDestroy(unsubscribeSessionStore)
</script>
<input type="checkbox" id="my-modal-5" checked class="modal-toggle" />
<input type="checkbox" id="register-modal" checked class="modal-toggle" />
<div class="modal">
<div class="modal-box w-80 relative text-center">
<a href="/" class="btn btn-xs btn-circle absolute right-2 top-2"></a>

View File

@ -0,0 +1,41 @@
<script lang="ts">
import { sessionStore } from '../../../stores'
import WelcomeCheckIcon from '$components/icons/WelcomeCheckIcon.svelte'
import { appName } from '$lib/app-name'
</script>
<input type="checkbox" id="link-device-modal" checked class="modal-toggle" />
<div class="modal">
<div class="modal-box w-80 relative text-center">
<div>
<h3 class="mb-7 text-xl font-serif">
Welcome, {$sessionStore.username}!
</h3>
<div class="flex justify-center">
<span>
<WelcomeCheckIcon />
</span>
</div>
<div>
<p class="mt-8 mb-4">Your account has been created.</p>
<div class="mb-8">
<input type="checkbox" id="password-message" class="peer hidden" />
<label
class="text-primary underline mb-8 hover:cursor-pointer peer-checked:hidden"
for="password-message"
>
Wait&mdash;what's my password?
</label>
<p class="hidden peer-checked:block">
You don't need a password! <br />
{appName} uses public key cryptography to authenticate you with this
device.
</p>
</div>
<a class="btn btn-primary" href="/backup">Continue</a>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,15 @@
<svg
width="19"
height="21"
viewBox="0 0 19 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5.5 3.5H3.5C2.39543 3.5 1.5 4.39543 1.5 5.5V17.5C1.5 18.6046 2.39543 19.5 3.5 19.5H13.5C14.6046 19.5 15.5 18.6046 15.5 17.5V16.5M5.5 3.5C5.5 4.60457 6.39543 5.5 7.5 5.5H9.5C10.6046 5.5 11.5 4.60457 11.5 3.5M5.5 3.5C5.5 2.39543 6.39543 1.5 7.5 1.5H9.5C10.6046 1.5 11.5 2.39543 11.5 3.5M11.5 3.5H13.5C14.6046 3.5 15.5 4.39543 15.5 5.5V8.5M17.5 12.5H7.5M7.5 12.5L10.5 9.5M7.5 12.5L10.5 15.5"
stroke="#3B82F6"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>

After

Width:  |  Height:  |  Size: 621 B

View File

@ -0,0 +1,14 @@
<svg
width="100"
height="101"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M26.8559 9.92063C30.8429 9.60246 34.6279 8.03465 37.6722 5.44038C44.7761 -0.613591 55.2246 -0.613591 62.3286 5.44038C65.3728 8.03465 69.1579 9.60246 73.1449 9.92063C82.449 10.6631 89.8372 18.0513 90.5797 27.3554C90.8978 31.3424 92.4656 35.1274 95.0599 38.1717C101.114 45.2757 101.114 55.7241 95.0599 62.8281C92.4656 65.8724 90.8978 69.6574 90.5797 73.6444C89.8372 82.9485 82.449 90.3367 73.1449 91.0792C69.1579 91.3973 65.3728 92.9652 62.3286 95.5594C55.2246 101.613 44.7761 101.613 37.6722 95.5594C34.6279 92.9652 30.8429 91.3973 26.8559 91.0792C17.5518 90.3367 10.1636 82.9485 9.42112 73.6444C9.10295 69.6574 7.53514 65.8724 4.94087 62.8281C-1.1131 55.7241 -1.1131 45.2757 4.94087 38.1717C7.53514 35.1274 9.10295 31.3424 9.42112 27.3554C10.1636 18.0513 17.5518 10.6631 26.8559 9.92063ZM72.9845 42.484C75.4057 40.0627 75.4057 36.1371 72.9845 33.7158C70.5632 31.2946 66.6376 31.2946 64.2163 33.7158L43.8004 54.1318L35.7845 46.1158C33.3632 43.6946 29.4376 43.6946 27.0163 46.1158C24.5951 48.5371 24.5951 52.4627 27.0163 54.884L39.4163 67.284C41.8376 69.7052 45.7632 69.7052 48.1844 67.284L72.9845 42.484Z"
fill="#6EE7B7"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -77,6 +77,15 @@ export const isUsernameAvailable = async (
export const register = async (username: string): Promise<boolean> => {
const { success } = await webnative.account.register({ username })
const fs = await bootstrapFilesystem()
filesystemStore.set(fs)
sessionStore.update(session => ({
...session,
username,
authed: true
}))
return success
}

View File

@ -1,4 +1,4 @@
import { appName } from '$lib/appName'
import { appName } from '$lib/app-name'
export type Session = {
username: string

1
src/lib/views.ts Normal file
View File

@ -0,0 +1 @@
export type BackupView = 'backup' | 'backup-device' | 'are-you-sure'

View File

@ -2,7 +2,7 @@
import { onMount } from 'svelte'
import '../global.css'
import { appName } from '$lib/appName'
import { appName } from '$lib/app-name'
import { initialize } from '$lib/common/webnative'
import { deviceStore, sessionStore, theme } from '../stores'
import { storeTheme } from '$lib/theme'

20
src/routes/backup.svelte Normal file
View File

@ -0,0 +1,20 @@
<script lang="ts">
import type { BackupView } from '$lib/views'
import Backup from '$components/auth/backup/Backup.svelte'
import BackupDevice from '$components/auth/backup/BackupDevice.svelte'
import AreYouSure from '$components/auth/backup/AreYouSure.svelte'
let view: BackupView = 'backup'
const navigate = (event: CustomEvent<{ view: BackupView }>) => {
view = event.detail.view
}
</script>
{#if view === 'backup'}
<Backup on:navigate={navigate} />
{:else if view === 'backup-device'}
<BackupDevice on:navigate={navigate} />
{:else if view === 'are-you-sure'}
<AreYouSure on:navigate={navigate} />
{/if}

View File

@ -1,5 +1,5 @@
<script lang="ts">
import Connect from '$components/auth/Connect.svelte'
import Connect from '$components/auth/connect/Connect.svelte'
</script>
<Connect />

View File

@ -1,5 +0,0 @@
<script lang="ts">
import LinkDevice from '$components/auth/LinkDevice.svelte'
</script>
<LinkDevice />

View File

@ -1,5 +1,12 @@
<script lang="ts">
import Register from '$components/auth/Register.svelte'
import { sessionStore } from '../stores'
import Register from '$components/auth/register/Register.svelte'
import Welcome from '$components/auth/register/Welcome.svelte'
</script>
<Register />
{#if $sessionStore.authed}
<Welcome />
{:else}
<Register />
{/if}