create accounts/subaddresses, chunked address style, fix canSend for subaddresses
This commit is contained in:
@@ -7,6 +7,13 @@
|
||||
import { Button } from '$lib/components/ui/button/index.js'
|
||||
import { Textarea } from '$lib/components/ui/textarea/index.js'
|
||||
import { Input } from '$lib/components/ui/input/index.js'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
} from '$lib/components/ui/select/index.js'
|
||||
import * as Dialog from '$lib/components/ui/dialog/index.js'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -31,6 +38,13 @@
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const chunkAddress = (addr: string) => {
|
||||
const start = addr.slice(0, 6)
|
||||
const mid = addr.slice(6, 12)
|
||||
const end = addr.slice(-6)
|
||||
return { start, mid, end }
|
||||
}
|
||||
|
||||
let moneroWalletName = $state('')
|
||||
let moneroWalletPass = $state('')
|
||||
let moneroMnemonic = $state('')
|
||||
@@ -48,6 +62,12 @@
|
||||
estimatedFee: string
|
||||
} | null>(null)
|
||||
|
||||
let accountDialogOpen = $state(false)
|
||||
let accountLabel = $state('')
|
||||
let subaddressDialogOpen = $state(false)
|
||||
let subaddressAccountIndex = $state('')
|
||||
let subaddressLabel = $state('')
|
||||
|
||||
$effect(() => {
|
||||
if (w.walletOpen && !autoRefreshTimer) {
|
||||
autoRefreshTimer = setInterval(() => w.refresh(), 30000)
|
||||
@@ -91,7 +111,7 @@
|
||||
{:else if !w.accountType}
|
||||
<p class="text-center text-muted-foreground text-sm mt-8">
|
||||
<a href="/" class="underline">Go to welcome page</a>
|
||||
to get started
|
||||
to get started
|
||||
</p>
|
||||
{:else if w.accountType === 'monero'}
|
||||
{#if w.downloading}
|
||||
@@ -283,6 +303,7 @@
|
||||
</Card>
|
||||
{:else}
|
||||
<!-- Monero wallet unlocked -->
|
||||
{@const a = chunkAddress(w.address)}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -313,7 +334,14 @@
|
||||
<div class="min-w-0 flex-1 space-y-1">
|
||||
<p class="font-medium text-xs">Address</p>
|
||||
<p class="font-mono text-xs break-all">
|
||||
{w.address}
|
||||
<span class="text-primary-foreground">
|
||||
{a.start}
|
||||
</span>
|
||||
<span class="text-muted-foreground">{a.mid}</span>
|
||||
<span class="text-muted-foreground/40">...</span>
|
||||
<span class="text-primary-foreground">
|
||||
{a.end}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -325,12 +353,19 @@
|
||||
</p>
|
||||
{/if}
|
||||
{#if feeEstimate}
|
||||
{@const labels = ['Unimportant', 'Normal', 'Elevated', 'Priority']}
|
||||
{@const labels = [
|
||||
'Unimportant',
|
||||
'Normal',
|
||||
'Elevated',
|
||||
'Priority',
|
||||
]}
|
||||
<div class="mt-1 space-y-0.5">
|
||||
{#each feeEstimate.fees as f, i}
|
||||
{@const feeAtomic = (BigInt(f) * 2500n) / 1000n}
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{labels[i]}: ~{atomicToXmr(feeAtomic.toString())} XMR
|
||||
{labels[i]}: ~{atomicToXmr(
|
||||
feeAtomic.toString(),
|
||||
)} XMR
|
||||
</p>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -408,7 +443,12 @@
|
||||
<div class="max-h-96 space-y-1 overflow-y-auto">
|
||||
{#each w.txs as tx}
|
||||
<button
|
||||
onclick={() => navigate(`/monero/tx/${tx.hash}`)}
|
||||
onclick={() =>
|
||||
navigate(`/monero/tx/:hash`, {
|
||||
params: {
|
||||
hash: tx.hash,
|
||||
},
|
||||
})}
|
||||
class="flex w-full items-start gap-2 rounded-md bg-muted px-3 py-2 text-xs text-left cursor-pointer hover:bg-muted/80 transition-colors">
|
||||
<div
|
||||
class="shrink-0 mt-0.5 {tx.direction === 'in'
|
||||
@@ -457,7 +497,121 @@
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="flex gap-2 mb-3">
|
||||
<Dialog.Root bind:open={accountDialogOpen}>
|
||||
<Dialog.Trigger>
|
||||
<Button size="sm" variant="outline">
|
||||
+ Account
|
||||
</Button>
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Content>
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Create Account</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
Optional label for the new account
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<div class="px-6 pb-4 space-y-3">
|
||||
<Input
|
||||
placeholder="Account label (optional)"
|
||||
bind:value={accountLabel} />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
<Dialog.Root bind:open={subaddressDialogOpen}>
|
||||
<Dialog.Trigger>
|
||||
<Button size="sm" variant="outline">
|
||||
+ Subaddress
|
||||
</Button>
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Content>
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Create Subaddress</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
Account index and optional label
|
||||
</Dialog.Description>
|
||||
</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>
|
||||
<Select
|
||||
type="single"
|
||||
bind:value={subaddressAccountIndex}>
|
||||
<SelectTrigger>
|
||||
{subaddressAccountIndex
|
||||
? `Account ${subaddressAccountIndex}`
|
||||
: 'Select account'}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{#each w.accounts as acct}
|
||||
<SelectItem value={String(acct.index)}>
|
||||
Account {acct.index}{acct.label
|
||||
? ` - ${acct.label}`
|
||||
: ''}
|
||||
</SelectItem>
|
||||
{/each}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Input
|
||||
placeholder="Subaddress label (optional)"
|
||||
bind:value={subaddressLabel} />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
</div>
|
||||
{#each w.accounts as account}
|
||||
{@const a = chunkAddress(account.primaryAddress)}
|
||||
<div class="mb-3 rounded-md bg-muted p-3">
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<p class="text-xs font-medium">
|
||||
@@ -469,10 +623,16 @@
|
||||
</div>
|
||||
<p
|
||||
class="mb-2 font-mono text-xs text-muted-foreground break-all">
|
||||
{account.primaryAddress}
|
||||
<span class="text-primary-foreground">
|
||||
{a.start}
|
||||
</span>
|
||||
<span class="text-muted-foreground">{a.mid}</span>
|
||||
<span class="text-muted-foreground/40">...</span>
|
||||
<span class="text-primary-foreground">{a.end}</span>
|
||||
</p>
|
||||
<button
|
||||
onclick={() => copyToClipboard(account.primaryAddress)}
|
||||
onclick={() =>
|
||||
copyToClipboard(account.primaryAddress)}
|
||||
class="text-xs text-muted-foreground hover:text-foreground flex items-center gap-1 transition-colors">
|
||||
<Copy size={12} /> Copy address
|
||||
</button>
|
||||
@@ -482,6 +642,7 @@
|
||||
Subaddresses
|
||||
</p>
|
||||
{#each account.subaddresses as sub}
|
||||
{@const a = chunkAddress(sub.address)}
|
||||
<div
|
||||
class="rounded-sm bg-background px-2 py-1.5 text-xs">
|
||||
<div class="flex items-start gap-2">
|
||||
@@ -498,12 +659,23 @@
|
||||
{atomicToXmr(sub.balance)} XMR
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
class="font-mono text-muted-foreground/50 truncate mt-0.5">
|
||||
{sub.address}
|
||||
<p class="font-mono text-xs mt-0.5">
|
||||
<span class="text-primary-foreground">
|
||||
{a.start}
|
||||
</span>
|
||||
<span class="text-muted-foreground">
|
||||
{a.mid}
|
||||
</span>
|
||||
<span class="text-muted-foreground/40">
|
||||
...
|
||||
</span>
|
||||
<span class="text-primary-foreground">
|
||||
{a.end}
|
||||
</span>
|
||||
</p>
|
||||
<button
|
||||
onclick={() => copyToClipboard(sub.address)}
|
||||
onclick={() =>
|
||||
copyToClipboard(sub.address)}
|
||||
class="mt-1 text-xs text-muted-foreground hover:text-foreground flex items-center gap-1 transition-colors">
|
||||
<Copy size={12} /> Copy
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user