Skip to content

Commit a4f3cc2

Browse files
authored
Smooth paginated transfers UI (#3885)
I'll have to think of how to make the pagination more generic and DRY, but getting this merged so that we have a transfers list to experiment with. - **feat(app2): introduce card component** - **fix(app): layout overflow issue, introduce sections** - **feat(app): fetch transfer and show details** - **feat(app2): load transfers with skeletons** - **feat(app): better transfers layout** - **feat(app2): pagination controler** - **fix(app2): transfers layout shift** - **feat(app): implement pagination** - **feat(app): paginate forwards and backwards** - **feat(app): live, next, prev pages**
2 parents e331078 + 63df525 commit a4f3cc2

15 files changed

+244
-53
lines changed

app2/src/app.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!doctype html>
2-
<html lang="en" class="dark dark:bg-zinc-900 dark:text-white">
2+
<html lang="en" class="dark dark:bg-zinc-950 dark:text-white">
33

44
<head>
55
<meta charset="utf-8" />

app2/src/lib/components/ChainList.svelte

+14-9
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
import { chains } from "$lib/stores/chains.svelte"
33
import { Option } from "effect"
44
import ChainComponent from "$lib/components/model/ChainComponent.svelte"
5+
import Card from "./ui/Card.svelte"
56
</script>
67

7-
{#if Option.isSome(chains.data)}
8-
<ul>
9-
{#each chains.data.value as chain}
10-
<li><ChainComponent {chain}/></li>
11-
{/each}
12-
</ul>
13-
{:else}
14-
Loading...
15-
{/if}
8+
<section class="p-4">
9+
<Card>
10+
{#if Option.isSome(chains.data)}
11+
<ul class="flex flex-col gap-4">
12+
{#each chains.data.value as chain}
13+
<li><ChainComponent {chain}/></li>
14+
{/each}
15+
</ul>
16+
{:else}
17+
Loading...
18+
{/if}
19+
</Card>
20+
</section>

app2/src/lib/components/layout/AppErrors/index.svelte

-4
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,5 @@ import ErrorComponent from "$lib/components/model/ErrorComponent.svelte"
88
<div class="max-w-full overflow-auto">
99
{#if Option.isSome(chains.error)}
1010
<ErrorComponent error={chains.error.value}/>
11-
{:else}
12-
<div class="bg-emerald-500 font-bold m-4 p-4 rounded">
13-
Chains data is fresh
14-
</div>
1511
{/if}
1612
</div>
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,31 @@
11
<script>
22
import ChainList from "$lib/components/ChainList.svelte"
3+
import Sections from "$lib/components/ui/Sections.svelte"
34
import { wallets } from "$lib/stores/wallets.svelte"
45
</script>
56

6-
<div class="p-4 flex flex-col gap-4">
7+
<Sections>
8+
<h1 class="font-extrabold text-3xl uppercase">Union</h1>
79
<section>
8-
<h2 class="font-bold text-xl m2-4">Transfer</h2>
10+
<h2 class="font-bold text-xl">Transfer</h2>
911
<ul>
1012
<li>Transfer</li>
1113
<li>Swap</li>
1214
<li>Stake</li>
1315
</ul>
1416
</section>
1517
<section>
16-
<h2 class="font-bold text-xl m2-4">Explorer</h2>
18+
<h2 class="font-bold text-xl">Explorer</h2>
1719
<ul>
1820
<li><a href="/" class="underline">Home</a></li>
1921
<li><a href="/explorer/transfers" class="underline">Transfers</a></li>
20-
<li>Packets</li>
21-
<li>Connections</li>
22-
<li>Channels</li>
22+
<!--<li><a href="/explorer/packets" class="underline">Packets</a></li>!-->
23+
<!--<li><a href="/explorer/connections" class="underline">Connections</a></li>!-->
24+
<!--<li><a href="/explorer/channels" class="underline">Channels</a></li>!-->
2325
</ul>
2426
</section>
2527

2628
<section>
27-
<h2 class="font-bold text-xl m2-4">Explorer</h2>
28-
<ul>
29-
<li>Transfers</li>
30-
<li>Connections</li>
31-
<li>Channels</li>
32-
</ul>
33-
</section>
3429
{wallets.evmAddress}
35-
</div>
30+
</section>
31+
</Sections>

app2/src/lib/components/model/ChainComponent.svelte

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ interface Props {
77
let { chain }: Props = $props()
88
</script>
99

10-
<div class="p-4">
11-
<h2 class="text-lg font-bold">{chain.display_name}</h2>
10+
<div>
11+
<p class="text-md font-bold">{chain.display_name}</p>
12+
<!--
1213
<div>{chain.chain_id}</div>
1314
<div>{chain.testnet}</div>
1415
<div>{chain.rpc_type}</div>
1516
<div>{JSON.stringify(chain.features)}</div>
17+
!-->
1618
</div>

app2/src/lib/components/model/ErrorComponent.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ interface Props {
1010
let { error }: Props = $props()
1111
</script>
1212

13-
<div class="p-4 rounded m-4 bg-red-500 overflow-auto flex flex-col gap-4">
13+
<div class="p-4 rounded bg-red-500 overflow-auto flex flex-col gap-4">
1414
<section>
1515
<h3 class="text-xl font-bold">{error._tag}</h3>
1616
<pre>{error.message}</pre>

app2/src/lib/components/model/TransferListItemComponent.svelte

Whitespace-only changes.
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script lang="ts">
2+
import type { Snippet } from "svelte"
3+
4+
// Card.svelte
5+
type Props = {
6+
children: Snippet
7+
class?: string
8+
}
9+
10+
// Define props with default
11+
const { children, class: className = "" }: Props = $props()
12+
</script>
13+
14+
<div class="rounded border dark:border-zinc-700 dark:bg-zinc-900 shadow-sm {className}">
15+
{@render children()}
16+
</div>
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script lang="ts">
2+
import type { Snippet } from "svelte"
3+
4+
// Card.svelte
5+
type Props = {
6+
children: Snippet
7+
class?: string
8+
}
9+
10+
// Define props with default
11+
const { children, class: className = "" }: Props = $props()
12+
</script>
13+
<label class="uppercase text-zinc-500 text-xs font-semibold block {className}">{@render children()}</label>
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script lang="ts">
2+
import type { Snippet } from "svelte"
3+
4+
// Card.svelte
5+
type Props = {
6+
children: Snippet
7+
class?: string
8+
}
9+
10+
// Define props with default
11+
const { children, class: className = "" }: Props = $props()
12+
</script>
13+
14+
<div class="flex flex-col gap-4 p-4 md:gap-6 md:p-6 {className}">
15+
{@render children()}
16+
</div>

app2/src/lib/queries/chains.svelte.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export let chainsQuery = (environment: Environment) =>
2828
}
2929
`),
3030
variables: { environment },
31-
refetchInterval: "5 seconds",
31+
refetchInterval: "60 seconds",
3232
writeData: data => {
3333
chains.data = data.pipe(Option.map(d => d.v1_ibc_union_chains))
3434
},

app2/src/lib/queries/transfer-list.svelte.ts

+62-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ import { graphql } from "gql.tada"
44
import { transferList } from "$lib/stores/transfers.svelte"
55
import { transferListItemFragment } from "$lib/queries/fragments/transfer-list-item"
66
import { TransferList } from "$lib/schema/transfer-list"
7+
import type { SortOrder } from "$lib/schema/sort-order"
78

89
export let transferListLatestQuery = createQueryGraphql({
910
schema: Schema.Struct({ v1_ibc_union_fungible_asset_orders: TransferList }),
1011
document: graphql(
1112
`
12-
query TransferList @cached(ttl: 1) {
13+
query TransferListLatest {
1314
v1_ibc_union_fungible_asset_orders(
1415
limit: 20,
16+
distinct_on: sort_order
1517
order_by: { sort_order: desc_nulls_last}) {
1618
...TransferListItem
1719
}
@@ -20,11 +22,69 @@ export let transferListLatestQuery = createQueryGraphql({
2022
[transferListItemFragment]
2123
),
2224
variables: {},
23-
refetchInterval: "1 second",
25+
refetchInterval: "200 millis",
2426
writeData: data => {
2527
transferList.data = data.pipe(Option.map(d => d.v1_ibc_union_fungible_asset_orders))
2628
},
2729
writeError: error => {
2830
transferList.error = error
2931
}
3032
})
33+
34+
export let transferListPageLtQuery = (page: typeof SortOrder.Type) =>
35+
createQueryGraphql({
36+
schema: Schema.Struct({ v1_ibc_union_fungible_asset_orders: TransferList }),
37+
document: graphql(
38+
`
39+
query TransferListPage($page: String!) @cached(ttl: 30) {
40+
v1_ibc_union_fungible_asset_orders(
41+
limit: 20,
42+
distinct_on: sort_order
43+
where: {sort_order: {_lt: $page}},
44+
order_by: {sort_order: desc_nulls_last}
45+
) {
46+
...TransferListItem
47+
}
48+
}
49+
`,
50+
[transferListItemFragment]
51+
),
52+
variables: { page },
53+
refetchInterval: "30 seconds",
54+
writeData: data => {
55+
transferList.data = data.pipe(Option.map(d => d.v1_ibc_union_fungible_asset_orders))
56+
},
57+
writeError: error => {
58+
transferList.error = error
59+
}
60+
})
61+
62+
export let transferListPageGtQuery = (page: typeof SortOrder.Type) =>
63+
createQueryGraphql({
64+
schema: Schema.Struct({ v1_ibc_union_fungible_asset_orders: TransferList }),
65+
document: graphql(
66+
`
67+
query TransferListPage($page: String!) @cached(ttl: 30) {
68+
v1_ibc_union_fungible_asset_orders(
69+
limit: 20,
70+
distinct_on: sort_order
71+
where: {sort_order: {_gt: $page}},
72+
order_by: {sort_order: asc_nulls_last}
73+
) {
74+
...TransferListItem
75+
}
76+
}
77+
`,
78+
[transferListItemFragment]
79+
),
80+
variables: { page },
81+
refetchInterval: "30 seconds",
82+
writeData: data => {
83+
transferList.data = data.pipe(
84+
Option.map(d => d.v1_ibc_union_fungible_asset_orders.toReversed())
85+
)
86+
},
87+
writeError: error => {
88+
transferList.error = error
89+
}
90+
})

app2/src/lib/schema/chain.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Schema } from "effect"
1+
import { Option, Schema } from "effect"
22

33
export const ChainId = Schema.String.pipe(Schema.brand("ChainId"))
44
export const ChainDisplayName = Schema.String.pipe(Schema.brand("ChainDisplayName"))
@@ -28,3 +28,8 @@ export class Chain extends Schema.Class<Chain>("Chain")({
2828
}) {}
2929

3030
export const Chains = Schema.Array(Chain)
31+
32+
export const getChain = (
33+
chains: typeof Chains.Type,
34+
chainId: typeof ChainId.Type
35+
): Option.Option<Chain> => Option.fromNullable(chains.find(chain => chain.chain_id === chainId))

app2/src/routes/+layout.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ onMount(() => {
1616
</script>
1717

1818
<div class="grid grid-cols-[auto_1fr] min-h-[100svh] w-screen">
19-
<aside class="fixed top-0 left-0 bottom-0 w-64 dark:bg-zinc-800 shadow overflow-auto">
19+
<aside class="fixed top-0 left-0 bottom-0 w-64 dark:bg-zinc-900 shadow overflow-auto">
2020
<Sidebar/>
2121
</aside>
2222

2323
<!-- Main content area: Has margin to clear fixed sidebar -->
24-
<main class="col-start-2 ml-64 max-w-[calc(100vw - calc(var(--spacing)*64))] overflow-hidden">
24+
<main class="col-start-2 ml-64 max-w-[calc(100vw-calc(var(--spacing)*64))]">
2525
<AppErrors/>
2626
{@render children()}
2727
</main>

0 commit comments

Comments
 (0)