-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: UX/UI enhancements for Transaction history page
- Loading branch information
Showing
4 changed files
with
284 additions
and
182 deletions.
There are no files selected for viewing
145 changes: 145 additions & 0 deletions
145
src/lib/components/dashboard/shared/TxsHistoryTable.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} |
Oops, something went wrong.