Refactor WalletPage.vue for improved layout and styling
- Adjusted spacing and padding in the WalletPage layout for better visual consistency across different screen sizes. - Updated text sizes and button dimensions to enhance readability and usability. - Improved the structure of transaction and address sections for a more organized presentation of information. - Enhanced QR code display and interaction elements for better user experience. These changes streamline the user interface, making it more intuitive and visually appealing.
This commit is contained in:
parent
ab1a6747ce
commit
82a5e40e8e
1 changed files with 111 additions and 111 deletions
|
|
@ -182,67 +182,66 @@ onMounted(async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto py-8 px-4 max-w-6xl">
|
||||
<div class="container mx-auto py-4 sm:py-8 px-3 sm:px-4 max-w-6xl">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold flex items-center gap-2">
|
||||
<Wallet class="h-8 w-8" />
|
||||
<div class="mb-4 sm:mb-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold flex items-center gap-2">
|
||||
<Wallet class="h-6 w-6 sm:h-8 sm:w-8" />
|
||||
Wallet
|
||||
</h1>
|
||||
<p class="text-muted-foreground mt-1">Manage your Bitcoin transactions</p>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="refresh"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': isLoading }" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="refresh"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': isLoading }" />
|
||||
</Button>
|
||||
<p class="text-sm sm:text-base text-muted-foreground mt-1">Manage your Bitcoin transactions</p>
|
||||
</div>
|
||||
|
||||
<!-- Balance Card -->
|
||||
<Card class="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Total Balance</CardTitle>
|
||||
<CardDescription>Available across all your wallets</CardDescription>
|
||||
<Card class="mb-3 sm:mb-6">
|
||||
<CardHeader class="pb-3 sm:pb-6">
|
||||
<CardTitle class="text-lg sm:text-xl">Total Balance</CardTitle>
|
||||
<CardDescription class="text-sm">Available across all your wallets</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="flex flex-col gap-6">
|
||||
<CardContent class="pt-0">
|
||||
<div class="flex flex-col gap-4 sm:gap-6">
|
||||
<!-- Balance Section -->
|
||||
<div class="text-center sm:text-left">
|
||||
<div class="text-4xl sm:text-3xl font-bold">
|
||||
{{ Math.floor(totalBalance / 1000).toLocaleString() }} <span class="text-2xl sm:text-xl text-muted-foreground font-normal">sats</span>
|
||||
<div class="text-3xl sm:text-4xl font-bold">
|
||||
{{ Math.floor(totalBalance / 1000).toLocaleString() }} <span class="text-xl sm:text-2xl text-muted-foreground font-normal">sats</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex gap-3 w-full sm:w-auto">
|
||||
<div class="flex gap-2 sm:gap-3 w-full sm:w-auto">
|
||||
<Button
|
||||
variant="default"
|
||||
@click="showReceiveDialog = true"
|
||||
class="gap-2 flex-1 sm:flex-none h-12 text-base"
|
||||
class="gap-2 flex-1 sm:flex-none h-10 sm:h-12 text-sm sm:text-base"
|
||||
>
|
||||
<QrCode class="h-5 w-5" />
|
||||
<QrCode class="h-4 w-4 sm:h-5 sm:w-5" />
|
||||
Receive
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@click="showSendDialog = true"
|
||||
class="gap-2 flex-1 sm:flex-none h-12 text-base"
|
||||
class="gap-2 flex-1 sm:flex-none h-10 sm:h-12 text-sm sm:text-base"
|
||||
>
|
||||
<Send class="h-5 w-5" />
|
||||
<Send class="h-4 w-4 sm:h-5 sm:w-5" />
|
||||
Send
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@click="showQRScanner = true"
|
||||
class="h-12 w-12 p-0"
|
||||
class="h-10 w-10 sm:h-12 sm:w-12 p-0"
|
||||
title="Scan QR Code"
|
||||
>
|
||||
<ScanLine class="h-5 w-5" />
|
||||
<ScanLine class="h-4 w-4 sm:h-5 sm:w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -250,62 +249,62 @@ onMounted(async () => {
|
|||
</Card>
|
||||
|
||||
<!-- Quick Receive QR Code -->
|
||||
<Card v-if="firstPayLink" class="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<QrCode class="h-5 w-5" />
|
||||
<Card v-if="firstPayLink" class="mb-3 sm:mb-6">
|
||||
<CardHeader class="pb-3 sm:pb-6">
|
||||
<CardTitle class="flex items-center gap-2 text-lg sm:text-xl">
|
||||
<QrCode class="h-4 w-4 sm:h-5 sm:w-5" />
|
||||
Quick Receive
|
||||
</CardTitle>
|
||||
<CardDescription>{{ firstPayLink.description }}</CardDescription>
|
||||
<CardDescription class="text-sm">{{ firstPayLink.description }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="flex flex-col sm:flex-row items-center gap-6">
|
||||
<CardContent class="pt-0">
|
||||
<div class="flex flex-col sm:flex-row items-center gap-4 sm:gap-6">
|
||||
<!-- QR Code -->
|
||||
<div class="flex-shrink-0">
|
||||
<div v-if="isGeneratingQR" class="w-48 h-48 flex items-center justify-center bg-muted rounded-lg">
|
||||
<RefreshCw class="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
<div v-if="isGeneratingQR" class="w-36 h-36 sm:w-48 sm:h-48 flex items-center justify-center bg-muted rounded-lg">
|
||||
<RefreshCw class="h-6 w-6 sm:h-8 sm:w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
<div v-else-if="defaultQrCode" class="text-center">
|
||||
<div class="bg-white p-4 rounded-lg cursor-pointer hover:bg-gray-50 transition-colors" @click="handleQRClick" title="Click to copy LNURL">
|
||||
<div class="bg-white p-2 sm:p-4 rounded-lg cursor-pointer hover:bg-gray-50 transition-colors" @click="handleQRClick" title="Click to copy LNURL">
|
||||
<img
|
||||
:src="defaultQrCode"
|
||||
alt="LNURL QR Code"
|
||||
class="w-48 h-48"
|
||||
class="w-36 h-36 sm:w-48 sm:h-48"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground mt-2">
|
||||
<p class="text-xs text-muted-foreground mt-1 sm:mt-2">
|
||||
Click QR code to copy LNURL
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="w-48 h-48 flex items-center justify-center bg-muted rounded-lg">
|
||||
<QrCode class="h-12 w-12 text-muted-foreground opacity-50" />
|
||||
<div v-else class="w-36 h-36 sm:w-48 sm:h-48 flex items-center justify-center bg-muted rounded-lg">
|
||||
<QrCode class="h-8 w-8 sm:h-12 sm:w-12 text-muted-foreground opacity-50" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address Info -->
|
||||
<div class="flex-1 space-y-4 w-full">
|
||||
<div class="flex-1 space-y-3 sm:space-y-4 w-full">
|
||||
<div>
|
||||
<h4 class="font-medium mb-2">Payment Range</h4>
|
||||
<div class="text-2xl font-bold text-green-600">
|
||||
<h4 class="font-medium mb-1 sm:mb-2 text-sm sm:text-base">Payment Range</h4>
|
||||
<div class="text-xl sm:text-2xl font-bold text-green-600">
|
||||
{{ firstPayLink.min?.toLocaleString() }} - {{ firstPayLink.max?.toLocaleString() }} sats
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="firstPayLink.lnaddress">
|
||||
<h4 class="font-medium mb-2">Lightning Address</h4>
|
||||
<h4 class="font-medium mb-1 sm:mb-2 text-sm sm:text-base">Lightning Address</h4>
|
||||
<div class="flex gap-2">
|
||||
<div class="font-mono text-sm bg-muted px-3 py-2 rounded flex-1">
|
||||
<div class="font-mono text-xs sm:text-sm bg-muted px-2 sm:px-3 py-1.5 sm:py-2 rounded flex-1 break-all">
|
||||
{{ firstPayLink.lnaddress }}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@click="copyToClipboard(firstPayLink.lnaddress || '', 'lightning-address')"
|
||||
class="h-auto px-2"
|
||||
class="h-auto px-2 shrink-0"
|
||||
title="Copy Lightning Address"
|
||||
>
|
||||
<Check v-if="copiedField === 'lightning-address'" class="h-4 w-4 text-green-600" />
|
||||
<Copy v-else class="h-4 w-4" />
|
||||
<Check v-if="copiedField === 'lightning-address'" class="h-3 w-3 sm:h-4 sm:w-4 text-green-600" />
|
||||
<Copy v-else class="h-3 w-3 sm:h-4 sm:w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -317,58 +316,58 @@ onMounted(async () => {
|
|||
|
||||
<!-- Tabs -->
|
||||
<Tabs v-model="selectedTab" class="w-full">
|
||||
<TabsList class="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="transactions">Transaction History</TabsTrigger>
|
||||
<TabsTrigger value="addresses">Receive Addresses</TabsTrigger>
|
||||
<TabsList class="grid w-full grid-cols-2 h-9 sm:h-10">
|
||||
<TabsTrigger value="transactions" class="text-sm sm:text-base">Transaction History</TabsTrigger>
|
||||
<TabsTrigger value="addresses" class="text-sm sm:text-base">Receive Addresses</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<!-- Transactions Tab -->
|
||||
<TabsContent value="transactions" class="space-y-4">
|
||||
<TabsContent value="transactions" class="space-y-3 sm:space-y-4 mt-3 sm:mt-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Recent Transactions</CardTitle>
|
||||
<CardDescription>Your payment history</CardDescription>
|
||||
<CardHeader class="pb-3 sm:pb-6">
|
||||
<CardTitle class="text-lg sm:text-xl">Recent Transactions</CardTitle>
|
||||
<CardDescription class="text-sm">Your payment history</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div v-if="error" class="text-center py-8 text-destructive">
|
||||
<CardContent class="pt-0">
|
||||
<div v-if="error" class="text-center py-6 sm:py-8 text-destructive">
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<div v-else-if="transactions.length === 0" class="text-center py-12 text-muted-foreground">
|
||||
<Clock class="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No transactions yet</p>
|
||||
<p class="text-sm mt-2">Your transaction history will appear here</p>
|
||||
<div v-else-if="transactions.length === 0" class="text-center py-8 sm:py-12 text-muted-foreground">
|
||||
<Clock class="h-8 w-8 sm:h-12 sm:w-12 mx-auto mb-3 sm:mb-4 opacity-50" />
|
||||
<p class="text-sm sm:text-base">No transactions yet</p>
|
||||
<p class="text-xs sm:text-sm mt-1 sm:mt-2">Your transaction history will appear here</p>
|
||||
</div>
|
||||
|
||||
<ScrollArea v-else class="h-[500px] pr-4">
|
||||
<div v-for="(txs, date) in groupedTransactions" :key="date" class="mb-6">
|
||||
<div class="text-sm font-medium text-muted-foreground mb-3">{{ date }}</div>
|
||||
<ScrollArea v-else class="h-[400px] sm:h-[500px] pr-2 sm:pr-4">
|
||||
<div v-for="(txs, date) in groupedTransactions" :key="date" class="mb-4 sm:mb-6">
|
||||
<div class="text-xs sm:text-sm font-medium text-muted-foreground mb-2 sm:mb-3">{{ date }}</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-3 sm:space-y-4">
|
||||
<div
|
||||
v-for="tx in txs"
|
||||
:key="tx.id"
|
||||
class="relative p-3 rounded-lg border hover:bg-accent/50 transition-colors"
|
||||
class="relative p-2.5 sm:p-3 rounded-lg border hover:bg-accent/50 transition-colors"
|
||||
>
|
||||
<!-- Tag badge in top-left corner -->
|
||||
<Badge v-if="tx.tag" variant="secondary" class="absolute -top-2.75 left-11 text-xs font-medium z-10 bg-blue-100 text-blue-800 border-blue-200 pointer-events-none">
|
||||
<Badge v-if="tx.tag" variant="secondary" class="absolute -top-2.75 left-8 sm:left-11 text-xs font-medium z-10 bg-blue-100 text-blue-800 border-blue-200 pointer-events-none">
|
||||
{{ tx.tag }}
|
||||
</Badge>
|
||||
<!-- Mobile Layout: Stack vertically -->
|
||||
<div class="block sm:hidden">
|
||||
<!-- Header Row: Icon, Amount, Type -->
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="p-1.5 rounded-full bg-background border"
|
||||
class="p-1 rounded-full bg-background border"
|
||||
:class="getTransactionColor(tx.type, tx.status)"
|
||||
>
|
||||
<component
|
||||
:is="getTransactionIcon(tx.type, tx.status)"
|
||||
class="h-4 w-4"
|
||||
class="h-3.5 w-3.5"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-sm text-muted-foreground">{{
|
||||
<span class="text-xs text-muted-foreground">{{
|
||||
(() => {
|
||||
try {
|
||||
if (tx.timestamp instanceof Date && !isNaN(tx.timestamp.getTime())) {
|
||||
|
|
@ -391,7 +390,7 @@ onMounted(async () => {
|
|||
|
||||
<div class="text-right">
|
||||
<p
|
||||
class="font-semibold text-base"
|
||||
class="font-semibold text-sm"
|
||||
:class="getTransactionColor(tx.type, tx.status)"
|
||||
>
|
||||
{{ tx.type === 'received' ? '+' : '-' }}{{ tx.amount.toLocaleString() }}
|
||||
|
|
@ -401,12 +400,12 @@ onMounted(async () => {
|
|||
</div>
|
||||
|
||||
<!-- Description Row -->
|
||||
<div class="mb-2">
|
||||
<p class="font-medium text-sm leading-tight break-words pr-2">{{ tx.description }}</p>
|
||||
<div class="mb-1">
|
||||
<p class="font-medium text-xs leading-tight break-words pr-1">{{ tx.description }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Badges Row -->
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<div class="flex items-center gap-1.5 flex-wrap">
|
||||
<span v-if="tx.fee" class="text-xs text-muted-foreground">
|
||||
Fee: {{ tx.fee }} sats
|
||||
</span>
|
||||
|
|
@ -473,58 +472,59 @@ onMounted(async () => {
|
|||
</TabsContent>
|
||||
|
||||
<!-- Addresses Tab -->
|
||||
<TabsContent value="addresses" class="space-y-4">
|
||||
<TabsContent value="addresses" class="space-y-3 sm:space-y-4 mt-3 sm:mt-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div class="flex items-center justify-between">
|
||||
<CardHeader class="pb-3 sm:pb-6">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-0">
|
||||
<div>
|
||||
<CardTitle>Receive Addresses</CardTitle>
|
||||
<CardDescription>Your LNURL and Lightning addresses for receiving payments</CardDescription>
|
||||
<CardTitle class="text-lg sm:text-xl">Receive Addresses</CardTitle>
|
||||
<CardDescription class="text-sm">Your LNURL and Lightning addresses for receiving payments</CardDescription>
|
||||
</div>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
@click="showReceiveDialog = true"
|
||||
class="w-full sm:w-auto"
|
||||
>
|
||||
<QrCode class="h-4 w-4 mr-2" />
|
||||
New Address
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div v-if="walletService?.payLinks?.value?.length === 0" class="text-center py-12 text-muted-foreground">
|
||||
<QrCode class="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No receive addresses created</p>
|
||||
<p class="text-sm mt-2">Create an address to start receiving payments</p>
|
||||
<CardContent class="pt-0">
|
||||
<div v-if="walletService?.payLinks?.value?.length === 0" class="text-center py-8 sm:py-12 text-muted-foreground">
|
||||
<QrCode class="h-8 w-8 sm:h-12 sm:w-12 mx-auto mb-3 sm:mb-4 opacity-50" />
|
||||
<p class="text-sm sm:text-base">No receive addresses created</p>
|
||||
<p class="text-xs sm:text-sm mt-1 sm:mt-2">Create an address to start receiving payments</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="mt-4"
|
||||
class="mt-3 sm:mt-4"
|
||||
@click="showReceiveDialog = true"
|
||||
>
|
||||
Create Your First Address
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-4">
|
||||
<div v-else class="space-y-3 sm:space-y-4">
|
||||
<div
|
||||
v-for="link in walletService?.payLinks?.value"
|
||||
:key="link.id"
|
||||
class="p-4 border rounded-lg hover:bg-accent/50 transition-colors"
|
||||
class="p-3 sm:p-4 border rounded-lg hover:bg-accent/50 transition-colors"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex flex-col sm:flex-row sm:items-start justify-between gap-3 sm:gap-0">
|
||||
<div class="flex-1">
|
||||
<p class="font-medium">{{ link.description }}</p>
|
||||
<div class="flex items-center gap-4 mt-2 text-sm text-muted-foreground">
|
||||
<span>{{ link.min }}-{{ link.max }} sats</span>
|
||||
<Badge v-if="link.username" variant="secondary">
|
||||
<p class="font-medium text-sm sm:text-base">{{ link.description }}</p>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 mt-2 text-sm text-muted-foreground">
|
||||
<span class="text-xs sm:text-sm">{{ link.min }}-{{ link.max }} sats</span>
|
||||
<Badge v-if="link.username" variant="secondary" class="w-fit text-xs">
|
||||
{{ link.username }}@{{ baseDomain }}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 space-y-2">
|
||||
<div v-if="link.lnaddress" class="flex items-center gap-2">
|
||||
<div class="mt-2 sm:mt-3 space-y-1.5 sm:space-y-2">
|
||||
<div v-if="link.lnaddress" class="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2">
|
||||
<span class="text-xs text-muted-foreground">Lightning Address:</span>
|
||||
<code class="text-xs bg-muted px-2 py-1 rounded">{{ link.lnaddress }}</code>
|
||||
<code class="text-xs bg-muted px-2 py-1 rounded break-all">{{ link.lnaddress }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue