0.0.2 loading states + wallet dialogs + monero unlock flow
This commit is contained in:
@@ -4,7 +4,7 @@ export default {
|
||||
app: {
|
||||
name: 'koins',
|
||||
identifier: 'koins.tzgyn.com',
|
||||
version: '0.0.1',
|
||||
version: '0.0.2',
|
||||
},
|
||||
build: {
|
||||
// Vite builds to dist/, we copy from there
|
||||
|
||||
Binary file not shown.
@@ -46,6 +46,21 @@ export const MoneroWallet = () => {
|
||||
let loading = $state(false)
|
||||
let error = $state('')
|
||||
let selectedAccountIndex = $state(0)
|
||||
let opening = $state(false)
|
||||
let openingDepth = 0
|
||||
let passwordRequired = $state(false)
|
||||
let startInFlight: Promise<{ running: boolean; connected: boolean }> | null = null
|
||||
let loginInFlight: Promise<void> | null = null
|
||||
|
||||
const beginOpening = () => {
|
||||
openingDepth += 1
|
||||
opening = true
|
||||
}
|
||||
|
||||
const endOpening = () => {
|
||||
openingDepth = Math.max(0, openingDepth - 1)
|
||||
if (openingDepth === 0) opening = false
|
||||
}
|
||||
|
||||
const checkBiometric = async () => {
|
||||
if (!electrobun.rpc) return
|
||||
@@ -76,7 +91,12 @@ export const MoneroWallet = () => {
|
||||
}
|
||||
|
||||
const login = async () => {
|
||||
if (walletOpen) return
|
||||
if (loginInFlight) return loginInFlight
|
||||
loginInFlight = (async () => {
|
||||
accountType = 'monero'
|
||||
beginOpening()
|
||||
try {
|
||||
if (installed && !running) {
|
||||
await start()
|
||||
await checkStatus()
|
||||
@@ -85,6 +105,19 @@ export const MoneroWallet = () => {
|
||||
if (wallets.length > 0) {
|
||||
const pw = await moneroGetStoredPassword(wallets[0])
|
||||
if (pw) await openExistingWallet(wallets[0], pw)
|
||||
else passwordRequired = true
|
||||
}
|
||||
error = ''
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to open wallet'
|
||||
} finally {
|
||||
endOpening()
|
||||
}
|
||||
})()
|
||||
try {
|
||||
await loginInFlight
|
||||
} finally {
|
||||
loginInFlight = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +176,8 @@ export const MoneroWallet = () => {
|
||||
if (!electrobun.rpc) throw new Error('RPC not available')
|
||||
const pw = password ?? (await moneroGetStoredPassword(name))
|
||||
if (!pw) throw new Error('Password required')
|
||||
beginOpening()
|
||||
try {
|
||||
await electrobun.rpc.request.moneroOpenWallet({
|
||||
name,
|
||||
password: pw,
|
||||
@@ -150,6 +185,9 @@ export const MoneroWallet = () => {
|
||||
walletName = name
|
||||
walletOpen = true
|
||||
await refresh()
|
||||
} finally {
|
||||
endOpening()
|
||||
}
|
||||
}
|
||||
|
||||
const checkStatus = async () => {
|
||||
@@ -184,9 +222,20 @@ export const MoneroWallet = () => {
|
||||
|
||||
const start = async () => {
|
||||
if (!electrobun.rpc) return
|
||||
const status = await electrobun.rpc.request.moneroStart({})
|
||||
beginOpening()
|
||||
try {
|
||||
if (!startInFlight) {
|
||||
startInFlight = electrobun.rpc.request.moneroStart({})
|
||||
const status = await startInFlight
|
||||
running = status.running
|
||||
connected = status.connected
|
||||
} else {
|
||||
await startInFlight
|
||||
}
|
||||
} finally {
|
||||
startInFlight = null
|
||||
endOpening()
|
||||
}
|
||||
}
|
||||
|
||||
const stop = async () => {
|
||||
@@ -195,6 +244,7 @@ export const MoneroWallet = () => {
|
||||
running = false
|
||||
walletOpen = false
|
||||
connected = false
|
||||
passwordRequired = false
|
||||
address = ''
|
||||
balAtomic = '0'
|
||||
unlockedAtomic = '0'
|
||||
@@ -209,6 +259,8 @@ export const MoneroWallet = () => {
|
||||
storePw?: boolean,
|
||||
) => {
|
||||
if (!electrobun.rpc) throw new Error('RPC not available')
|
||||
beginOpening()
|
||||
try {
|
||||
const result = await electrobun.rpc.request.moneroCreateWallet({
|
||||
name,
|
||||
password,
|
||||
@@ -219,6 +271,9 @@ export const MoneroWallet = () => {
|
||||
if (storePw) await moneroStorePassword(name, password)
|
||||
await refresh()
|
||||
return result
|
||||
} finally {
|
||||
endOpening()
|
||||
}
|
||||
}
|
||||
|
||||
const restoreWallet = async (
|
||||
@@ -229,6 +284,8 @@ export const MoneroWallet = () => {
|
||||
storePw?: boolean,
|
||||
) => {
|
||||
if (!electrobun.rpc) throw new Error('RPC not available')
|
||||
beginOpening()
|
||||
try {
|
||||
const result = await electrobun.rpc.request.moneroRestoreWallet({
|
||||
name,
|
||||
password,
|
||||
@@ -240,14 +297,22 @@ export const MoneroWallet = () => {
|
||||
address = result.address
|
||||
if (storePw) await moneroStorePassword(name, password)
|
||||
await refresh()
|
||||
} finally {
|
||||
endOpening()
|
||||
}
|
||||
}
|
||||
|
||||
const openWallet = async (name: string, password: string) => {
|
||||
if (!electrobun.rpc) return
|
||||
beginOpening()
|
||||
try {
|
||||
await electrobun.rpc.request.moneroOpenWallet({ name, password })
|
||||
walletName = name
|
||||
walletOpen = true
|
||||
await refresh()
|
||||
} finally {
|
||||
endOpening()
|
||||
}
|
||||
}
|
||||
|
||||
const autoUnlock = async (name?: string) => {
|
||||
@@ -255,15 +320,36 @@ export const MoneroWallet = () => {
|
||||
const target = name ?? wallets[0]
|
||||
if (!target) return false
|
||||
const pw = await moneroGetStoredPassword(target)
|
||||
if (!pw) return false
|
||||
if (!pw) {
|
||||
passwordRequired = true
|
||||
return false
|
||||
}
|
||||
beginOpening()
|
||||
try {
|
||||
await electrobun.rpc.request.moneroOpenWallet({
|
||||
name: target,
|
||||
password: pw,
|
||||
})
|
||||
walletName = target
|
||||
walletOpen = true
|
||||
passwordRequired = false
|
||||
error = ''
|
||||
await refresh()
|
||||
return true
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to open wallet'
|
||||
return false
|
||||
} finally {
|
||||
endOpening()
|
||||
}
|
||||
}
|
||||
|
||||
const unlockWallet = async (password: string, remember = false) => {
|
||||
const target = wallets[0]
|
||||
if (!target) return
|
||||
await openWallet(target, password)
|
||||
passwordRequired = false
|
||||
if (remember) await moneroStorePassword(target, password)
|
||||
}
|
||||
|
||||
const refresh = async () => {
|
||||
@@ -404,6 +490,12 @@ export const MoneroWallet = () => {
|
||||
get loading() {
|
||||
return loading
|
||||
},
|
||||
get opening() {
|
||||
return opening
|
||||
},
|
||||
get passwordRequired() {
|
||||
return passwordRequired
|
||||
},
|
||||
get error() {
|
||||
return error
|
||||
},
|
||||
@@ -451,6 +543,7 @@ export const MoneroWallet = () => {
|
||||
restoreWallet,
|
||||
openWallet,
|
||||
autoUnlock,
|
||||
unlockWallet,
|
||||
refresh,
|
||||
send,
|
||||
sendAll,
|
||||
|
||||
@@ -123,6 +123,17 @@
|
||||
}
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (
|
||||
moneroWallet.ready &&
|
||||
!moneroWallet.walletOpen &&
|
||||
!moneroWallet.opening &&
|
||||
!moneroWallet.passwordRequired
|
||||
) {
|
||||
moneroWallet.login()
|
||||
}
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const handler = (event: ErrorEvent) => {
|
||||
@@ -181,7 +192,7 @@
|
||||
{:else}
|
||||
<div class="flex flex-col items-center pt-4 pb-2">
|
||||
<p class="text-xs text-muted-foreground">Total Portfolio Value</p>
|
||||
{#if loadingXrp || loadingXmr}
|
||||
{#if loadingXrp || loadingXmr || moneroWallet.opening || (moneroWallet.wallets.length > 0 && !moneroWallet.walletOpen)}
|
||||
<div class="mt-2 h-9 w-44 rounded-md bg-muted animate-pulse"></div>
|
||||
{:else}
|
||||
<p class="mt-1 text-3xl font-semibold tabular-nums">
|
||||
@@ -240,9 +251,7 @@
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{#if loadingXmr && !moneroWallet.walletOpen}
|
||||
<div class="h-14 flex items-center"><Loader /></div>
|
||||
{:else if moneroWallet.walletOpen}
|
||||
{#if moneroWallet.walletOpen}
|
||||
<p class="font-mono text-xl tabular-nums">{fmtBal(xmrBalance, 4)}</p>
|
||||
{#if xmrPrice}
|
||||
<p class="mt-0.5 text-sm text-muted-foreground tabular-nums">
|
||||
@@ -252,8 +261,31 @@
|
||||
${fmtUsd(parseFloat(xmrPrice) )} / XMR
|
||||
</p>
|
||||
{/if}
|
||||
{:else if moneroWallet.passwordRequired && moneroWallet.wallets.length > 0}
|
||||
<p class="text-sm text-muted-foreground py-3">Wallet locked — unlock to view balance</p>
|
||||
{:else if moneroWallet.wallets.length > 0}
|
||||
<p class="text-sm text-muted-foreground py-3">Open wallet to view balance</p>
|
||||
<div class="h-14 flex items-center gap-2">
|
||||
<Loader />
|
||||
<p class="text-sm text-muted-foreground">Opening wallet...</p>
|
||||
</div>
|
||||
{:else if moneroWallet.opening}
|
||||
<div class="h-14 flex items-center gap-2">
|
||||
<Loader />
|
||||
<p class="text-sm text-muted-foreground">Starting wallet server...</p>
|
||||
</div>
|
||||
{:else if moneroWallet.error}
|
||||
<div class="py-3">
|
||||
<p class="text-xs text-destructive break-words">{moneroWallet.error}</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="mt-2 gap-1.5"
|
||||
onclick={() => moneroWallet.login()}>
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
{:else if loadingXmr}
|
||||
<div class="h-14 flex items-center"><Loader /></div>
|
||||
{:else}
|
||||
<Button
|
||||
onclick={() => navigate('/monero')}
|
||||
|
||||
@@ -19,8 +19,27 @@
|
||||
let moneroMnemonic = $state('')
|
||||
let moneroRestoreHeight = $state<number | undefined>(undefined)
|
||||
|
||||
let walletPassword = $state('')
|
||||
let rememberPassword = $state(true)
|
||||
let unlocking = $state(false)
|
||||
let unlockError = $state('')
|
||||
|
||||
let initStarted = false
|
||||
|
||||
async function unlockWithPassword() {
|
||||
if (unlocking || !walletPassword.trim()) return
|
||||
unlocking = true
|
||||
unlockError = ''
|
||||
try {
|
||||
await w.unlockWallet(walletPassword.trim(), rememberPassword)
|
||||
walletPassword = ''
|
||||
} catch (e) {
|
||||
unlockError = e instanceof Error ? e.message : 'Failed to unlock wallet'
|
||||
} finally {
|
||||
unlocking = false
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (w.walletOpen && w.walletName) {
|
||||
navigate('/monero/wallet/:name', {
|
||||
@@ -40,7 +59,7 @@
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (w.running && !w.walletOpen && w.wallets.length > 0 && !w.loading) {
|
||||
if (w.running && !w.walletOpen && w.wallets.length > 0 && !w.loading && !w.opening) {
|
||||
w.autoUnlock(w.wallets[0])
|
||||
}
|
||||
})
|
||||
@@ -66,6 +85,15 @@
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{:else if w.opening}
|
||||
<Card>
|
||||
<CardContent class="flex items-center justify-center gap-2 py-8">
|
||||
<Loader />
|
||||
<span class="text-sm text-muted-foreground">
|
||||
{!w.running ? 'Starting wallet server...' : 'Opening wallet...'}
|
||||
</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{:else if !w.installed}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -99,7 +127,48 @@
|
||||
</CardContent>
|
||||
</Card>
|
||||
{:else if !w.walletOpen}
|
||||
{#if w.wallets.length > 0}
|
||||
{#if w.opening}
|
||||
<Card>
|
||||
<CardContent class="flex items-center justify-center py-8">
|
||||
<Loader />
|
||||
<span class="ml-2 text-sm text-muted-foreground">
|
||||
Opening wallet...
|
||||
</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{:else if w.passwordRequired && w.wallets.length > 0}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Wallet locked</CardTitle>
|
||||
<CardDescription>
|
||||
Enter the password for {w.wallets[0]} to open it
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="flex flex-col gap-3">
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Wallet password"
|
||||
bind:value={walletPassword} />
|
||||
<label class="flex items-center gap-2 text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={rememberPassword}
|
||||
class="h-3.5 w-3.5" />
|
||||
Remember password for auto-unlock
|
||||
</label>
|
||||
<Button
|
||||
onclick={unlockWithPassword}
|
||||
disabled={unlocking || !walletPassword.trim()}>
|
||||
{unlocking ? 'Opening...' : 'Unlock Wallet'}
|
||||
</Button>
|
||||
{#if unlockError}
|
||||
<p class="text-xs text-destructive">{unlockError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{:else if w.wallets.length > 0}
|
||||
<Card>
|
||||
<CardContent class="flex items-center justify-center py-8">
|
||||
<Loader />
|
||||
|
||||
@@ -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) {
|
||||
navigate('/monero')
|
||||
}
|
||||
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')
|
||||
}
|
||||
})
|
||||
</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>
|
||||
<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>Optional label for the new account</Dialog.Description>
|
||||
<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>
|
||||
<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>Account index and optional label</Dialog.Description>
|
||||
<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>
|
||||
@@ -456,3 +580,4 @@
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user