0.0.2 loading states + wallet dialogs + monero unlock flow

This commit is contained in:
2026-08-02 12:15:55 +08:00
parent 50a7771d97
commit c9e492ad95
6 changed files with 399 additions and 80 deletions
@@ -24,6 +24,8 @@
import Send from '@lucide/svelte/icons/send'
import QrCodeIcon from '@lucide/svelte/icons/qr-code'
import Plus from '@lucide/svelte/icons/plus'
import Users from '@lucide/svelte/icons/users'
import Tags from '@lucide/svelte/icons/tags'
import Loader from '$lib/components/loader.svelte'
import QrCode from '$lib/components/qr.svelte'
import { navigate, route } from 'sv-router/generated'
@@ -97,9 +99,53 @@
let accountDialogOpen = $state(false)
let accountLabel = $state('')
let accountBusy = $state(false)
let accountError = $state('')
let subaddressDialogOpen = $state(false)
let subaddressAccountIndex = $state('')
let subaddressLabel = $state('')
let subaddressBusy = $state(false)
let subaddressError = $state('')
$effect(() => {
if (!subaddressAccountIndex && w.accounts.length > 0) {
subaddressAccountIndex = String(w.accounts[0].index)
}
})
async function handleCreateAccount() {
if (accountBusy) return
accountBusy = true
accountError = ''
try {
await w.createAccount(accountLabel || undefined)
accountDialogOpen = false
accountLabel = ''
} catch (e) {
accountError = e instanceof Error ? e.message : 'Failed to create account'
} finally {
accountBusy = false
}
}
async function handleCreateSubaddress() {
if (subaddressBusy || !subaddressAccountIndex) return
subaddressBusy = true
subaddressError = ''
try {
await w.createSubaddress(
parseInt(subaddressAccountIndex),
subaddressLabel || undefined,
)
subaddressDialogOpen = false
subaddressAccountIndex = ''
subaddressLabel = ''
} catch (e) {
subaddressError = e instanceof Error ? e.message : 'Failed to create subaddress'
} finally {
subaddressBusy = false
}
}
const walletName = $derived(route.params.name)
@@ -134,16 +180,27 @@
})
onMount(() => {
if (!w.walletOpen || w.walletName !== walletName) {
if (w.walletOpen && w.walletName === walletName) {
w.refresh()
electrobun.rpc?.request
.fetchMoneroPrice({})
.then((p) => (moneroPrice = p))
}
})
$effect(() => {
if (!w.opening && (!w.walletOpen || w.walletName !== walletName)) {
navigate('/monero')
}
w.refresh()
electrobun.rpc?.request
.fetchMoneroPrice({})
.then((p) => (moneroPrice = p))
})
</script>
{#if !w.walletOpen || w.walletName !== walletName}
<div class="flex flex-col items-center justify-center gap-3 py-24">
<Loader />
<p class="text-sm text-muted-foreground">Opening wallet...</p>
</div>
{:else}
<div class="space-y-6">
<div class="flex flex-col items-center py-2">
{#if w.loading}
@@ -340,14 +397,44 @@
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Create Account</Dialog.Title>
<Dialog.Description>Optional label for the new account</Dialog.Description>
<div class="flex items-center gap-3">
<div class="flex size-10 shrink-0 items-center justify-center rounded-full bg-muted">
<Users size={16} class="text-muted-foreground" />
</div>
<div class="space-y-0.5">
<Dialog.Title>Create Account</Dialog.Title>
<Dialog.Description>
New account with its own balance and addresses
</Dialog.Description>
</div>
</div>
</Dialog.Header>
<div class="px-6 pb-4 space-y-3">
<Input placeholder="Account label (optional)" bind:value={accountLabel} />
<div class="space-y-1.5">
<label class="text-xs font-medium" for="account-label">Label</label>
<Input id="account-label" placeholder="Savings" bind:value={accountLabel} />
<p class="text-xs text-muted-foreground">
Optional — shown next to the account in your wallet
</p>
</div>
{#if accountError}
<p class="text-xs text-destructive">{accountError}</p>
{/if}
<div class="flex justify-end gap-2">
<Button variant="outline" size="sm" onclick={() => { accountDialogOpen = false; accountLabel = '' }}>Cancel</Button>
<Button size="sm" onclick={async () => { await w.createAccount(accountLabel || undefined); accountDialogOpen = false; accountLabel = '' }}>Create</Button>
<Button
variant="outline"
size="sm"
disabled={accountBusy}
onclick={() => {
accountDialogOpen = false
accountLabel = ''
accountError = ''
}}>
Cancel
</Button>
<Button size="sm" disabled={accountBusy} onclick={handleCreateAccount}>
{accountBusy ? 'Creating...' : 'Create'}
</Button>
</div>
</div>
</Dialog.Content>
@@ -360,29 +447,66 @@
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Create Subaddress</Dialog.Title>
<Dialog.Description>Account index and optional label</Dialog.Description>
<div class="flex items-center gap-3">
<div class="flex size-10 shrink-0 items-center justify-center rounded-full bg-muted">
<Tags size={16} class="text-muted-foreground" />
</div>
<div class="space-y-0.5">
<Dialog.Title>Create Subaddress</Dialog.Title>
<Dialog.Description>
Receiving address tied to an account
</Dialog.Description>
</div>
</div>
</Dialog.Header>
<div class="px-6 pb-4 space-y-3">
<div class="space-y-1.5">
<label class="text-xs font-medium">Account</label>
<label class="text-xs font-medium" for="subaddress-account">Account</label>
<Select type="single" bind:value={subaddressAccountIndex}>
<SelectTrigger>
{subaddressAccountIndex ? `Account ${subaddressAccountIndex}` : 'Select account'}
Account {subaddressAccountIndex}
</SelectTrigger>
<SelectContent>
{#each w.accounts as acct}
<SelectItem value={String(acct.index)}>
Account {acct.index}{acct.label ? ` - ${acct.label}` : ''}
Account {acct.index}{acct.label ? ` - ${acct.label}` : ''} ({atomicToXmr(acct.unlockedBalance)} XMR)
</SelectItem>
{/each}
</SelectContent>
</Select>
<p class="text-xs text-muted-foreground">
Where the subaddress belongs
</p>
</div>
<Input placeholder="Subaddress label (optional)" bind:value={subaddressLabel} />
<div class="space-y-1.5">
<label class="text-xs font-medium" for="subaddress-label">Label</label>
<Input id="subaddress-label" placeholder="Donations" bind:value={subaddressLabel} />
<p class="text-xs text-muted-foreground">
Optional — helps you track who sent you funds
</p>
</div>
{#if subaddressError}
<p class="text-xs text-destructive">{subaddressError}</p>
{/if}
<div class="flex justify-end gap-2">
<Button variant="outline" size="sm" onclick={() => { subaddressDialogOpen = false; subaddressAccountIndex = ''; subaddressLabel = '' }}>Cancel</Button>
<Button size="sm" disabled={!subaddressAccountIndex} onclick={async () => { await w.createSubaddress(parseInt(subaddressAccountIndex), subaddressLabel || undefined); subaddressDialogOpen = false; subaddressAccountIndex = ''; subaddressLabel = '' }}>Create</Button>
<Button
variant="outline"
size="sm"
disabled={subaddressBusy}
onclick={() => {
subaddressDialogOpen = false
subaddressAccountIndex = ''
subaddressLabel = ''
subaddressError = ''
}}>
Cancel
</Button>
<Button
size="sm"
disabled={subaddressBusy || !subaddressAccountIndex}
onclick={handleCreateSubaddress}>
{subaddressBusy ? 'Creating...' : 'Create'}
</Button>
</div>
</div>
</Dialog.Content>
@@ -455,4 +579,5 @@
</div>
</CardContent>
</Card>
</div>
</div>
{/if}