Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: UX/UI enhancements for Transaction history page #294

Merged
merged 2 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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