Skip to content
This repository was archived by the owner on Mar 10, 2025. It is now read-only.

Commit 74b4478

Browse files
committed
Merge branch '307-make-app-more-responsive' of https://github.com/stacks-network/sbtc-bridge-web into 307-make-app-more-responsive
* '307-make-app-more-responsive' of https://github.com/stacks-network/sbtc-bridge-web: feat: UX/UI enhancements for Transaction history page (#294)
2 parents 30a78a3 + 8b130ed commit 74b4478

File tree

4 files changed

+270
-194
lines changed

4 files changed

+270
-194
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<script lang="ts">
2+
import { onMount } from 'svelte';
3+
import { Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from 'flowbite-svelte';
4+
5+
import { page } from '$app/stores';
6+
import { COMMS_ERROR, explorerTxUrl } from '$lib/utils.js'
7+
import { truncate, explorerBtcTxUrl } from '$lib/utils'
8+
import { findSbtcEventByBitcoinAddress, findSbtcEventsByPage } from '$lib/bridge_api'
9+
import { fmtNumber, type SbtcClarityEvent } from 'sbtc-bridge-lib'
10+
import { satsToBitcoin } from '$lib/utils'
11+
import ArrowUpRight from '$lib/components/shared/ArrowUpRight.svelte';
12+
import Paging from '$lib/components/transactions/Paging.svelte';
13+
import { sbtcConfig } from '$stores/stores';
14+
import { CONFIG } from '$lib/config';
15+
16+
// fetch/hydrate data from local storage
17+
let inited = false;
18+
let sbtcEvents:{ results: Array<SbtcClarityEvent>, events:number}
19+
let errorReason:string|undefined;
20+
let myDepositsFilter:boolean;
21+
const limit = 20;
22+
let numPages = 0;
23+
24+
const getReclaimUrl = (pegin:any) => {
25+
return '/transactions/' + pegin.bitcoinTxid.payload.value.split('x')[1]
26+
}
27+
28+
const getType = (eventType:string|undefined) => {
29+
return (eventType === 'mint') ? 'deposit' : 'withdrawal'
30+
}
31+
32+
const getAddress = (event:any) => {
33+
const type = getType(event.payloadData.eventType)
34+
if (event.payloadData.eventType === 'mint') {
35+
return event.recipient
36+
}
37+
}
38+
39+
const toggleMine = async () => {
40+
sbtcEvents.results = []
41+
sbtcEvents.events = 0
42+
myDepositsFilter = !myDepositsFilter
43+
if (!myDepositsFilter) await fetchPageCheck(0)
44+
else fetchMine()
45+
}
46+
47+
const fetchMine = async () => {
48+
const mySbtcEvents = await findSbtcEventByBitcoinAddress($sbtcConfig.keySets[CONFIG.VITE_NETWORK].cardinal)
49+
sbtcEvents.results = mySbtcEvents
50+
sbtcEvents.events = mySbtcEvents.length
51+
}
52+
53+
const fetchPage = async (evt:any) => {
54+
await fetchPageCheck(evt.detail.page)
55+
}
56+
57+
const fetchPageCheck = async (mypage:number) => {
58+
if (mypage < 0) mypage = 0
59+
if (mypage > numPages) mypage = numPages
60+
sbtcEvents = await findSbtcEventsByPage(mypage, limit)
61+
const resid = ((sbtcEvents.events % limit) > 0) ? 1 : 0;
62+
numPages = Math.floor(sbtcEvents.events / limit) + resid;
63+
}
64+
65+
onMount(async () => {
66+
try {
67+
let mypage = 0;
68+
if ($page.url.searchParams.has('page')) {
69+
mypage = Number($page.url.searchParams.get('page')) - 1
70+
}
71+
await fetchPageCheck(mypage)
72+
inited = true;
73+
} catch (err) {
74+
errorReason = COMMS_ERROR;
75+
}
76+
})
77+
</script>
78+
79+
80+
<Table>
81+
<TableHead class="!dark:bg-transparent !bg-transparent !text-base !text-white !normal-case border-b border-white">
82+
<TableHeadCell class="!px-0 !font-normal">Amount (BTC)</TableHeadCell>
83+
<TableHeadCell class="!px-0 !font-normal">Address</TableHeadCell>
84+
<TableHeadCell class="!px-0 !font-normal">Type</TableHeadCell>
85+
<TableHeadCell class="!px-0 !font-normal">Height</TableHeadCell>
86+
<TableHeadCell class="!px-0 !font-normal !text-right">Actions</TableHeadCell>
87+
</TableHead>
88+
<TableBody>
89+
{#each sbtcEvents.results as event}
90+
<TableBodyRow class="!dark:bg-transparent !bg-transparent !border-transparent">
91+
<TableBodyCell class="!px-0 !py-2 !font-extralight">{satsToBitcoin(event.payloadData.amountSats)}</TableBodyCell>
92+
<TableBodyCell class="!px-0 !py-2 !font-extralight">
93+
<div class="flex items-center">
94+
<a class="" href={explorerBtcTxUrl(event.bitcoinTxid.payload.value.split('x')[1])} target="_blank" rel="noreferrer">{truncate(event.payloadData.spendingAddress, 5)}</a>
95+
<div class="ms-3">
96+
<ArrowUpRight class="h-6 w-6 text-white" target={explorerBtcTxUrl(event.bitcoinTxid.payload.value.split('x')[1])} />
97+
</div>
98+
</div>
99+
</TableBodyCell>
100+
<TableBodyCell class="!px-0 !py-2 !font-extralight">
101+
<div class="flex items-center">
102+
{#if getType(event.payloadData.eventType) === 'deposit'}
103+
<span class="border px-3 py-1 rounded-2xl text-yellow-400 border-yellow-400">
104+
{getType(event.payloadData.eventType)}
105+
</span>
106+
{:else}
107+
<span class="border px-3 py-1 rounded-2xl text-blue-400 border-blue-400">
108+
{getType(event.payloadData.eventType)}
109+
</span>
110+
{/if}
111+
<div class="ms-3">
112+
<ArrowUpRight class="h-6 w-6 text-white" target={explorerTxUrl(event.txid)} />
113+
</div>
114+
</div>
115+
</TableBodyCell>
116+
<TableBodyCell class="!px-0 !py-2 !font-extralight">{fmtNumber(event.payloadData.burnBlockHeight)}</TableBodyCell>
117+
<TableBodyCell class="!px-0 !py-2 !font-extralight !text-right">
118+
<a
119+
type="button"
120+
class="text-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500/50 hover:underline"
121+
href={getReclaimUrl(event)}
122+
>
123+
View details
124+
</a>
125+
</TableBodyCell>
126+
</TableBodyRow>
127+
{/each}
128+
</TableBody>
129+
</Table>
130+
131+
<div class="py-6 border-t border-white flex items-center justify-between mt-6">
132+
<div>
133+
<p class="text-sm font-extralight">
134+
Showing
135+
<span class="font-normal">1</span>
136+
to
137+
<span class="font-normal">10</span>
138+
of
139+
<span class="font-normal">97</span>
140+
results
141+
</p>
142+
</div>
143+
<Paging on:fetch_page={fetchPage} {numPages} totalEvents={(sbtcEvents) ? sbtcEvents.events : 0} limit={20}/>
144+
</div>
145+

src/lib/components/shared/ArrowUpRight.svelte

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ let anchorClass = clazz + ' rounded-md bg-black flex items-center justify-center
88
</script>
99

1010
<div class="ml-auto flex items-center">
11-
<a title="Show in Explorer" href={target} class={anchorClass} target="_blank" >
12-
<Icon src="{ArrowUpRight}" mini {clazz} aria-hidden="true" />
13-
</a>
11+
<a role="button" title="Show in Explorer" href={target} class={anchorClass} target="_blank" >
12+
<Icon src="{ArrowUpRight}" mini {clazz} aria-hidden="true" />
13+
</a>
1414
</div>
+66-64
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,80 @@
11
<script lang="ts">
22
import { afterNavigate, goto } from '$app/navigation';
3-
import { page } from '$app/stores';
4-
import { Pagination } from 'flowbite-svelte';
5-
import { ChevronLeftOutline, ChevronRightOutline, ListMusicOutline } from 'flowbite-svelte-icons';
6-
import { createEventDispatcher, onMount } from "svelte";
3+
import { page } from '$app/stores';
4+
import { Pagination } from 'flowbite-svelte';
5+
import { ChevronLeftOutline, ChevronRightOutline } from 'flowbite-svelte-icons';
6+
import { createEventDispatcher, onMount } from "svelte";
77
8-
const dispatch = createEventDispatcher();
8+
const dispatch = createEventDispatcher();
99
10-
export let totalEvents:number;
11-
export let limit:number;
12-
export let numPages:number;
13-
let inited = false;
10+
export let totalEvents:number;
11+
export let limit:number;
12+
export let numPages:number;
13+
let inited = false;
1414
15-
let pages:Array<{name:string, href:string, active:boolean}> = [];
15+
let pages:Array<{name:string, href:string, active:boolean}> = [];
1616
17-
$: activeUrl = $page.url.searchParams.get('page');
17+
$: activeUrl = $page.url.searchParams.get('page');
1818
19-
$: {
20-
pages.forEach((page) => {
21-
let splitUrl = page.href.split('?');
22-
let queryString = splitUrl.slice(1).join('?');
23-
const hrefParams = new URLSearchParams(queryString);
24-
let hrefValue = hrefParams.get('page');
25-
if (hrefValue === activeUrl) {
26-
page.active = true;
27-
} else {
28-
page.active = false;
29-
}
30-
});
31-
pages = pages;
32-
}
19+
$: {
20+
pages.forEach((page) => {
21+
let splitUrl = page.href.split('?');
22+
let queryString = splitUrl.slice(1).join('?');
23+
const hrefParams = new URLSearchParams(queryString);
24+
let hrefValue = hrefParams.get('page');
25+
if (hrefValue === activeUrl) {
26+
page.active = true;
27+
} else {
28+
page.active = false;
29+
}
30+
});
31+
pages = pages;
32+
}
3333
34-
const previous = () => {
35-
const current = ($page.url.searchParams.has('page')) ? Number($page.url.searchParams.get('page')) : 1;
36-
if (current <= 1) return
37-
goto('/transactions?page=' + (current - 1))
38-
};
39-
const next = () => {
40-
const current = ($page.url.searchParams.has('page')) ? Number($page.url.searchParams.get('page')) : 0;
41-
if (current >= numPages) return
42-
goto('/transactions?page=' + (current + 1))
43-
};
44-
afterNavigate((nav) => {
45-
const mypage = ($page.url.searchParams.size === 0) ? 0 : Number($page.url.searchParams.get('page'))
46-
dispatch("fetch_page", { page: mypage - 1 });
47-
})
34+
const previous = () => {
35+
const current = ($page.url.searchParams.has('page')) ? Number($page.url.searchParams.get('page')) : 1;
36+
if (current <= 1) return
37+
goto('/transactions?page=' + (current - 1))
38+
};
39+
const next = () => {
40+
const current = ($page.url.searchParams.has('page')) ? Number($page.url.searchParams.get('page')) : 0;
41+
if (current >= numPages) return
42+
goto('/transactions?page=' + (current + 1))
43+
};
44+
afterNavigate((nav) => {
45+
const mypage = ($page.url.searchParams.size === 0) ? 0 : Number($page.url.searchParams.get('page'))
46+
dispatch("fetch_page", { page: mypage - 1 });
47+
})
4848
49-
onMount(async () => {
50-
let active = false;
51-
for (let i=0; i < numPages; i++) {
52-
let name = Number(i+1)
53-
if ((i === 0 && ($page.url.searchParams.size === 0))) active = true
54-
else if ((i+1) === Number($page.url.searchParams.get('page'))) active = true
55-
pages.push({name: String(name), href: '/transactions?page=' + (i+1), active})
56-
active = false;
57-
}
58-
inited = true;
59-
})
49+
onMount(async () => {
50+
let active = false;
51+
for (let i=0; i < numPages; i++) {
52+
let name = Number(i+1)
53+
if ((i === 0 && ($page.url.searchParams.size === 0))) active = true
54+
else if ((i+1) === Number($page.url.searchParams.get('page'))) active = true
55+
pages.push({name: String(name), href: '/transactions?page=' + (i+1), active})
56+
active = false;
57+
}
58+
inited = true;
59+
})
6060
6161
</script>
6262

6363
{#if totalEvents > 0 && inited}
64-
<div class="">
65-
<div class="">
66-
<Pagination {pages} on:previous={previous} on:next={next} icon>
67-
<svelte:fragment slot="prev">
68-
<span class="sr-only">Previous</span>
69-
<ChevronLeftOutline class="w-2.5 h-2.5" />
70-
</svelte:fragment>
71-
<svelte:fragment slot="next">
72-
<span class="sr-only">Next</span>
73-
<ChevronRightOutline class="w-2.5 h-2.5" />
74-
</svelte:fragment>
75-
</Pagination>
76-
</div>
77-
</div>
64+
<Pagination
65+
{pages}
66+
on:previous={previous}
67+
on:next={next}
68+
normalClass="!bg-gray-1000 !dark:bg-gray-1000 !border-[0.5px] !border-gray-700 !hover:bg-gray-800 !dark:hover:bg-gray-800"
69+
activeClass="!dark:gray-700 !bg-primary-500/10 !border-[0.5px] !border-gray-700 !dark:bg-primary-500/10 !text-primary-500"
70+
>
71+
<svelte:fragment slot="prev">
72+
<span class="sr-only">Previous</span>
73+
<ChevronLeftOutline class="w-2.5 h-2.5" />
74+
</svelte:fragment>
75+
<svelte:fragment slot="next">
76+
<span class="sr-only">Next</span>
77+
<ChevronRightOutline class="w-2.5 h-2.5" />
78+
</svelte:fragment>
79+
</Pagination>
7880
{/if}

0 commit comments

Comments
 (0)