Skip to content

Commit

Permalink
feat: add pagination to token list
Browse files Browse the repository at this point in the history
  • Loading branch information
chalabi2 committed Dec 4, 2024
1 parent c0991f5 commit fa30e4a
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 132 deletions.
14 changes: 5 additions & 9 deletions components/bank/components/historyBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
import TxInfoModal from '../modals/txInfo';
import { shiftDigits, truncateString } from '@/utils';
import { BurnIcon, DenomImage, formatDenom, MintIcon } from '@/components';
import {
HistoryTxType,
useGetFilteredTxAndSuccessfulProposals,
useTokenFactoryDenomsMetadata,
} from '@/hooks';
import { HistoryTxType, useTokenFactoryDenomsMetadata } from '@/hooks';
import { ReceiveIcon, SendIcon } from '@/components/icons';
import { useEndpointStore } from '@/store/endpointStore';

import useIsMobile from '@/hooks/useIsMobile';

interface Transaction {
Expand Down Expand Up @@ -75,8 +71,8 @@ export function HistoryBox({
const { metadatas } = useTokenFactoryDenomsMetadata();

const isMobile = useIsMobile();
const skeletonGroupCount = isMobile ? 1 : 2;
const skeletonTxCount = isMobile ? 5 : 7;
const skeletonGroupCount = 1;
const skeletonTxCount = isMobile ? 5 : 9;

function formatDateShort(dateString: string): string {
const date = new Date(dateString);
Expand Down Expand Up @@ -169,7 +165,7 @@ export function HistoryBox({

return (
<div className="w-full mx-auto rounded-[24px] h-full flex flex-col">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center justify-between ">
<h3 className="text-lg md:text-xl font-semibold text-[#161616] dark:text-white">
Transaction History
</h3>
Expand Down
297 changes: 180 additions & 117 deletions components/bank/components/tokenList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useMemo } from 'react';
import React, { useState, useMemo, useEffect } from 'react';
import { DenomImage } from '@/components/factory';
import { shiftDigits } from '@/utils';
import { CombinedBalanceInfo } from '@/utils/types';
Expand Down Expand Up @@ -27,149 +27,212 @@ export default function TokenList({
const [searchTerm, setSearchTerm] = useState('');
const [selectedDenom, setSelectedDenom] = useState<any>(null);
const [isSendModalOpen, setIsSendModalOpen] = useState(false);
const [currentPage, setCurrentPage] = useState(1);

const isMobile = useIsMobile();

const pageSize = isMobile ? 4 : 9;

const filteredBalances = useMemo(() => {
if (!Array.isArray(balances)) return [];
return balances.filter(balance =>
balance.metadata?.display.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [balances, searchTerm]);
const isMobile = useIsMobile();
const skeletonCount = isMobile ? 5 : 9;

const totalPages = Math.max(1, Math.ceil(filteredBalances.length / pageSize));

useEffect(() => {
setCurrentPage(1);
}, [searchTerm]);

const paginatedBalances = useMemo(() => {
const startIndex = (currentPage - 1) * pageSize;
return filteredBalances.slice(startIndex, startIndex + pageSize);
}, [filteredBalances, currentPage, pageSize]);

return (
<div className="w-full mx-auto rounded-[24px] h-full flex flex-col">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 md:gap-0 mb-4">
<h3 className="text-lg md:text-xl font-semibold text-[#161616] dark:text-white">
Your Assets
</h3>
<div className="w-full md:w-auto">
<div className="flex flex-col gap-4 mb-4">
<div className="flex flex-row items-center justify-between">
<h3 className="text-lg md:text-xl font-semibold text-[#161616] dark:text-white">
Your Assets
</h3>

{totalPages > 1 && (
<div className="flex items-center gap-2">
<button
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage === 1 || isLoading}
className="p-2 hover:bg-[#0000001A] dark:hover:bg-[#FFFFFF1A] text-black dark:text-white rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
</button>

{[...Array(totalPages)].map((_, index) => {
const pageNum = index + 1;
if (
pageNum === 1 ||
pageNum === totalPages ||
(pageNum >= currentPage - 1 && pageNum <= currentPage + 1)
) {
return (
<button
key={pageNum}
onClick={() => setCurrentPage(pageNum)}
className={`w-8 h-8 flex items-center justify-center rounded-lg transition-colors
${
currentPage === pageNum
? 'bg-[#0000001A] dark:bg-[#FFFFFF1A] text-black dark:text-white'
: 'hover:bg-[#0000001A] dark:hover:bg-[#FFFFFF1A] text-black dark:text-white'
}`}
>
{pageNum}
</button>
);
} else if (pageNum === currentPage - 2 || pageNum === currentPage + 2) {
return (
<span key={pageNum} className="text-black dark:text-white">
...
</span>
);
}
return null;
})}

<button
onClick={() => setCurrentPage(prev => Math.min(totalPages, prev + 1))}
disabled={currentPage === totalPages || isLoading}
className="p-2 hover:bg-[#0000001A] dark:hover:bg-[#FFFFFF1A] text-black dark:text-white rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
</button>
</div>
)}
</div>

<div className="w-full">
<input
type="text"
placeholder="Search for a token..."
className="input input-md w-full md:w-64 pr-8 bg-[#0000000A] dark:bg-[#FFFFFF0F]"
className="input input-md w-full pr-8 bg-[#0000000A] dark:bg-[#FFFFFF0F]"
style={{ borderRadius: '12px' }}
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
/>
</div>
</div>

<div className="flex-1 overflow-hidden">
<div className="h-full overflow-y-auto pb-6">
{isLoading ? (
<div className="space-y-2">
{[...Array(skeletonCount)].map(i => (
<div
key={i}
className="flex flex-row justify-between gap-4 items-center p-4 bg-[#FFFFFFCC] dark:bg-[#FFFFFF0F] rounded-[16px] min-h-[80px]"
>
<div className="flex flex-row gap-4 items-center justify-start">
<div className="skeleton w-10 h-10 rounded-full" />
<div className="space-y-2">
<div className="skeleton h-4 w-24" />
<div className="skeleton h-3 w-16" />
</div>
</div>
<div className="skeleton h-4 w-32" />
<div className="flex flex-row gap-2">
<div className="skeleton w-8 h-8 rounded-md" />
<div className="skeleton w-8 h-8 rounded-md" />
<div className="flex-1">
{isLoading ? (
<div className="space-y-2">
{[...Array(pageSize)].map((_, i) => (
<div
key={i}
className="flex flex-row justify-between gap-4 items-center p-4 bg-[#FFFFFFCC] dark:bg-[#FFFFFF0F] rounded-[16px] min-h-[80px]"
>
<div className="flex flex-row gap-4 items-center justify-start">
<div className="skeleton w-10 h-10 rounded-full" />
<div className="space-y-2">
<div className="skeleton h-4 w-24" />
<div className="skeleton h-3 w-16" />
</div>
</div>
))}
</div>
) : filteredBalances.length === 0 ? (
<div className="flex items-center justify-center h-[200px] w-full bg-[#FFFFFFCC] dark:bg-[#FFFFFF0F] rounded-[16px]">
<p className="text-center text-[#00000099] dark:text-[#FFFFFF99]">No tokens found!</p>
</div>
) : (
<div className="space-y-2 overflow-y-auto">
{filteredBalances.map(balance => (
<div
key={balance.denom}
aria-label={balance.denom}
className="flex flex-row justify-between gap-4 items-center p-4 bg-[#FFFFFFCC] dark:bg-[#FFFFFF0F] rounded-[16px] cursor-pointer hover:bg-[#FFFFFF66] dark:hover:bg-[#FFFFFF1A] transition-colors"
onClick={() => {
setSelectedDenom(balance?.denom);
(
document?.getElementById(`denom-info-modal`) as HTMLDialogElement
)?.showModal();
}}
>
<div className="flex flex-row gap-4 items-center justify-start">
<div className="w-10 h-10 rounded-full overflow-hidden bg-[#0000000A] dark:bg-[#FFFFFF0F] flex items-center justify-center">
<DenomImage denom={balance.metadata} />
</div>
<div>
<p className="font-semibold text-[#161616] dark:text-white">
{truncateString(balance.metadata?.display ?? '', 12)}
</p>
<p className="text-sm text-[#00000099] dark:text-[#FFFFFF99]">
{balance.metadata?.denom_units[0]?.denom.split('/').pop()}
</p>
</div>
<div className="skeleton h-4 w-32" />
<div className="flex flex-row gap-2">
<div className="skeleton w-8 h-8 rounded-md" />
<div className="skeleton w-8 h-8 rounded-md" />
</div>
</div>
))}
</div>
) : paginatedBalances.length === 0 ? (
<div className="flex items-center justify-center h-[200px] w-full bg-[#FFFFFFCC] dark:bg-[#FFFFFF0F] rounded-[16px]">
<p className="text-center text-[#00000099] dark:text-[#FFFFFF99]">No tokens found!</p>
</div>
) : (
<div className="space-y-2">
{paginatedBalances.map(balance => (
<div
key={balance.denom}
aria-label={balance.denom}
className="flex flex-row justify-between gap-4 items-center p-4 bg-[#FFFFFFCC] dark:bg-[#FFFFFF0F] rounded-[16px] cursor-pointer hover:bg-[#FFFFFF66] dark:hover:bg-[#FFFFFF1A] transition-colors"
onClick={() => {
setSelectedDenom(balance?.denom);
(document?.getElementById(`denom-info-modal`) as HTMLDialogElement)?.showModal();
}}
>
<div className="flex flex-row gap-4 items-center justify-start">
<div className="w-10 h-10 rounded-full overflow-hidden bg-[#0000000A] dark:bg-[#FFFFFF0F] flex items-center justify-center">
<DenomImage denom={balance.metadata} />
</div>
<div className="text-center hidden sm:block">
<div>
<p className="font-semibold text-[#161616] dark:text-white">
{Number(
shiftDigits(
balance.amount,
-(balance.metadata?.denom_units[1]?.exponent ?? 6)
)
).toLocaleString(undefined, {
maximumFractionDigits: balance.metadata?.denom_units[1]?.exponent ?? 6,
})}{' '}
<span>
{truncateString(balance.metadata?.display ?? '', 12).toUpperCase()}
</span>
{truncateString(balance.metadata?.display ?? '', 12)}
</p>
<p className="text-sm text-[#00000099] dark:text-[#FFFFFF99]">
{balance.metadata?.denom_units[0]?.denom.split('/').pop()}
</p>
</div>
<div className="flex flex-row gap-2">
<button
onClick={e => {
e.stopPropagation();
setSelectedDenom(balance?.denom);
(
document?.getElementById(`denom-info-modal`) as HTMLDialogElement
)?.showModal();
}}
className="p-2 rounded-md bg-[#0000000A] dark:bg-[#FFFFFF0F] hover:bg-[#FFFFFF66] dark:hover:bg-[#FFFFFF33] transition-colors"
>
<QuestionIcon className="w-4 h-4 text-primary" />
</button>
<button
onClick={e => {
e.stopPropagation();
setSelectedDenom(balance?.denom);
setIsSendModalOpen(true);
}}
className="p-2 rounded-md bg-[#0000000A] dark:bg-[#FFFFFF0F] hover:bg-[#FFFFFF66] dark:hover:bg-[#FFFFFF33] transition-colors"
>
<SendTxIcon className="w-4 h-4 text-primary" />
</button>
</div>
</div>
))}
</div>
)}
{/* DenomInfoModal */}
<DenomInfoModal
denom={filteredBalances.find(b => b.denom === selectedDenom)?.metadata ?? null}
modalId="denom-info-modal"
/>
<SendModal
modalId="send-modal"
isOpen={isSendModalOpen}
address={address}
balances={balances ?? []}
isBalancesLoading={isLoading}
refetchBalances={refetchBalances}
refetchHistory={refetchHistory}
selectedDenom={selectedDenom}
setOpen={setIsSendModalOpen}
/>
</div>
<div className="text-center hidden sm:block md:block lg:hidden xl:block">
<p className="font-semibold text-[#161616] dark:text-white">
{Number(
shiftDigits(
balance.amount,
-(balance.metadata?.denom_units[1]?.exponent ?? 6)
)
).toLocaleString(undefined, {
maximumFractionDigits: balance.metadata?.denom_units[1]?.exponent ?? 6,
})}{' '}
<span>{truncateString(balance.metadata?.display ?? '', 12).toUpperCase()}</span>
</p>
</div>
<div className="flex flex-row gap-2">
<button
onClick={e => {
e.stopPropagation();
setSelectedDenom(balance?.denom);
(
document?.getElementById(`denom-info-modal`) as HTMLDialogElement
)?.showModal();
}}
className="p-2 rounded-md bg-[#0000000A] dark:bg-[#FFFFFF0F] hover:bg-[#FFFFFF66] dark:hover:bg-[#FFFFFF33] transition-colors"
>
<QuestionIcon className="w-4 h-4 text-primary" />
</button>
<button
onClick={e => {
e.stopPropagation();
setSelectedDenom(balance?.denom);
setIsSendModalOpen(true);
}}
className="p-2 rounded-md bg-[#0000000A] dark:bg-[#FFFFFF0F] hover:bg-[#FFFFFF66] dark:hover:bg-[#FFFFFF33] transition-colors"
>
<SendTxIcon className="w-4 h-4 text-primary" />
</button>
</div>
</div>
))}
</div>
)}
</div>

<DenomInfoModal
denom={filteredBalances.find(b => b.denom === selectedDenom)?.metadata ?? null}
modalId="denom-info-modal"
/>
<SendModal
modalId="send-modal"
isOpen={isSendModalOpen}
address={address}
balances={balances ?? []}
isBalancesLoading={isLoading}
refetchBalances={refetchBalances}
refetchHistory={refetchHistory}
selectedDenom={selectedDenom}
setOpen={setIsSendModalOpen}
/>
</div>
);
}
Loading

0 comments on commit fa30e4a

Please sign in to comment.