Skip to content

Commit

Permalink
Merge branch '307-make-app-more-responsive' of https://github.com/sta…
Browse files Browse the repository at this point in the history
…cks-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)
  • Loading branch information
radicleart committed Nov 27, 2023
2 parents 30a78a3 + 8b130ed commit 74b4478
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 194 deletions.
145 changes: 145 additions & 0 deletions src/lib/components/dashboard/shared/TxsHistoryTable.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<script lang="ts">
import { onMount } from 'svelte';
import { Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from 'flowbite-svelte';
import { page } from '$app/stores';
import { COMMS_ERROR, explorerTxUrl } from '$lib/utils.js'
import { truncate, explorerBtcTxUrl } from '$lib/utils'
import { findSbtcEventByBitcoinAddress, findSbtcEventsByPage } from '$lib/bridge_api'
import { fmtNumber, type SbtcClarityEvent } from 'sbtc-bridge-lib'
import { satsToBitcoin } from '$lib/utils'
import ArrowUpRight from '$lib/components/shared/ArrowUpRight.svelte';
import Paging from '$lib/components/transactions/Paging.svelte';
import { sbtcConfig } from '$stores/stores';
import { CONFIG } from '$lib/config';
// fetch/hydrate data from local storage
let inited = false;
let sbtcEvents:{ results: Array<SbtcClarityEvent>, events:number}
let errorReason:string|undefined;
let myDepositsFilter:boolean;
const limit = 20;
let numPages = 0;
const getReclaimUrl = (pegin:any) => {
return '/transactions/' + pegin.bitcoinTxid.payload.value.split('x')[1]
}
const getType = (eventType:string|undefined) => {
return (eventType === 'mint') ? 'deposit' : 'withdrawal'
}
const getAddress = (event:any) => {
const type = getType(event.payloadData.eventType)
if (event.payloadData.eventType === 'mint') {
return event.recipient
}
}
const toggleMine = async () => {
sbtcEvents.results = []
sbtcEvents.events = 0
myDepositsFilter = !myDepositsFilter
if (!myDepositsFilter) await fetchPageCheck(0)
else fetchMine()
}
const fetchMine = async () => {
const mySbtcEvents = await findSbtcEventByBitcoinAddress($sbtcConfig.keySets[CONFIG.VITE_NETWORK].cardinal)
sbtcEvents.results = mySbtcEvents
sbtcEvents.events = mySbtcEvents.length
}
const fetchPage = async (evt:any) => {
await fetchPageCheck(evt.detail.page)
}
const fetchPageCheck = async (mypage:number) => {
if (mypage < 0) mypage = 0
if (mypage > numPages) mypage = numPages
sbtcEvents = await findSbtcEventsByPage(mypage, limit)
const resid = ((sbtcEvents.events % limit) > 0) ? 1 : 0;
numPages = Math.floor(sbtcEvents.events / limit) + resid;
}
onMount(async () => {
try {
let mypage = 0;
if ($page.url.searchParams.has('page')) {
mypage = Number($page.url.searchParams.get('page')) - 1
}
await fetchPageCheck(mypage)
inited = true;
} catch (err) {
errorReason = COMMS_ERROR;
}
})
</script>


<Table>
<TableHead class="!dark:bg-transparent !bg-transparent !text-base !text-white !normal-case border-b border-white">
<TableHeadCell class="!px-0 !font-normal">Amount (BTC)</TableHeadCell>
<TableHeadCell class="!px-0 !font-normal">Address</TableHeadCell>
<TableHeadCell class="!px-0 !font-normal">Type</TableHeadCell>
<TableHeadCell class="!px-0 !font-normal">Height</TableHeadCell>
<TableHeadCell class="!px-0 !font-normal !text-right">Actions</TableHeadCell>
</TableHead>
<TableBody>
{#each sbtcEvents.results as event}
<TableBodyRow class="!dark:bg-transparent !bg-transparent !border-transparent">
<TableBodyCell class="!px-0 !py-2 !font-extralight">{satsToBitcoin(event.payloadData.amountSats)}</TableBodyCell>
<TableBodyCell class="!px-0 !py-2 !font-extralight">
<div class="flex items-center">
<a class="" href={explorerBtcTxUrl(event.bitcoinTxid.payload.value.split('x')[1])} target="_blank" rel="noreferrer">{truncate(event.payloadData.spendingAddress, 5)}</a>
<div class="ms-3">
<ArrowUpRight class="h-6 w-6 text-white" target={explorerBtcTxUrl(event.bitcoinTxid.payload.value.split('x')[1])} />
</div>
</div>
</TableBodyCell>
<TableBodyCell class="!px-0 !py-2 !font-extralight">
<div class="flex items-center">
{#if getType(event.payloadData.eventType) === 'deposit'}
<span class="border px-3 py-1 rounded-2xl text-yellow-400 border-yellow-400">
{getType(event.payloadData.eventType)}
</span>
{:else}
<span class="border px-3 py-1 rounded-2xl text-blue-400 border-blue-400">
{getType(event.payloadData.eventType)}
</span>
{/if}
<div class="ms-3">
<ArrowUpRight class="h-6 w-6 text-white" target={explorerTxUrl(event.txid)} />
</div>
</div>
</TableBodyCell>
<TableBodyCell class="!px-0 !py-2 !font-extralight">{fmtNumber(event.payloadData.burnBlockHeight)}</TableBodyCell>
<TableBodyCell class="!px-0 !py-2 !font-extralight !text-right">
<a
type="button"
class="text-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500/50 hover:underline"
href={getReclaimUrl(event)}
>
View details
</a>
</TableBodyCell>
</TableBodyRow>
{/each}
</TableBody>
</Table>

<div class="py-6 border-t border-white flex items-center justify-between mt-6">
<div>
<p class="text-sm font-extralight">
Showing
<span class="font-normal">1</span>
to
<span class="font-normal">10</span>
of
<span class="font-normal">97</span>
results
</p>
</div>
<Paging on:fetch_page={fetchPage} {numPages} totalEvents={(sbtcEvents) ? sbtcEvents.events : 0} limit={20}/>
</div>

6 changes: 3 additions & 3 deletions src/lib/components/shared/ArrowUpRight.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ let anchorClass = clazz + ' rounded-md bg-black flex items-center justify-center
</script>

<div class="ml-auto flex items-center">
<a title="Show in Explorer" href={target} class={anchorClass} target="_blank" >
<Icon src="{ArrowUpRight}" mini {clazz} aria-hidden="true" />
</a>
<a role="button" title="Show in Explorer" href={target} class={anchorClass} target="_blank" >
<Icon src="{ArrowUpRight}" mini {clazz} aria-hidden="true" />
</a>
</div>
130 changes: 66 additions & 64 deletions src/lib/components/transactions/Paging.svelte
Original file line number Diff line number Diff line change
@@ -1,78 +1,80 @@
<script lang="ts">
import { afterNavigate, goto } from '$app/navigation';
import { page } from '$app/stores';
import { Pagination } from 'flowbite-svelte';
import { ChevronLeftOutline, ChevronRightOutline, ListMusicOutline } from 'flowbite-svelte-icons';
import { createEventDispatcher, onMount } from "svelte";
import { page } from '$app/stores';
import { Pagination } from 'flowbite-svelte';
import { ChevronLeftOutline, ChevronRightOutline } from 'flowbite-svelte-icons';
import { createEventDispatcher, onMount } from "svelte";
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher();
export let totalEvents:number;
export let limit:number;
export let numPages:number;
let inited = false;
export let totalEvents:number;
export let limit:number;
export let numPages:number;
let inited = false;
let pages:Array<{name:string, href:string, active:boolean}> = [];
let pages:Array<{name:string, href:string, active:boolean}> = [];
$: activeUrl = $page.url.searchParams.get('page');
$: activeUrl = $page.url.searchParams.get('page');
$: {
pages.forEach((page) => {
let splitUrl = page.href.split('?');
let queryString = splitUrl.slice(1).join('?');
const hrefParams = new URLSearchParams(queryString);
let hrefValue = hrefParams.get('page');
if (hrefValue === activeUrl) {
page.active = true;
} else {
page.active = false;
}
});
pages = pages;
}
$: {
pages.forEach((page) => {
let splitUrl = page.href.split('?');
let queryString = splitUrl.slice(1).join('?');
const hrefParams = new URLSearchParams(queryString);
let hrefValue = hrefParams.get('page');
if (hrefValue === activeUrl) {
page.active = true;
} else {
page.active = false;
}
});
pages = pages;
}
const previous = () => {
const current = ($page.url.searchParams.has('page')) ? Number($page.url.searchParams.get('page')) : 1;
if (current <= 1) return
goto('/transactions?page=' + (current - 1))
};
const next = () => {
const current = ($page.url.searchParams.has('page')) ? Number($page.url.searchParams.get('page')) : 0;
if (current >= numPages) return
goto('/transactions?page=' + (current + 1))
};
afterNavigate((nav) => {
const mypage = ($page.url.searchParams.size === 0) ? 0 : Number($page.url.searchParams.get('page'))
dispatch("fetch_page", { page: mypage - 1 });
})
const previous = () => {
const current = ($page.url.searchParams.has('page')) ? Number($page.url.searchParams.get('page')) : 1;
if (current <= 1) return
goto('/transactions?page=' + (current - 1))
};
const next = () => {
const current = ($page.url.searchParams.has('page')) ? Number($page.url.searchParams.get('page')) : 0;
if (current >= numPages) return
goto('/transactions?page=' + (current + 1))
};
afterNavigate((nav) => {
const mypage = ($page.url.searchParams.size === 0) ? 0 : Number($page.url.searchParams.get('page'))
dispatch("fetch_page", { page: mypage - 1 });
})
onMount(async () => {
let active = false;
for (let i=0; i < numPages; i++) {
let name = Number(i+1)
if ((i === 0 && ($page.url.searchParams.size === 0))) active = true
else if ((i+1) === Number($page.url.searchParams.get('page'))) active = true
pages.push({name: String(name), href: '/transactions?page=' + (i+1), active})
active = false;
}
inited = true;
})
onMount(async () => {
let active = false;
for (let i=0; i < numPages; i++) {
let name = Number(i+1)
if ((i === 0 && ($page.url.searchParams.size === 0))) active = true
else if ((i+1) === Number($page.url.searchParams.get('page'))) active = true
pages.push({name: String(name), href: '/transactions?page=' + (i+1), active})
active = false;
}
inited = true;
})
</script>

{#if totalEvents > 0 && inited}
<div class="">
<div class="">
<Pagination {pages} on:previous={previous} on:next={next} icon>
<svelte:fragment slot="prev">
<span class="sr-only">Previous</span>
<ChevronLeftOutline class="w-2.5 h-2.5" />
</svelte:fragment>
<svelte:fragment slot="next">
<span class="sr-only">Next</span>
<ChevronRightOutline class="w-2.5 h-2.5" />
</svelte:fragment>
</Pagination>
</div>
</div>
<Pagination
{pages}
on:previous={previous}
on:next={next}
normalClass="!bg-gray-1000 !dark:bg-gray-1000 !border-[0.5px] !border-gray-700 !hover:bg-gray-800 !dark:hover:bg-gray-800"
activeClass="!dark:gray-700 !bg-primary-500/10 !border-[0.5px] !border-gray-700 !dark:bg-primary-500/10 !text-primary-500"
>
<svelte:fragment slot="prev">
<span class="sr-only">Previous</span>
<ChevronLeftOutline class="w-2.5 h-2.5" />
</svelte:fragment>
<svelte:fragment slot="next">
<span class="sr-only">Next</span>
<ChevronRightOutline class="w-2.5 h-2.5" />
</svelte:fragment>
</Pagination>
{/if}
Loading

0 comments on commit 74b4478

Please sign in to comment.